diff --git a/Dockerfile.build b/Containerfile similarity index 57% rename from Dockerfile.build rename to Containerfile index 284d458..7c5a9ab 100644 --- a/Dockerfile.build +++ b/Containerfile @@ -1,4 +1,4 @@ -FROM registry.fedoraproject.org/fedora:43 +FROM registry.fedoraproject.org/fedora:43 as builder RUN dnf install -y \ rust \ @@ -30,6 +30,18 @@ RUN cargo clippy -- -D warnings RUN cargo build -r # Test will require stub binary to be available -ENV PATH="${PATH}:/berserker" +ENV PATH="${PATH}:/berserker:/berserker/target/release" RUN cargo test + +FROM registry.fedoraproject.org/fedora:43 + +RUN mkdir /etc/berserker + +COPY --from=builder /berserker/target/release/berserker /usr/local/bin/berserker +COPY --from=builder /berserker/workload.toml /etc/berserker/workload.toml +COPY --from=builder /berserker/stub /usr/local/bin/stub + +ENV PATH="${PATH}:/usr/local/bin" + +ENTRYPOINT berserker diff --git a/Containerfile.test b/Containerfile.test new file mode 100644 index 0000000..0e54a16 --- /dev/null +++ b/Containerfile.test @@ -0,0 +1,14 @@ +FROM berserker:latest + +RUN dnf install -y \ + which \ + bpftrace \ + bpftool \ + procps-ng + +# Test will require stub binary to be available +ENV PATH="${PATH}:/berserker:/usr/local/bin" + +ADD ./tests /tests/ + +ENTRYPOINT ["/tests/entrypoint.sh"] diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b5cd26c..0000000 --- a/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM builder:latest as builder - -FROM registry.fedoraproject.org/fedora:43 - -RUN mkdir /etc/berserker - -COPY --from=builder /berserker/target/release/berserker /usr/local/bin/berserker -COPY --from=builder /berserker/workload.toml /etc/berserker/workload.toml -COPY --from=builder /berserker/stub /usr/local/bin/stub - -ENV PATH="${PATH}:/usr/local/bin" - -ENTRYPOINT berserker diff --git a/Makefile b/Makefile index 4171400..679fb90 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,11 @@ ifeq ($(BERSERKER_TAG),) BERSERKER_TAG=$(shell git describe --tags --abbrev=10 --dirty) endif - .PHONY: all all: - docker build -t builder -f Dockerfile.build . - docker build -t berserker . + docker build -t berserker -f Containerfile . + docker build -t berserker-test -f Containerfile.test . + docker run --privileged berserker-test .PHONY: build-network build-berserker-network: diff --git a/tests/entrypoint.sh b/tests/entrypoint.sh new file mode 100755 index 0000000..aeb3289 --- /dev/null +++ b/tests/entrypoint.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +mount -t debugfs none /sys/kernel/debug + +./tests/workers/script/unit/smoke.sh diff --git a/tests/workers/script/dist/smoke.sh b/tests/workers/script/dist/smoke.sh new file mode 100755 index 0000000..eb927d6 --- /dev/null +++ b/tests/workers/script/dist/smoke.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +set -eou pipefail + +stop() { echo "$*" 1>&2 ; exit 1; } + +which bpftrace &>/dev/null || stop "Don't have bpftrace" +which bpftool &>/dev/null || stop "Don't have bpftool" +which berserker &>/dev/null || stop "Don't have berserker" +which stub &>/dev/null || stop "Don't have random process tool" +which pkill &>/dev/null || stop "Don't have pkill" + +if [ ! -d "tests/workers/script/unit" ]; then + echo "Can't find test directory. Smoke tests have to be run from the project root directory" +fi + +echo "Cleanup..." +rm -f /tmp/berserker.log +rm -f /tmp/events.log + +# in case if it's still running from a previous run +pkill berserker || true + +# make berserkers verbose +export RUST_LOG=trace + +echo "Starting bpftrace..." +bpftrace tests/workers/script/unit/syscalls.bt &> /tmp/events.log & + +# let bpftrace attach probes +attempts=0 + +while ! bpftool prog | grep -q bpftrace ; +do + if [[ "$attempts" -gt 40 ]]; then + echo "Can't find bpftool after ${attempts} attempts." + cat /tmp/events.log + exit 1 + fi; + + attempts=$((attempts+1)) + echo "Wait for bpftrace"; + sleep 0.5; +done + +echo "Starting berserker..." +berserker -f tests/workers/script/dist/workload.ber &> /tmp/berserker.log & + +# let berserker do some work +sleep 5; + +echo "Stopping..." +pkill berserker || true +pkill bpftrace || true + +echo "Verifying the results..." +if ! grep -q -E 'exec .*/stub' /tmp/events.log; then + echo "FAIL: no task instruction" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +if ! grep -q -E 'openat /tmp/tests/test' /tmp/events.log; then + echo "FAIL: no open instruction" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +if ! grep -E 'openat /tmp/tests/.*' /tmp/events.log | grep -q -v '/tmp/tests/test'; then + echo "FAIL: no open random path instruction" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +if ! grep -q -E 'connect .*' /tmp/events.log; then + echo "FAIL: no ping instruction" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +if ! grep -q -E 'sendto .*' /tmp/events.log; then + echo "FAIL: ping instruction did not work" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +echo "PASS" + +rm -f /tmp/berserker.log +rm -f /tmp/events.log + +exit 0; diff --git a/tests/workers/script/dist/syscalls.bt b/tests/workers/script/dist/syscalls.bt new file mode 100644 index 0000000..c47fe22 --- /dev/null +++ b/tests/workers/script/dist/syscalls.bt @@ -0,0 +1,20 @@ +t:syscalls:sys_enter_execve /comm == "berserker"/ +{ + printf("exec %s\n", str(args->filename)); +} + +t:syscalls:sys_enter_openat /comm == "berserker"/ { + printf("openat %s\n", str(args->filename)); +} + +t:syscalls:sys_enter_connect /comm == "berserker"/ { + $addr_in = (struct sockaddr_in *)args->uservaddr; + $addr = ntop($addr_in->sin_addr.s_addr); + printf("connect %s\n", $addr); +} + +t:syscalls:sys_enter_sendto /comm == "berserker"/ { + $addr_in = (struct sockaddr_in *)args->addr; + $addr = ntop($addr_in->sin_addr.s_addr); + printf("sendto %s\n", $addr); +} diff --git a/tests/workers/script/dist/workload.ber b/tests/workers/script/dist/workload.ber new file mode 100644 index 0000000..1338a6c --- /dev/null +++ b/tests/workers/script/dist/workload.ber @@ -0,0 +1,20 @@ +machine { + server(8080); + path("/tmp/tests"); +} + +main (workers = 1) { + debug("run task stub"); + task(stub); + + debug("open file /tmp/tests/test"); + open("/tmp/tests/test"); + + debug("open random file under /tmp/tests/"); + open(random_path("/tmp/tests")); + + debug("ping server"); + ping("127.0.0.1:8080"); +} : exp { + rate = 10.0; +} diff --git a/tests/workers/script/unit/smoke.sh b/tests/workers/script/unit/smoke.sh new file mode 100755 index 0000000..8509640 --- /dev/null +++ b/tests/workers/script/unit/smoke.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -eou pipefail + +stop() { echo "$*" 1>&2 ; exit 1; } + +which bpftrace &>/dev/null || stop "Don't have bpftrace" +which bpftool &>/dev/null || stop "Don't have bpftool" +which berserker &>/dev/null || stop "Don't have berserker" +which stub &>/dev/null || stop "Don't have random process tool" +which pkill &>/dev/null || stop "Don't have pkill" + +if [ ! -d "tests/workers/script/unit" ]; then + echo "Can't find test directory. Smoke tests have to be run from the project root directory" +fi + +echo "Cleanup..." +rm -f /tmp/berserker.log +rm -f /tmp/events.log + +# in case if it's still running from a previous run +pkill berserker || true + +# make berserkers verbose +export RUST_LOG=trace + +echo "Starting bpftrace..." +bpftrace tests/workers/script/unit/syscalls.bt &> /tmp/events.log & + +# let bpftrace attach probes +attempts=0 + +while ! bpftool prog | grep -q bpftrace ; +do + if [[ "$attempts" -gt 40 ]]; then + echo "Can't find bpftool after ${attempts} attempts." + cat /tmp/events.log + bpftool prog + bpftool prog | grep -q bpftrace + exit 1 + fi; + + attempts=$((attempts+1)) + echo "Wait for bpftrace"; + sleep 0.5; +done + +echo "Starting berserker..." +berserker -f tests/workers/script/unit/workload.ber &> /tmp/berserker.log + +echo "Stopping..." +pkill berserker || true +pkill bpftrace || true + +echo "Verifying the results..." +if ! grep -q -E 'exec .*/stub' /tmp/events.log; then + echo "FAIL: no task instruction" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +if ! grep -q -E 'openat /tmp/tests/test' /tmp/events.log; then + echo "FAIL: no open instruction" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +if ! grep -E 'openat /tmp/tests/.*' /tmp/events.log | grep -q -v '/tmp/tests/test'; then + echo "FAIL: no open random path instruction" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +if ! grep -q -E 'connect .*' /tmp/events.log; then + echo "FAIL: no ping instruction" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +if ! grep -q -E 'sendto .*' /tmp/events.log; then + echo "FAIL: ping instruction did not work" + cat /tmp/berserker.log + cat /tmp/events.log + exit 1; +fi + +echo "PASS" + +rm -f /tmp/berserker.log +rm -f /tmp/events.log + +exit 0; diff --git a/tests/workers/script/unit/syscalls.bt b/tests/workers/script/unit/syscalls.bt new file mode 100644 index 0000000..c47fe22 --- /dev/null +++ b/tests/workers/script/unit/syscalls.bt @@ -0,0 +1,20 @@ +t:syscalls:sys_enter_execve /comm == "berserker"/ +{ + printf("exec %s\n", str(args->filename)); +} + +t:syscalls:sys_enter_openat /comm == "berserker"/ { + printf("openat %s\n", str(args->filename)); +} + +t:syscalls:sys_enter_connect /comm == "berserker"/ { + $addr_in = (struct sockaddr_in *)args->uservaddr; + $addr = ntop($addr_in->sin_addr.s_addr); + printf("connect %s\n", $addr); +} + +t:syscalls:sys_enter_sendto /comm == "berserker"/ { + $addr_in = (struct sockaddr_in *)args->addr; + $addr = ntop($addr_in->sin_addr.s_addr); + printf("sendto %s\n", $addr); +} diff --git a/tests/workers/script/unit/workload.ber b/tests/workers/script/unit/workload.ber new file mode 100644 index 0000000..09b6c4c --- /dev/null +++ b/tests/workers/script/unit/workload.ber @@ -0,0 +1,18 @@ +machine { + server(8080); + path("/tmp/tests"); +} + +main (workers = 1) { + debug("run task stub"); + task(stub); + + debug("open file /tmp/tests/test"); + open("/tmp/tests/test"); + + debug("open random file under /tmp/tests/"); + open(random_path("/tmp/tests")); + + debug("ping server"); + ping("127.0.0.1:8080"); +}