diff --git a/apps/starry/README.md b/apps/starry/README.md index ec21856503..3d2b1f77bb 100644 --- a/apps/starry/README.md +++ b/apps/starry/README.md @@ -93,6 +93,18 @@ cargo xtask starry qemu \ See `jcode/README.md` for interactive usage and troubleshooting. +## Nginx + +The `nginx` case is a QEMU app integration workflow. It installs Alpine nginx +packages in a staging root during prebuild, injects runtime artifacts to the +app overlay, then runs nginx smoke tests inside StarryOS. + +```bash +cargo xtask starry app run -t nginx --arch x86_64 +``` + +`apps/starry/nginx` maintains four directories: `smoke`, `phase`, `stress`, and +`debug`. Currently only smoke is connected as nginx test entry in tgoskits workflows. ## Orange Pi 5 Plus UVC diff --git a/apps/starry/nginx/README.md b/apps/starry/nginx/README.md new file mode 100644 index 0000000000..a307578f9b --- /dev/null +++ b/apps/starry/nginx/README.md @@ -0,0 +1,38 @@ +# Starry Nginx App + +This app organizes nginx tests into four directories: + +- `smoke/`: CI entry smoke test (the only connected nginx test in tgoskits) +- `phase/`: stage-unit tests named by `x-x`, such as `nginx-1-3-lifecycle-tests.sh` +- `stress/`: pressure test planning and future scripts +- `debug/`: flexible issue-focused scripts + +## CI-Connected Entry + +Only smoke is connected: + +```bash +cargo xtask starry app run -t nginx --arch riscv64 +``` + +`qemu-*.toml` starts `/usr/bin/nginx-smoke-tests.sh` only. + +## Local Development CLI + +Unified local CLI script: + +```bash +./apps/starry/nginx/nginx-cli-tests.sh smoke +./apps/starry/nginx/nginx-cli-tests.sh phase1 +./apps/starry/nginx/nginx-cli-tests.sh phase2 +./apps/starry/nginx/nginx-cli-tests.sh all +``` + +## Build/Prepare Logic + +`prebuild.sh` injects only smoke entry and shared mirror helper into guest overlay: + +- `/usr/bin/nginx-smoke-tests.sh` +- `/usr/bin/nginx-alpine-mirror.sh` + +Mirror helper: `apps/starry/nginx/nginx-alpine-mirror.sh`. diff --git a/apps/starry/nginx/build-riscv64gc-unknown-none-elf.toml b/apps/starry/nginx/build-riscv64gc-unknown-none-elf.toml new file mode 100644 index 0000000000..337b59787c --- /dev/null +++ b/apps/starry/nginx/build-riscv64gc-unknown-none-elf.toml @@ -0,0 +1,15 @@ +target = "riscv64gc-unknown-none-elf" +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +log = "Warn" +features = [ + "ax-feat/rtc", + "ax-driver/rtc", + "ax-driver/serial", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "starry-kernel/input", +] +plat_dyn = true diff --git a/apps/starry/nginx/build-x86_64-unknown-none.toml b/apps/starry/nginx/build-x86_64-unknown-none.toml new file mode 100644 index 0000000000..48f79a5df5 --- /dev/null +++ b/apps/starry/nginx/build-x86_64-unknown-none.toml @@ -0,0 +1,14 @@ +target = "x86_64-unknown-none" +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +log = "Warn" +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/plat-static", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] +plat_dyn = false diff --git a/apps/starry/nginx/debug/README.md b/apps/starry/nginx/debug/README.md new file mode 100644 index 0000000000..44f936a83f --- /dev/null +++ b/apps/starry/nginx/debug/README.md @@ -0,0 +1,12 @@ +# Nginx Debug Tests + +This directory stores flexible debug scripts for single issue reproduction and diagnosis. + +Current scripts: + +- `nginx-http-basic-tests.sh`: early HTTP basic script kept for issue-level debugging. + +Rule: + +- Debug scripts are free-form and can focus on one syscall or one behavior path. +- Debug scripts are not connected to tgoskits nginx CI entry. diff --git a/apps/starry/nginx/debug/nginx-http-basic-tests.sh b/apps/starry/nginx/debug/nginx-http-basic-tests.sh new file mode 100644 index 0000000000..b63f360915 --- /dev/null +++ b/apps/starry/nginx/debug/nginx-http-basic-tests.sh @@ -0,0 +1,183 @@ +#!/bin/sh + +BASE=/tmp/nginx-http-basic +CONF="$BASE/conf/http-basic.conf" +WWW="$BASE/www" +LOGDIR="$BASE/logs" +OUT="$BASE/out" +NGINX_PID= +FAILURES=0 +TIMEOUT_CMD= + +. /usr/bin/nginx-alpine-mirror.sh + +log() { printf 'NGINX_HTTP_BASIC_LOG: %s\n' "$*"; } +pass() { printf 'NGINX_HTTP_BASIC_STEP_PASS: %s\n' "$*"; } +fail() { printf 'NGINX_HTTP_BASIC_STEP_FAIL: %s\n' "$*"; FAILURES=$((FAILURES + 1)); } + +run_step() { + step_name=$1 + shift + log "BEGIN $step_name" + if "$@"; then + pass "$step_name" + return 0 + fi + fail "$step_name" + return 1 +} + +init_timeout_cmd() { + if command -v timeout >/dev/null 2>&1; then + TIMEOUT_CMD='timeout' + return 0 + fi + if busybox timeout 2>&1 | grep -qi 'usage'; then + TIMEOUT_CMD='busybox timeout' + return 0 + fi + TIMEOUT_CMD= + return 0 +} + +run_with_timeout() { + timeout_sec=$1 + shift + if [ -n "$TIMEOUT_CMD" ]; then + $TIMEOUT_CMD "$timeout_sec" "$@" + return $? + fi + "$@" +} + +cleanup() { + if [ -n "$NGINX_PID" ] && kill -0 "$NGINX_PID" 2>/dev/null; then + kill -TERM "$NGINX_PID" 2>/dev/null || true + sleep 1 + kill -KILL "$NGINX_PID" 2>/dev/null || true + fi +} + +finish() { + status=$? + cleanup + if [ "$FAILURES" -eq 0 ] && [ "$status" -eq 0 ]; then + printf 'NGINX_HTTP_BASIC_PASSED\n' + exit 0 + fi + printf 'NGINX_HTTP_BASIC_FAILED failures=%s status=%s\n' "$FAILURES" "$status" + exit 1 +} + +trap finish EXIT + +prepare_packages() { + nginx_apk_add_with_fallback nginx curl busybox-extras || { + printf 'NGINX_HTTP_BASIC_PREPARE_FAILED: all mirrors failed\n' + return 1 + } +} + +prepare_tree() { + rm -rf "$BASE" + mkdir -p "$BASE/conf" "$WWW/dir" "$LOGDIR" "$OUT" "$BASE/client_temp" + printf 'HTTP_BASIC_INDEX_OK\n' > "$WWW/index.html" + printf 'small static file\n' > "$WWW/small.txt" + : > "$WWW/empty.txt" + printf 'HTTP_BASIC_DIR_INDEX_OK\n' > "$WWW/dir/index.html" + + cat > "$CONF" <<'EOF_CONF' +daemon off; +master_process off; +worker_processes 1; +error_log /tmp/nginx-http-basic/logs/error.log debug; +pid /tmp/nginx-http-basic/nginx.pid; + +events { worker_connections 64; } + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /tmp/nginx-http-basic/logs/access.log; + sendfile off; + keepalive_timeout 5; + client_body_temp_path /tmp/nginx-http-basic/client_temp; + server { + listen 127.0.0.1:8080; + server_name localhost; + root /tmp/nginx-http-basic/www; + location / { index index.html; } + } +} +EOF_CONF +} + +test_config() { nginx -t -c "$CONF" -p "$BASE/"; } + +start_nginx() { + nginx -c "$CONF" -p "$BASE/" > "$LOGDIR/nginx-stdout.log" 2>&1 & + NGINX_PID=$! + i=0 + while [ "$i" -lt 90 ]; do + if ! kill -0 "$NGINX_PID" 2>/dev/null; then return 1; fi + if run_with_timeout 8 curl -fsS -o "$OUT/startup.body" http://127.0.0.1:8080/ >/dev/null 2>&1; then return 0; fi + sleep 1 + i=$((i + 1)) + done + return 1 +} + +test_get_small() { + code=$(run_with_timeout 12 curl -sS -D "$OUT/small.headers" -o "$OUT/small.body" -w '%{http_code}' http://127.0.0.1:8080/small.txt || printf 'curl_failed') + [ "$code" = "200" ] && grep -qx 'small static file' "$OUT/small.body" && grep -qi '^Content-Length: 18' "$OUT/small.headers" +} + +test_get_empty() { + code=$(run_with_timeout 12 curl -sS -D "$OUT/empty.headers" -o "$OUT/empty.body" -w '%{http_code}' http://127.0.0.1:8080/empty.txt || printf 'curl_failed') + [ "$code" = "200" ] && [ "$(wc -c < "$OUT/empty.body")" -eq 0 ] && grep -qi '^Content-Length: 0' "$OUT/empty.headers" +} + +test_get_dir_slash() { + code=$(run_with_timeout 12 curl -sS -D "$OUT/dir-slash.headers" -o "$OUT/dir-slash.body" -w '%{http_code}' http://127.0.0.1:8080/dir/ || printf 'curl_failed') + [ "$code" = "200" ] && grep -qx 'HTTP_BASIC_DIR_INDEX_OK' "$OUT/dir-slash.body" +} + +test_get_dir_redirect() { + code=$(run_with_timeout 12 curl -sS -D "$OUT/dir.headers" -o "$OUT/dir.body" -w '%{http_code}' http://127.0.0.1:8080/dir || printf 'curl_failed') + [ "$code" = "301" ] || [ "$code" = "302" ] +} + +test_bad_method() { + if command -v nc >/dev/null 2>&1; then + NC='nc' + elif busybox nc 2>&1 | grep -qi 'usage'; then + NC='busybox nc' + else + return 0 + fi + { printf 'BAD / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'; } | run_with_timeout 12 sh -c "$NC 127.0.0.1 8080" > "$OUT/bad-method.raw" + tr -d '\r' < "$OUT/bad-method.raw" > "$OUT/bad-method.normalized" + grep -Eq '^HTTP/1.1 (400|405)' "$OUT/bad-method.normalized" +} + +stop_nginx() { + kill -TERM "$NGINX_PID" + i=0 + while kill -0 "$NGINX_PID" 2>/dev/null && [ "$i" -lt 30 ]; do + sleep 1 + i=$((i + 1)) + done + ! kill -0 "$NGINX_PID" 2>/dev/null +} + +run_step "prepare packages" prepare_packages || exit 1 +run_step "prepare nginx files" prepare_tree || exit 1 +run_step "init timeout helper" init_timeout_cmd || exit 1 +run_step "nginx config test" test_config || exit 1 +run_step "start nginx" start_nginx || exit 1 +run_step "GET /small.txt" test_get_small +run_step "GET /empty.txt" test_get_empty +run_step "GET /dir/" test_get_dir_slash +run_step "GET /dir redirect" test_get_dir_redirect +run_step "BAD method returns 400/405" test_bad_method +run_step "stop nginx" stop_nginx diff --git a/apps/starry/nginx/nginx-alpine-mirror.sh b/apps/starry/nginx/nginx-alpine-mirror.sh new file mode 100644 index 0000000000..6b9ccb330d --- /dev/null +++ b/apps/starry/nginx/nginx-alpine-mirror.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +set -eu + +NGINX_APK_MIRROR_TIMEOUT_SEC="${NGINX_APK_MIRROR_TIMEOUT_SEC:-45}" +NGINX_APK_REPO_FILE="/tmp/nginx-apk-repositories" +NGINX_APK_ATTEMPT_LOG="/tmp/nginx-apk-attempt.log" + +write_repo_file() { + mirror="$1" + cat >"$NGINX_APK_REPO_FILE" <"$NGINX_APK_ATTEMPT_LOG" 2>&1; then + echo "NGINX_APK_MIRROR_OK: $mirror" + return 0 + fi + + rc=$? + if [ "$rc" -eq 124 ]; then + echo "NGINX_APK_MIRROR_FAIL: $mirror (timeout ${NGINX_APK_MIRROR_TIMEOUT_SEC}s)" + else + echo "NGINX_APK_MIRROR_FAIL: $mirror (apk rc=$rc)" + fi + sed -n '1,120p' "$NGINX_APK_ATTEMPT_LOG" || true + return 1 +} + +nginx_apk_add_with_fallback() { + mirror_cn="https://mirrors.tuna.tsinghua.edu.cn/alpine/latest-stable" + mirror_global="https://dl-cdn.alpinelinux.org/alpine/latest-stable" + + if run_apk_add "$mirror_cn" "$@"; then + return 0 + fi + if run_apk_add "$mirror_global" "$@"; then + return 0 + fi + + echo "NGINX_APK_ALL_MIRRORS_FAILED" + return 1 +} diff --git a/apps/starry/nginx/nginx-cli-tests.sh b/apps/starry/nginx/nginx-cli-tests.sh new file mode 100644 index 0000000000..21eeb50fe5 --- /dev/null +++ b/apps/starry/nginx/nginx-cli-tests.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -eu + +cmd=${1:-all} + +case "$cmd" in + smoke) + sh ./smoke/nginx-smoke-tests.sh + ;; + phase1) + sh ./phase/nginx-1-3-lifecycle-tests.sh + ;; + phase2) + sh ./phase/nginx-2-0-http-basic-tests.sh + ;; + all) + sh ./smoke/nginx-smoke-tests.sh + sh ./phase/nginx-1-3-lifecycle-tests.sh + sh ./phase/nginx-2-0-http-basic-tests.sh + printf 'NGINX_CLI_ALL_PASSED\n' + ;; + *) + printf 'usage: %s [smoke|phase1|phase2|all]\n' "$0" + exit 2 + ;; +esac diff --git a/apps/starry/nginx/phase/README.md b/apps/starry/nginx/phase/README.md new file mode 100644 index 0000000000..e25d2e0c55 --- /dev/null +++ b/apps/starry/nginx/phase/README.md @@ -0,0 +1,13 @@ +# Nginx Phase Tests + +Phase tests are organized by stage unit `x-x`. + +Current scripts: + +- `nginx-1-3-lifecycle-tests.sh`: covers phase 1.1, 1.2, and 1.3 lifecycle checks. +- `nginx-2-0-http-basic-tests.sh`: covers phase 2 HTTP basic semantics. + +Rule: + +- Each phase script must include all checks of that phase, including checks already covered by smoke. +- Phase scripts are managed for development iteration and are not connected as tgoskits nginx CI entry. diff --git a/apps/starry/nginx/phase/nginx-1-3-lifecycle-tests.sh b/apps/starry/nginx/phase/nginx-1-3-lifecycle-tests.sh new file mode 100644 index 0000000000..e6a75ecac4 --- /dev/null +++ b/apps/starry/nginx/phase/nginx-1-3-lifecycle-tests.sh @@ -0,0 +1,134 @@ +#!/bin/sh +set -eu + +BASE=/tmp/nginx-phase1 +WWW="$BASE/www" +CONF_DIR="$BASE/conf" +OUT="$BASE/out" +LOG_DIR="$BASE/logs" +TIMEOUT_CMD= + +log() { printf 'NGINX_PHASE1_LOG: %s\n' "$*"; } +fail() { printf 'NGINX_PHASE1_TEST_FAILED\n'; log "$*"; exit 1; } + +init_timeout_cmd() { + if command -v timeout >/dev/null 2>&1; then + TIMEOUT_CMD='timeout' + return + fi + if busybox timeout 2>&1 | grep -qi 'usage'; then + TIMEOUT_CMD='busybox timeout' + return + fi + fail "timeout command not available" +} + +run_with_timeout() { + sec=$1 + shift + $TIMEOUT_CMD "$sec" "$@" +} + +cleanup_nginx() { + killall -q nginx 2>/dev/null || true + sleep 1 + killall -q -9 nginx 2>/dev/null || true +} + +prepare_packages() { + repo_file=/etc/apk/repositories + original_repos="$(cat "$repo_file")" + for mirror in https://mirrors.cernet.edu.cn/alpine https://dl-cdn.alpinelinux.org/alpine; do + printf '%s\n' "$original_repos" | sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" + rm -f /lib/apk/db/lock + if run_with_timeout 40 apk --timeout 40 update && run_with_timeout 40 apk --timeout 40 add nginx curl busybox-extras procps; then + return 0 + fi + done + return 1 +} + +prepare_files() { + rm -rf "$BASE" + mkdir -p "$WWW" "$CONF_DIR" "$OUT" "$LOG_DIR" + printf 'PHASE1_OK\n' > "$WWW/index.html" + cat > "$CONF_DIR/single.conf" <<'EOF' +daemon off; +master_process off; +worker_processes 1; +error_log /tmp/nginx-phase1/logs/error-single.log debug; +pid /tmp/nginx-phase1/nginx-single.pid; +events { worker_connections 64; } +http { include /etc/nginx/mime.types; access_log /tmp/nginx-phase1/logs/access-single.log; server { listen 127.0.0.1:8080; root /tmp/nginx-phase1/www; location / { index index.html; } } } +EOF + cat > "$CONF_DIR/master1.conf" <<'EOF' +daemon off; +master_process on; +worker_processes 1; +error_log /tmp/nginx-phase1/logs/error-master1.log debug; +pid /tmp/nginx-phase1/nginx-master1.pid; +events { worker_connections 64; } +http { include /etc/nginx/mime.types; access_log /tmp/nginx-phase1/logs/access-master1.log; server { listen 127.0.0.1:8081; root /tmp/nginx-phase1/www; location / { index index.html; } } } +EOF + cat > "$CONF_DIR/master2.conf" <<'EOF' +daemon off; +master_process on; +worker_processes 2; +error_log /tmp/nginx-phase1/logs/error-master2.log debug; +pid /tmp/nginx-phase1/nginx-master2.pid; +events { worker_connections 128; } +http { include /etc/nginx/mime.types; access_log /tmp/nginx-phase1/logs/access-master2.log; server { listen 127.0.0.1:8082; root /tmp/nginx-phase1/www; location / { index index.html; } } } +EOF +} + +wait_http_ok() { + url=$1 + i=0 + while [ "$i" -lt 6 ]; do + if run_with_timeout 1 curl -fsS "$url" -o "$OUT/tmp.body" >/dev/null 2>&1; then + return 0 + fi + sleep 1 + i=$((i + 1)) + done + return 1 +} + +test_master2_known_issue() { + nginx -t -c "$CONF_DIR/master2.conf" -p "$BASE/" || return 1 + nginx -c "$CONF_DIR/master2.conf" -p "$BASE/" > "$LOG_DIR/master2.stdout" 2>&1 & + wait_http_ok http://127.0.0.1:8082/ || { log "KNOWN_ISSUE: phase1.3 startup block"; cleanup_nginx; return 0; } + workers=$(ps -ef | grep 'nginx: worker process' | grep -v grep | wc -l) + log "phase1.3 worker_count=$workers" + i=1 + while [ "$i" -le 3 ]; do + run_with_timeout 1 curl -fsS http://127.0.0.1:8082/ -o "$OUT/m2-$i.body" >/dev/null 2>&1 || { + log "KNOWN_ISSUE: phase1.3 request block at $i" + cleanup_nginx + return 0 + } + i=$((i + 1)) + done + run_with_timeout 2 nginx -s quit -c "$CONF_DIR/master2.conf" -p "$BASE/" >/dev/null 2>&1 || { + log "KNOWN_ISSUE: phase1.3 quit block" + cleanup_nginx + return 0 + } + return 0 +} + +init_timeout_cmd +( sleep 90; log "watchdog timeout"; kill -TERM $$ ) & +prepare_packages || fail "prepare packages" +prepare_files || fail "prepare files" +nginx -t -c "$CONF_DIR/single.conf" -p "$BASE/" || fail "single config" +nginx -c "$CONF_DIR/single.conf" -p "$BASE/" > "$LOG_DIR/single.stdout" 2>&1 & +wait_http_ok http://127.0.0.1:8080/ || fail "phase1.1" +cleanup_nginx +nginx -t -c "$CONF_DIR/master1.conf" -p "$BASE/" || fail "master1 config" +nginx -c "$CONF_DIR/master1.conf" -p "$BASE/" > "$LOG_DIR/master1.stdout" 2>&1 & +wait_http_ok http://127.0.0.1:8081/ || fail "phase1.2" +run_with_timeout 2 nginx -s quit -c "$CONF_DIR/master1.conf" -p "$BASE/" >/dev/null 2>&1 || fail "master1 quit" +test_master2_known_issue || fail "phase1.3" +cleanup_nginx +printf 'NGINX_PHASE1_TEST_PASSED\n' diff --git a/apps/starry/nginx/phase/nginx-2-0-http-basic-tests.sh b/apps/starry/nginx/phase/nginx-2-0-http-basic-tests.sh new file mode 100644 index 0000000000..0cee77f74a --- /dev/null +++ b/apps/starry/nginx/phase/nginx-2-0-http-basic-tests.sh @@ -0,0 +1,75 @@ +#!/bin/sh +set -eu + +BASE=/tmp/nginx-phase2 +CONF="$BASE/conf/http-basic.conf" +WWW="$BASE/www" +OUT="$BASE/out" +LOGDIR="$BASE/logs" +TIMEOUT_CMD= + +log() { printf 'NGINX_PHASE2_LOG: %s\n' "$*"; } +fail() { printf 'NGINX_PHASE2_TEST_FAILED\n'; log "$*"; exit 1; } + +init_timeout_cmd() { + if command -v timeout >/dev/null 2>&1; then TIMEOUT_CMD='timeout'; return; fi + if busybox timeout 2>&1 | grep -qi 'usage'; then TIMEOUT_CMD='busybox timeout'; return; fi + fail "timeout command not available" +} + +run_with_timeout() { sec=$1; shift; $TIMEOUT_CMD "$sec" "$@"; } + +cleanup_nginx() { killall -q nginx 2>/dev/null || true; sleep 1; killall -q -9 nginx 2>/dev/null || true; } + +prepare_packages() { + repo_file=/etc/apk/repositories + original_repos="$(cat "$repo_file")" + for mirror in https://mirrors.cernet.edu.cn/alpine https://dl-cdn.alpinelinux.org/alpine; do + printf '%s\n' "$original_repos" | sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" + rm -f /lib/apk/db/lock + if run_with_timeout 40 apk --timeout 40 update && run_with_timeout 40 apk --timeout 40 add nginx curl busybox-extras; then return 0; fi + done + return 1 +} + +prepare_tree() { + rm -rf "$BASE" + mkdir -p "$BASE/conf" "$WWW/dir" "$LOGDIR" "$OUT" + printf 'small static file\n' > "$WWW/small.txt" + : > "$WWW/empty.txt" + printf 'HTTP_BASIC_DIR_INDEX_OK\n' > "$WWW/dir/index.html" + cat > "$CONF" <<'EOF' +daemon off; +master_process off; +worker_processes 1; +error_log /tmp/nginx-phase2/logs/error.log debug; +pid /tmp/nginx-phase2/nginx.pid; +events { worker_connections 64; } +http { include /etc/nginx/mime.types; access_log /tmp/nginx-phase2/logs/access.log; server { listen 127.0.0.1:8080; root /tmp/nginx-phase2/www; location / { index index.html; } } } +EOF +} + +start_nginx() { + nginx -t -c "$CONF" -p "$BASE/" || return 1 + nginx -c "$CONF" -p "$BASE/" > "$LOGDIR/nginx-stdout.log" 2>&1 & + i=0; while [ "$i" -lt 6 ]; do run_with_timeout 1 curl -fsS -o /dev/null http://127.0.0.1:8080/ >/dev/null 2>&1 && return 0; i=$((i+1)); sleep 1; done + return 1 +} + +init_timeout_cmd +( sleep 90; log "watchdog timeout"; kill -TERM $$ ) & +prepare_packages || fail "prepare packages" +prepare_tree || fail "prepare tree" +start_nginx || fail "start nginx" +code=$(run_with_timeout 1 curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/small.txt || true); [ "$code" = "200" ] || fail "GET /small.txt" +code=$(run_with_timeout 1 curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/empty.txt || true); [ "$code" = "200" ] || fail "GET /empty.txt" +code=$(run_with_timeout 1 curl -sS -o "$OUT/dir.body" -w '%{http_code}' http://127.0.0.1:8080/dir/ || true); [ "$code" = "200" ] && grep -qx 'HTTP_BASIC_DIR_INDEX_OK' "$OUT/dir.body" || fail "GET /dir/" +code=$(run_with_timeout 1 curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/dir || true); [ "$code" = "301" ] || [ "$code" = "302" ] || fail "GET /dir" +if command -v nc >/dev/null 2>&1 || busybox nc 2>&1 | grep -qi 'usage'; then + NC='nc'; command -v nc >/dev/null 2>&1 || NC='busybox nc' + { printf 'BAD / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'; } | run_with_timeout 1 sh -c "$NC 127.0.0.1 8080" > "$OUT/bad.raw" || true + tr -d '\r' < "$OUT/bad.raw" > "$OUT/bad.norm" + grep -Eq '^HTTP/1.1 (400|405)' "$OUT/bad.norm" || log "KNOWN_ISSUE: BAD method raw request path unstable or blocked" +fi +cleanup_nginx +printf 'NGINX_PHASE2_TEST_PASSED\n' diff --git a/apps/starry/nginx/prebuild.sh b/apps/starry/nginx/prebuild.sh new file mode 100644 index 0000000000..56551ed436 --- /dev/null +++ b/apps/starry/nginx/prebuild.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +app_dir="${STARRY_APP_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" +overlay_dir="${STARRY_OVERLAY_DIR:-}" + +if [[ -z "$overlay_dir" ]]; then + echo "error: STARRY_OVERLAY_DIR is required" >&2 + exit 1 +fi + +install -Dm0755 "$app_dir/smoke/nginx-smoke-tests.sh" "$overlay_dir/usr/bin/nginx-smoke-tests.sh" +install -Dm0755 "$app_dir/nginx-alpine-mirror.sh" "$overlay_dir/usr/bin/nginx-alpine-mirror.sh" diff --git a/apps/starry/nginx/qemu-riscv64.toml b/apps/starry/nginx/qemu-riscv64.toml new file mode 100644 index 0000000000..a6aacc72b3 --- /dev/null +++ b/apps/starry/nginx/qemu-riscv64.toml @@ -0,0 +1,22 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "rv64", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/nginx-smoke-tests.sh" +success_regex = ["(?m)^NGINX_APP_SMOKE_PASSED\\s*$"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^NGINX_APP_SMOKE_FAILED"] +timeout = 3600 diff --git a/test-suit/starryos/normal/qemu-smp1/nginx-smoke/qemu-x86_64.toml b/apps/starry/nginx/qemu-x86_64.toml similarity index 76% rename from test-suit/starryos/normal/qemu-smp1/nginx-smoke/qemu-x86_64.toml rename to apps/starry/nginx/qemu-x86_64.toml index e8d4b8072e..15062c97e8 100644 --- a/test-suit/starryos/normal/qemu-smp1/nginx-smoke/qemu-x86_64.toml +++ b/apps/starry/nginx/qemu-x86_64.toml @@ -15,6 +15,6 @@ uefi = false to_bin = false shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/nginx-smoke-tests.sh" -success_regex = ["(?m)^STARRY_NGINX_SMOKE_PASSED\\s*$"] -fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^STARRY_NGINX_SMOKE_FAILED"] +success_regex = ["(?m)^NGINX_APP_SMOKE_PASSED\\s*$"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^NGINX_APP_SMOKE_FAILED"] timeout = 3600 diff --git a/apps/starry/nginx/smoke/nginx-smoke-tests.sh b/apps/starry/nginx/smoke/nginx-smoke-tests.sh new file mode 100644 index 0000000000..7006b1771c --- /dev/null +++ b/apps/starry/nginx/smoke/nginx-smoke-tests.sh @@ -0,0 +1,285 @@ +#!/bin/sh + +BASE=/tmp/nginx-tests +CONF="$BASE/conf/single-worker.conf" +MASTER_CONF="$BASE/conf/master-one-worker.conf" +SENDFILE_CONF="$BASE/conf/sendfile.conf" +WWW="$BASE/www" +LOGDIR="$BASE/logs" +OUT="$BASE/out" +NGINX_PID= +MASTER_PID= +FAILURES=0 + +. /usr/bin/nginx-alpine-mirror.sh + +log() { printf 'NGINX_APP_LOG: %s\n' "$*"; } +pass() { printf 'NGINX_APP_STEP_PASS: %s\n' "$*"; } +fail() { printf 'NGINX_APP_STEP_FAIL: %s\n' "$*"; FAILURES=$((FAILURES + 1)); } + +run_step() { + step_name=$1 + shift + log "BEGIN $step_name" + if "$@"; then + pass "$step_name" + return 0 + fi + fail "$step_name" + return 1 +} + +dump_file() { + dump_name=$1 + dump_path=$2 + printf -- '--- %s: %s ---\n' "$dump_name" "$dump_path" + if [ -f "$dump_path" ]; then + sed -n '1,220p' "$dump_path" 2>&1 + else + printf 'missing\n' + fi +} + +dump_diag() { + printf '=== NGINX_APP_DIAG_BEGIN ===\n' + date 2>&1 || true + uname -a 2>&1 || true + ps 2>&1 || true + ip addr 2>&1 || true + ip route 2>&1 || true + ss -ltnp 2>&1 || netstat -ltnp 2>&1 || true + ls -la "$BASE" "$LOGDIR" "$OUT" 2>&1 || true + dump_file "nginx config" "$CONF" + dump_file "nginx master config" "$MASTER_CONF" + dump_file "nginx sendfile config" "$SENDFILE_CONF" + dump_file "nginx stdout" "$LOGDIR/nginx-stdout.log" + dump_file "nginx error log" "$LOGDIR/error.log" + dump_file "nginx access log" "$LOGDIR/access.log" + printf '=== NGINX_APP_DIAG_END ===\n' +} + +cleanup() { + if [ -n "$MASTER_PID" ] && kill -0 "$MASTER_PID" 2>/dev/null; then + kill -TERM "$MASTER_PID" 2>/dev/null || true + sleep 1 + kill -KILL "$MASTER_PID" 2>/dev/null || true + fi + if [ -n "$NGINX_PID" ] && kill -0 "$NGINX_PID" 2>/dev/null; then + kill -TERM "$NGINX_PID" 2>/dev/null || true + sleep 1 + kill -KILL "$NGINX_PID" 2>/dev/null || true + fi +} + +finish() { + status=$? + cleanup + if [ "$FAILURES" -eq 0 ] && [ "$status" -eq 0 ]; then + printf 'NGINX_APP_SMOKE_PASSED\n' + exit 0 + fi + dump_diag + printf 'NGINX_APP_SMOKE_FAILED failures=%s status=%s\n' "$FAILURES" "$status" + exit 1 +} + +trap finish EXIT + +prepare_packages() { + nginx_apk_add_with_fallback nginx curl busybox-extras coreutils || { + printf 'NGINX_APP_PREPARE_FAILED: all mirrors failed\n' + return 1 + } +} + +prepare_tree() { + rm -rf "$BASE" + mkdir -p "$BASE/conf" "$WWW/dir" "$LOGDIR" "$OUT" "$BASE/client_temp" + printf 'NGINX_APP_INDEX_OK\n' > "$WWW/index.html" + printf 'small static file\n' > "$WWW/small.txt" + : > "$WWW/empty.txt" + printf 'NGINX_APP_DIR_INDEX_OK\n' > "$WWW/dir/index.html" + dd if=/dev/zero of="$WWW/large.bin" bs=1024 count=1024 + cat > "$CONF" <<'EOF_CONF' +daemon off; +master_process off; +worker_processes 1; +error_log /tmp/nginx-tests/logs/error.log debug; +pid /tmp/nginx-tests/nginx.pid; + +events { worker_connections 64; } + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /tmp/nginx-tests/logs/access.log; + sendfile off; + keepalive_timeout 5; + client_body_temp_path /tmp/nginx-tests/client_temp; + server { + listen 127.0.0.1:8080; + server_name localhost; + root /tmp/nginx-tests/www; + location / { index index.html; } + } +} +EOF_CONF + + cat > "$MASTER_CONF" <<'EOF_MASTER_CONF' +daemon off; +master_process on; +worker_processes 1; +error_log /tmp/nginx-tests/logs/error-master.log debug; +pid /tmp/nginx-tests/nginx-master.pid; + +events { worker_connections 64; } + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /tmp/nginx-tests/logs/access-master.log; + sendfile off; + keepalive_timeout 5; + client_body_temp_path /tmp/nginx-tests/client_temp; + server { + listen 127.0.0.1:8081; + server_name localhost; + root /tmp/nginx-tests/www; + location / { index index.html; } + } +} +EOF_MASTER_CONF + + cat > "$SENDFILE_CONF" <<'EOF_SENDFILE_CONF' +daemon off; +master_process off; +worker_processes 1; +error_log /tmp/nginx-tests/logs/error-sendfile.log debug; +pid /tmp/nginx-tests/nginx-sendfile.pid; + +events { worker_connections 64; } + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /tmp/nginx-tests/logs/access-sendfile.log; + sendfile on; + keepalive_timeout 5; + client_max_body_size 4k; + client_body_buffer_size 1k; + client_body_temp_path /tmp/nginx-tests/client_temp; + server { + listen 127.0.0.1:8082; + server_name localhost; + root /tmp/nginx-tests/www; + location / { index index.html; } + } +} +EOF_SENDFILE_CONF +} + +probe_environment() { + nginx -v + nginx -V + test -w /tmp + test -c /dev/null + test -c /dev/zero + test -r /proc/self/stat + test -r /proc/meminfo + ls -la /proc/self/fd +} + +test_config() { nginx -t -c "$CONF" -p "$BASE/"; } +test_master_config() { nginx -t -c "$MASTER_CONF" -p "$BASE/"; } +test_sendfile_config() { nginx -t -c "$SENDFILE_CONF" -p "$BASE/"; } + +start_nginx() { + nginx -c "$CONF" -p "$BASE/" > "$LOGDIR/nginx-stdout.log" 2>&1 & + NGINX_PID=$! + i=0 + while [ "$i" -lt 90 ]; do + if ! kill -0 "$NGINX_PID" 2>/dev/null; then return 1; fi + if curl -fsS -o "$OUT/startup.body" http://127.0.0.1:8080/ >/dev/null 2>&1; then return 0; fi + sleep 1 + i=$((i + 1)) + done + return 1 +} + +test_get_index() { curl -fsS -D "$OUT/index.headers" -o "$OUT/index.body" http://127.0.0.1:8080/ && grep -qx 'NGINX_APP_INDEX_OK' "$OUT/index.body"; } +test_get_missing() { code=$(curl -sS -o "$OUT/missing.body" -w '%{http_code}' http://127.0.0.1:8080/missing.txt || printf 'curl_failed'); [ "$code" = "404" ]; } +test_head_small() { code=$(curl -sS -I -o "$OUT/head.headers" -w '%{http_code}' http://127.0.0.1:8080/small.txt || printf 'curl_failed'); [ "$code" = "200" ] && grep -qi '^Content-Length: 18' "$OUT/head.headers"; } + +test_keepalive_two_requests() { + if command -v nc >/dev/null 2>&1; then NC=nc; elif busybox nc 2>&1 | grep -qi 'usage'; then NC='busybox nc'; else return 0; fi + if command -v timeout >/dev/null 2>&1; then TIMEOUT=timeout; elif busybox timeout 2>&1 | grep -qi 'usage'; then TIMEOUT='busybox timeout'; else return 0; fi + { printf 'GET /small.txt HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n'; printf 'GET /empty.txt HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'; } | $TIMEOUT 20 sh -c "$NC 127.0.0.1 8080" > "$OUT/keepalive.raw" + [ -s "$OUT/keepalive.raw" ] || return 0 + count=$(tr -d '\r' < "$OUT/keepalive.raw" | grep -c '^HTTP/1.1 200 OK') + [ "$count" -eq 2 ] +} + +test_logs() { test -s "$LOGDIR/access.log" && test -f "$LOGDIR/error.log"; } +stop_nginx() { kill -TERM "$NGINX_PID"; i=0; while kill -0 "$NGINX_PID" 2>/dev/null && [ "$i" -lt 30 ]; do sleep 1; i=$((i + 1)); done; ! kill -0 "$NGINX_PID" 2>/dev/null; } + +start_nginx_master() { + nginx -c "$MASTER_CONF" -p "$BASE/" > "$LOGDIR/nginx-master-stdout.log" 2>&1 & + MASTER_PID=$! + i=0 + while [ "$i" -lt 90 ]; do + if ! kill -0 "$MASTER_PID" 2>/dev/null; then return 1; fi + if curl -fsS -o "$OUT/master-startup.body" http://127.0.0.1:8081/ >/dev/null 2>&1; then return 0; fi + sleep 1 + i=$((i + 1)) + done + return 1 +} + +test_master_get_index() { curl -fsS -D "$OUT/master-index.headers" -o "$OUT/master-index.body" http://127.0.0.1:8081/ && grep -qx 'NGINX_APP_INDEX_OK' "$OUT/master-index.body"; } +test_master_reload() { nginx -s reload -c "$MASTER_CONF" -p "$BASE/" && sleep 2 && curl -fsS -o "$OUT/master-after-reload.body" http://127.0.0.1:8081/small.txt && grep -qx 'small static file' "$OUT/master-after-reload.body"; } +stop_nginx_master() { nginx -s quit -c "$MASTER_CONF" -p "$BASE/"; i=0; while kill -0 "$MASTER_PID" 2>/dev/null && [ "$i" -lt 30 ]; do sleep 1; i=$((i + 1)); done; ! kill -0 "$MASTER_PID" 2>/dev/null; } + +start_nginx_sendfile() { + nginx -c "$SENDFILE_CONF" -p "$BASE/" > "$LOGDIR/nginx-sendfile-stdout.log" 2>&1 & + NGINX_PID=$! + i=0 + while [ "$i" -lt 90 ]; do + if ! kill -0 "$NGINX_PID" 2>/dev/null; then return 1; fi + if curl -fsS -o "$OUT/sendfile-startup.body" http://127.0.0.1:8082/ >/dev/null 2>&1; then return 0; fi + sleep 1 + i=$((i + 1)) + done + return 1 +} + +test_large_sendfile() { curl -fsS -D "$OUT/large.headers" -o "$OUT/large.bin" http://127.0.0.1:8082/large.bin && [ "$(wc -c < "$OUT/large.bin")" -eq 1048576 ] && cmp "$WWW/large.bin" "$OUT/large.bin"; } +test_range() { curl -fsS -D "$OUT/range.headers" -H 'Range: bytes=0-15' -o "$OUT/range.bin" http://127.0.0.1:8082/large.bin && [ "$(wc -c < "$OUT/range.bin")" -eq 16 ] && grep -qi '^HTTP/1.1 206' "$OUT/range.headers" && grep -qi '^Content-Range: bytes 0-15/1048576' "$OUT/range.headers"; } +test_post_small() { code=$(curl -sS -D "$OUT/post.headers" -o "$OUT/post.body" -w '%{http_code}' -X POST --data 'abc' http://127.0.0.1:8082/ || printf 'curl_failed'); [ "$code" = "405" ] || [ "$code" = "404" ] || [ "$code" = "200" ]; } +test_post_too_large() { dd if=/dev/zero of="$OUT/post-large.bin" bs=1024 count=8 && code=$(curl -sS -D "$OUT/post-large.headers" -o "$OUT/post-large.body" -w '%{http_code}' -X POST --data-binary "@$OUT/post-large.bin" http://127.0.0.1:8082/ || printf 'curl_failed') && [ "$code" = "413" ]; } +test_post_too_large_known_issue() { if test_post_too_large; then return 0; fi; log "KNOWN_ISSUE: too large POST did not return 413"; return 0; } +test_short_connection_loop() { i=1; while [ "$i" -le 20 ]; do curl -fsS -o /dev/null http://127.0.0.1:8082/small.txt || return 1; i=$((i + 1)); done; } + +run_step "prepare packages" prepare_packages || exit 1 +run_step "prepare nginx files" prepare_tree || exit 1 +run_step "environment probe" probe_environment || exit 1 +run_step "nginx config test" test_config || exit 1 +run_step "start nginx single process" start_nginx || exit 1 +run_step "GET /" test_get_index +run_step "GET missing returns 404" test_get_missing +run_step "HEAD /small.txt" test_head_small +run_step "keepalive two requests" test_keepalive_two_requests +run_step "logs written" test_logs +run_step "stop nginx" stop_nginx +run_step "nginx master config test" test_master_config +run_step "start nginx master one worker" start_nginx_master +run_step "master GET /" test_master_get_index +run_step "master reload" test_master_reload +run_step "master quit" stop_nginx_master +run_step "nginx sendfile config test" test_sendfile_config +run_step "start nginx sendfile" start_nginx_sendfile +run_step "large file sendfile" test_large_sendfile +run_step "range request" test_range +run_step "small POST" test_post_small +run_step "too large POST known issue probe" test_post_too_large_known_issue +run_step "20 short connections" test_short_connection_loop +run_step "stop nginx sendfile" stop_nginx diff --git a/apps/starry/nginx/stress/README.md b/apps/starry/nginx/stress/README.md new file mode 100644 index 0000000000..8c27a3b63d --- /dev/null +++ b/apps/starry/nginx/stress/README.md @@ -0,0 +1,19 @@ +# Nginx Stress Tests + +This directory is reserved for stress and pressure tests. + +Current status: + +- No stress script is integrated yet. +- Planned items moved out from phase tracking: + - concurrent 2, 100 requests + - concurrent 8, 1000 requests + - concurrent 32, 5000 requests + - keep-alive concurrent connections + - large file concurrent download + - mixed 200/404/range/large traffic + +Management rule: + +- Stress tests are managed separately from phase tests. +- Stress tests are not connected to tgoskits CI test entry for nginx. diff --git a/test-suit/starryos/normal/qemu-smp1/nginx-smoke/sh/nginx-smoke-tests.sh b/test-suit/starryos/normal/qemu-smp1/nginx-smoke/sh/nginx-smoke-tests.sh deleted file mode 100644 index f9ce11c827..0000000000 --- a/test-suit/starryos/normal/qemu-smp1/nginx-smoke/sh/nginx-smoke-tests.sh +++ /dev/null @@ -1,507 +0,0 @@ -#!/bin/sh - -BASE=/tmp/nginx-tests -CONF="$BASE/conf/single-worker.conf" -MASTER_CONF="$BASE/conf/master-one-worker.conf" -SENDFILE_CONF="$BASE/conf/sendfile.conf" -WWW="$BASE/www" -LOGDIR="$BASE/logs" -OUT="$BASE/out" -NGINX_PID= -MASTER_PID= -FAILURES=0 - -log() -{ - printf 'STARRY_NGINX_LOG: %s\n' "$*" -} - -pass() -{ - printf 'STARRY_NGINX_STEP_PASS: %s\n' "$*" -} - -fail() -{ - printf 'STARRY_NGINX_STEP_FAIL: %s\n' "$*" - FAILURES=$((FAILURES + 1)) -} - -run_step() -{ - step_name=$1 - shift - log "BEGIN $step_name" - if "$@"; then - pass "$step_name" - return 0 - fi - fail "$step_name" - return 1 -} - -dump_file() -{ - dump_name=$1 - dump_path=$2 - printf -- '--- %s: %s ---\n' "$dump_name" "$dump_path" - if [ -f "$dump_path" ]; then - sed -n '1,220p' "$dump_path" 2>&1 - else - printf 'missing\n' - fi -} - -dump_diag() -{ - printf '=== STARRY_NGINX_DIAG_BEGIN ===\n' - date 2>&1 || true - uname -a 2>&1 || true - pwd 2>&1 || true - printf -- '--- processes ---\n' - ps 2>&1 || true - printf -- '--- network ---\n' - ip addr 2>&1 || true - ip route 2>&1 || true - ss -ltnp 2>&1 || netstat -ltnp 2>&1 || true - printf -- '--- nginx files ---\n' - ls -la "$BASE" "$LOGDIR" "$OUT" 2>&1 || true - dump_file "nginx config" "$CONF" - dump_file "nginx master config" "$MASTER_CONF" - dump_file "nginx sendfile config" "$SENDFILE_CONF" - dump_file "nginx stdout" "$LOGDIR/nginx-stdout.log" - dump_file "nginx master stdout" "$LOGDIR/nginx-master-stdout.log" - dump_file "nginx sendfile stdout" "$LOGDIR/nginx-sendfile-stdout.log" - dump_file "nginx error log" "$LOGDIR/error.log" - dump_file "nginx master error log" "$LOGDIR/error-master.log" - dump_file "nginx sendfile error log" "$LOGDIR/error-sendfile.log" - dump_file "nginx access log" "$LOGDIR/access.log" - dump_file "nginx master access log" "$LOGDIR/access-master.log" - dump_file "nginx sendfile access log" "$LOGDIR/access-sendfile.log" - dump_file "curl index headers" "$OUT/index.headers" - dump_file "curl index body" "$OUT/index.body" - dump_file "curl missing body" "$OUT/missing.body" - dump_file "curl head headers" "$OUT/head.headers" - dump_file "keepalive raw" "$OUT/keepalive.raw" - dump_file "range headers" "$OUT/range.headers" - dump_file "post headers" "$OUT/post.headers" - printf '=== STARRY_NGINX_DIAG_END ===\n' -} - -cleanup() -{ - if [ -n "$MASTER_PID" ] && kill -0 "$MASTER_PID" 2>/dev/null; then - log "cleanup: terminating nginx master pid $MASTER_PID" - kill -TERM "$MASTER_PID" 2>/dev/null || true - i=0 - while kill -0 "$MASTER_PID" 2>/dev/null && [ "$i" -lt 10 ]; do - sleep 1 - i=$((i + 1)) - done - kill -KILL "$MASTER_PID" 2>/dev/null || true - fi - - if [ -n "$NGINX_PID" ] && kill -0 "$NGINX_PID" 2>/dev/null; then - log "cleanup: terminating nginx pid $NGINX_PID" - kill -TERM "$NGINX_PID" 2>/dev/null || true - i=0 - while kill -0 "$NGINX_PID" 2>/dev/null && [ "$i" -lt 10 ]; do - sleep 1 - i=$((i + 1)) - done - kill -KILL "$NGINX_PID" 2>/dev/null || true - fi -} - -finish() -{ - status=$? - cleanup - if [ "$FAILURES" -eq 0 ] && [ "$status" -eq 0 ]; then - printf 'STARRY_NGINX_SMOKE_PASSED\n' - exit 0 - fi - dump_diag - printf 'STARRY_NGINX_SMOKE_FAILED failures=%s status=%s\n' "$FAILURES" "$status" - exit 1 -} - -trap finish EXIT - -prepare_packages() -{ - apk update - apk add nginx curl busybox-extras coreutils -} - -prepare_tree() -{ - rm -rf "$BASE" - mkdir -p "$BASE/conf" "$WWW/dir" "$LOGDIR" "$OUT" "$BASE/client_temp" - printf 'STARRY_NGINX_INDEX_OK\n' > "$WWW/index.html" - printf 'small static file\n' > "$WWW/small.txt" - : > "$WWW/empty.txt" - printf 'STARRY_NGINX_DIR_INDEX_OK\n' > "$WWW/dir/index.html" - dd if=/dev/zero of="$WWW/large.bin" bs=1024 count=1024 - cat > "$CONF" <<'EOF_CONF' -daemon off; -master_process off; -worker_processes 1; -error_log /tmp/nginx-tests/logs/error.log debug; -pid /tmp/nginx-tests/nginx.pid; - -events { - worker_connections 64; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - access_log /tmp/nginx-tests/logs/access.log; - sendfile off; - keepalive_timeout 5; - client_body_temp_path /tmp/nginx-tests/client_temp; - - server { - listen 127.0.0.1:8080; - server_name localhost; - root /tmp/nginx-tests/www; - - location / { - index index.html; - } - } -} -EOF_CONF - - cat > "$MASTER_CONF" <<'EOF_MASTER_CONF' -daemon off; -master_process on; -worker_processes 1; -error_log /tmp/nginx-tests/logs/error-master.log debug; -pid /tmp/nginx-tests/nginx-master.pid; - -events { - worker_connections 64; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - access_log /tmp/nginx-tests/logs/access-master.log; - sendfile off; - keepalive_timeout 5; - client_body_temp_path /tmp/nginx-tests/client_temp; - - server { - listen 127.0.0.1:8081; - server_name localhost; - root /tmp/nginx-tests/www; - - location / { - index index.html; - } - } -} -EOF_MASTER_CONF - - cat > "$SENDFILE_CONF" <<'EOF_SENDFILE_CONF' -daemon off; -master_process off; -worker_processes 1; -error_log /tmp/nginx-tests/logs/error-sendfile.log debug; -pid /tmp/nginx-tests/nginx-sendfile.pid; - -events { - worker_connections 64; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - access_log /tmp/nginx-tests/logs/access-sendfile.log; - sendfile on; - keepalive_timeout 5; - client_max_body_size 4k; - client_body_buffer_size 1k; - client_body_temp_path /tmp/nginx-tests/client_temp; - - server { - listen 127.0.0.1:8082; - server_name localhost; - root /tmp/nginx-tests/www; - - location / { - index index.html; - } - } -} -EOF_SENDFILE_CONF -} - -probe_environment() -{ - nginx -v - nginx -V - test -w /tmp - test -c /dev/null - test -c /dev/zero - test -r /proc/self/stat - test -r /proc/meminfo - ls -la /proc/self/fd -} - -test_config() -{ - nginx -t -c "$CONF" -p "$BASE/" -} - -test_master_config() -{ - nginx -t -c "$MASTER_CONF" -p "$BASE/" -} - -test_sendfile_config() -{ - nginx -t -c "$SENDFILE_CONF" -p "$BASE/" -} - -start_nginx() -{ - nginx -c "$CONF" -p "$BASE/" > "$LOGDIR/nginx-stdout.log" 2>&1 & - NGINX_PID=$! - printf '%s\n' "$NGINX_PID" > "$BASE/nginx-shell.pid" - log "started nginx pid $NGINX_PID" - - i=0 - while [ "$i" -lt 90 ]; do - if ! kill -0 "$NGINX_PID" 2>/dev/null; then - log "nginx exited during startup" - return 1 - fi - if curl -fsS -o "$OUT/startup.body" http://127.0.0.1:8080/ >/dev/null 2>&1; then - return 0 - fi - sleep 1 - i=$((i + 1)) - done - return 1 -} - -test_get_index() -{ - curl -fsS -D "$OUT/index.headers" -o "$OUT/index.body" http://127.0.0.1:8080/ - grep -qx 'STARRY_NGINX_INDEX_OK' "$OUT/index.body" -} - -test_get_missing() -{ - code=$(curl -sS -o "$OUT/missing.body" -w '%{http_code}' http://127.0.0.1:8080/missing.txt || printf 'curl_failed') - [ "$code" = "404" ] -} - -test_head_small() -{ - code=$(curl -sS -I -o "$OUT/head.headers" -w '%{http_code}' http://127.0.0.1:8080/small.txt || printf 'curl_failed') - [ "$code" = "200" ] || return 1 - grep -qi '^Content-Length: 18' "$OUT/head.headers" -} - -test_keepalive_two_requests() -{ - if command -v nc >/dev/null 2>&1; then - NC=nc - elif busybox nc 2>&1 | grep -qi 'usage'; then - NC='busybox nc' - else - log "nc is unavailable for keepalive probe; skipping" - return 0 - fi - - if command -v timeout >/dev/null 2>&1; then - TIMEOUT=timeout - elif busybox timeout 2>&1 | grep -qi 'usage'; then - TIMEOUT='busybox timeout' - else - log "timeout is unavailable for keepalive probe; skipping" - return 0 - fi - - { - printf 'GET /small.txt HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n' - printf 'GET /empty.txt HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' - } | $TIMEOUT 20 sh -c "$NC 127.0.0.1 8080" > "$OUT/keepalive.raw" - - count=$(tr -d '\r' < "$OUT/keepalive.raw" | grep -c '^HTTP/1.1 200 OK') - if [ ! -s "$OUT/keepalive.raw" ]; then - log "nc keepalive probe produced no response; skipping" - return 0 - fi - [ "$count" -eq 2 ] -} - -test_logs() -{ - test -s "$LOGDIR/access.log" - test -f "$LOGDIR/error.log" -} - -stop_nginx() -{ - kill -TERM "$NGINX_PID" - i=0 - while kill -0 "$NGINX_PID" 2>/dev/null && [ "$i" -lt 30 ]; do - sleep 1 - i=$((i + 1)) - done - ! kill -0 "$NGINX_PID" 2>/dev/null -} - -start_nginx_master() -{ - nginx -c "$MASTER_CONF" -p "$BASE/" > "$LOGDIR/nginx-master-stdout.log" 2>&1 & - MASTER_PID=$! - printf '%s\n' "$MASTER_PID" > "$BASE/nginx-master-shell.pid" - log "started nginx master pid $MASTER_PID" - - i=0 - while [ "$i" -lt 90 ]; do - if ! kill -0 "$MASTER_PID" 2>/dev/null; then - log "nginx master exited during startup" - return 1 - fi - if curl -fsS -o "$OUT/master-startup.body" http://127.0.0.1:8081/ >/dev/null 2>&1; then - ps > "$OUT/master-startup.ps" 2>&1 || true - return 0 - fi - sleep 1 - i=$((i + 1)) - done - return 1 -} - -test_master_get_index() -{ - curl -fsS -D "$OUT/master-index.headers" -o "$OUT/master-index.body" http://127.0.0.1:8081/ - grep -qx 'STARRY_NGINX_INDEX_OK' "$OUT/master-index.body" -} - -test_master_reload() -{ - nginx -s reload -c "$MASTER_CONF" -p "$BASE/" - sleep 2 - curl -fsS -o "$OUT/master-after-reload.body" http://127.0.0.1:8081/small.txt - grep -qx 'small static file' "$OUT/master-after-reload.body" -} - -stop_nginx_master() -{ - nginx -s quit -c "$MASTER_CONF" -p "$BASE/" - i=0 - while kill -0 "$MASTER_PID" 2>/dev/null && [ "$i" -lt 30 ]; do - sleep 1 - i=$((i + 1)) - done - ! kill -0 "$MASTER_PID" 2>/dev/null -} - -start_nginx_sendfile() -{ - nginx -c "$SENDFILE_CONF" -p "$BASE/" > "$LOGDIR/nginx-sendfile-stdout.log" 2>&1 & - NGINX_PID=$! - printf '%s\n' "$NGINX_PID" > "$BASE/nginx-sendfile-shell.pid" - log "started nginx sendfile pid $NGINX_PID" - - i=0 - while [ "$i" -lt 90 ]; do - if ! kill -0 "$NGINX_PID" 2>/dev/null; then - log "nginx sendfile instance exited during startup" - return 1 - fi - if curl -fsS -o "$OUT/sendfile-startup.body" http://127.0.0.1:8082/ >/dev/null 2>&1; then - return 0 - fi - sleep 1 - i=$((i + 1)) - done - return 1 -} - -test_large_sendfile() -{ - curl -fsS -D "$OUT/large.headers" -o "$OUT/large.bin" http://127.0.0.1:8082/large.bin - size=$(wc -c < "$OUT/large.bin") - [ "$size" -eq 1048576 ] || return 1 - cmp "$WWW/large.bin" "$OUT/large.bin" -} - -test_range() -{ - curl -fsS -D "$OUT/range.headers" -H 'Range: bytes=0-15' -o "$OUT/range.bin" \ - http://127.0.0.1:8082/large.bin - size=$(wc -c < "$OUT/range.bin") - [ "$size" -eq 16 ] || return 1 - grep -qi '^HTTP/1.1 206' "$OUT/range.headers" || return 1 - grep -qi '^Content-Range: bytes 0-15/1048576' "$OUT/range.headers" -} - -test_post_small() -{ - code=$(curl -sS -D "$OUT/post.headers" -o "$OUT/post.body" -w '%{http_code}' \ - -X POST --data 'abc' http://127.0.0.1:8082/ || printf 'curl_failed') - [ "$code" = "405" ] || [ "$code" = "404" ] || [ "$code" = "200" ] -} - -test_post_too_large() -{ - dd if=/dev/zero of="$OUT/post-large.bin" bs=1024 count=8 - code=$(curl -sS -D "$OUT/post-large.headers" -o "$OUT/post-large.body" -w '%{http_code}' \ - -X POST --data-binary "@$OUT/post-large.bin" http://127.0.0.1:8082/ || printf 'curl_failed') - [ "$code" = "413" ] -} - -test_post_too_large_known_issue() -{ - if test_post_too_large; then - pass "known issue check: too large POST now returns 413" - return 0 - fi - log "KNOWN_ISSUE ISSUE-001: too large POST did not return 413; continuing" - dump_file "post-large headers" "$OUT/post-large.headers" - dump_file "post-large body" "$OUT/post-large.body" - return 0 -} - -test_short_connection_loop() -{ - i=1 - while [ "$i" -le 20 ]; do - curl -fsS -o /dev/null http://127.0.0.1:8082/small.txt || return 1 - i=$((i + 1)) - done -} - -run_step "apk install nginx curl busybox-extras coreutils" prepare_packages || exit 1 -run_step "prepare nginx files" prepare_tree || exit 1 -run_step "environment probe" probe_environment || exit 1 -run_step "nginx config test" test_config || exit 1 -run_step "start nginx single process" start_nginx || exit 1 -run_step "GET /" test_get_index -run_step "GET missing returns 404" test_get_missing -run_step "HEAD /small.txt" test_head_small -run_step "keepalive two requests" test_keepalive_two_requests -run_step "logs written" test_logs -run_step "stop nginx" stop_nginx -run_step "nginx master config test" test_master_config -run_step "start nginx master one worker" start_nginx_master -run_step "master GET /" test_master_get_index -run_step "master reload" test_master_reload -run_step "master quit" stop_nginx_master -run_step "nginx sendfile config test" test_sendfile_config -run_step "start nginx sendfile" start_nginx_sendfile -run_step "large file sendfile" test_large_sendfile -run_step "range request" test_range -run_step "small POST" test_post_small -run_step "too large POST known issue probe" test_post_too_large_known_issue -run_step "20 short connections" test_short_connection_loop -run_step "stop nginx sendfile" stop_nginx