Add ngx_http_ipng_stats_module: per-VIP, per-device traffic counters
Full implementation of the nginx dynamic module with: - SO_BINDTODEVICE-based per-interface traffic attribution - Per-worker lock-free counters flushed to shared memory - Prometheus text and JSON scrape endpoint at configurable location - UDP-only global logtail (ipng_stats_logtail) for fire-and-forget access log streaming - $ipng_source_tag nginx variable for use in log_format/map - Histogram buckets, EWMA rate gauges, zone meta-metrics - Debian packaging (libnginx-mod-http-ipng-stats) - Robot Framework end-to-end tests via containerlab - SPDX Apache-2.0 headers on all source files
This commit is contained in:
280
tests/01-module/01-e2e.robot
Normal file
280
tests/01-module/01-e2e.robot
Normal file
@@ -0,0 +1,280 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
*** Settings ***
|
||||
Documentation End-to-end tests for ngx_http_ipng_stats_module.
|
||||
... Deploys a 3-node containerlab topology and validates
|
||||
... attribution, counters, histograms, filters, variables,
|
||||
... and reload semantics.
|
||||
Library OperatingSystem
|
||||
Library String
|
||||
Suite Setup Deploy Lab
|
||||
Suite Teardown Cleanup Lab
|
||||
|
||||
*** Variables ***
|
||||
${lab-name} ipng-stats-test
|
||||
${lab-file} lab/ipng-stats.clab.yml
|
||||
${runtime} docker
|
||||
${CLAB_BIN} sudo containerlab
|
||||
${SERVER} clab-${lab-name}-server
|
||||
${CLIENT1} clab-${lab-name}-client1
|
||||
${CLIENT2} clab-${lab-name}-client2
|
||||
${SCRAPE_URL} http://172.20.40.2:9113/.well-known/ipng/statsz
|
||||
${SERVER_MGMT} http://172.20.40.2:8080
|
||||
|
||||
*** Test Cases ***
|
||||
|
||||
# --- Basic functionality ---
|
||||
|
||||
Module loads
|
||||
[Documentation] nginx -t passes with the module loaded.
|
||||
${output} = Docker Exec ${SERVER} nginx -t 2>&1
|
||||
Should Contain ${output} syntax is ok
|
||||
|
||||
Prometheus scrape
|
||||
[Documentation] Scrape returns HELP/TYPE preamble.
|
||||
${output} = Scrape Prometheus
|
||||
Should Contain ${output} nginx-ipng-stats-plugin
|
||||
Should Contain ${output} nginx_ipng_requests_total
|
||||
|
||||
JSON scrape
|
||||
[Documentation] Accept: application/json returns valid JSON with schema.
|
||||
${rc} ${output} = Run And Return Rc And Output
|
||||
... curl -sf -H 'Accept: application/json' ${SCRAPE_URL} | python3 -m json.tool
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
Should Contain ${output} "schema": 1
|
||||
|
||||
# --- Per-device attribution ---
|
||||
|
||||
Attribute cl1 via eth1
|
||||
[Documentation] Traffic on server:eth1 carries source_tag=cl1, vip=10.0.1.1.
|
||||
Send Fast Requests ${CLIENT1} 10.0.1.1 5
|
||||
Wait For Flush
|
||||
${output} = Scrape Prometheus
|
||||
Should Contain ${output} source_tag="cl1"
|
||||
Should Contain ${output} vip="10.0.1.1"
|
||||
|
||||
Attribute cl2 via eth2
|
||||
[Documentation] Traffic on server:eth2 carries source_tag=cl2, vip=10.0.2.1.
|
||||
Send Fast Requests ${CLIENT2} 10.0.2.1 5
|
||||
Wait For Flush
|
||||
${output} = Scrape Prometheus
|
||||
Should Contain ${output} source_tag="cl2"
|
||||
Should Contain ${output} vip="10.0.2.1"
|
||||
|
||||
Direct traffic tagged
|
||||
[Documentation] Mgmt-interface traffic carries source_tag=direct.
|
||||
${rc} ${output} = Run And Return Rc And Output
|
||||
... curl -sf ${SERVER_MGMT}/
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
Wait For Flush
|
||||
${output} = Scrape Prometheus
|
||||
Should Contain ${output} source_tag="direct"
|
||||
|
||||
# --- Status code tracking ---
|
||||
|
||||
Per-code counters
|
||||
[Documentation] 404 and 200 appear as distinct code= labels.
|
||||
Docker Exec Ignore Rc ${CLIENT1} curl -s http://10.0.1.1:8080/notfound
|
||||
Docker Exec Ignore Rc ${CLIENT1} curl -s http://10.0.1.1:8080/notfound
|
||||
Wait For Flush
|
||||
${output} = Scrape With Filter source_tag=cl1
|
||||
Should Contain ${output} code="404"
|
||||
Should Contain ${output} code="200"
|
||||
|
||||
# --- Duration histogram ---
|
||||
|
||||
Duration histogram
|
||||
[Documentation] proxy_pass to a 50 ms backend populates sum and buckets.
|
||||
Send Slow Requests ${CLIENT1} 10.0.1.1 3
|
||||
Wait For Flush
|
||||
${prom} = Scrape With Filter source_tag=cl1
|
||||
Should Match Regexp ${prom} request_duration_seconds_sum\\{[^}]*\\}\\s+\\d+\\.\\d*[1-9]
|
||||
|
||||
${rc} ${json} = Run And Return Rc And Output
|
||||
... curl -sf -H 'Accept: application/json' '${SCRAPE_URL}?source_tag=cl1' | python3 -m json.tool
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
Should Contain ${json} request_duration_ms
|
||||
Should Contain ${json} buckets
|
||||
|
||||
# --- Scrape filters ---
|
||||
|
||||
Filter by source_tag
|
||||
[Documentation] ?source_tag=cl1 returns cl1 only; cl2 only.
|
||||
${output} = Scrape With Filter source_tag=cl1
|
||||
Should Contain ${output} source_tag="cl1"
|
||||
Should Not Contain ${output} source_tag="cl2"
|
||||
|
||||
${output} = Scrape With Filter source_tag=cl2
|
||||
Should Contain ${output} source_tag="cl2"
|
||||
Should Not Contain ${output} source_tag="cl1"
|
||||
|
||||
Filter by VIP
|
||||
[Documentation] ?vip=10.0.1.1 excludes 10.0.2.1.
|
||||
${output} = Scrape With Filter vip=10.0.1.1
|
||||
Should Contain ${output} vip="10.0.1.1"
|
||||
Should Not Contain ${output} vip="10.0.2.1"
|
||||
|
||||
Filter combined
|
||||
[Documentation] source_tag + vip intersection.
|
||||
${output} = Scrape With Filter source_tag=cl1&vip=10.0.1.1
|
||||
Should Contain ${output} source_tag="cl1"
|
||||
Should Contain ${output} vip="10.0.1.1"
|
||||
Should Not Contain ${output} source_tag="cl2"
|
||||
|
||||
Filter unknown tag
|
||||
[Documentation] Unknown source_tag returns empty data set.
|
||||
${output} = Scrape With Filter source_tag=nonexistent
|
||||
Should Not Contain ${output} nginx_ipng_requests_total{
|
||||
|
||||
# --- nginx variable ---
|
||||
|
||||
Variable in access log
|
||||
[Documentation] $ipng_source_tag appears as cl1, cl2, direct in log.
|
||||
${output} = Docker Exec ${SERVER} cat /var/log/nginx/access.log
|
||||
Should Match Regexp ${output} src=cl1
|
||||
Should Match Regexp ${output} src=cl2
|
||||
Should Match Regexp ${output} src=direct
|
||||
|
||||
UDP logtail
|
||||
[Documentation] ipng_stats_logtail udp:// sends log lines to a local
|
||||
... nc listener; captured file has all sources and VIPs.
|
||||
${output} = Docker Exec ${SERVER} cat /var/log/nginx/logtail-udp.log
|
||||
Should Match Regexp ${output} cl1
|
||||
Should Match Regexp ${output} cl2
|
||||
Should Match Regexp ${output} direct
|
||||
Should Match Regexp ${output} 10\\.0\\.1\\.1
|
||||
Should Match Regexp ${output} 10\\.0\\.2\\.1
|
||||
# Tab-separated format
|
||||
Should Match Regexp ${output} \\t
|
||||
|
||||
VIP in access log
|
||||
[Documentation] $server_addr resolves to real IPs, not 0.0.0.0.
|
||||
${output} = Docker Exec ${SERVER} cat /var/log/nginx/access.log
|
||||
Should Contain ${output} vip=10.0.1.1
|
||||
Should Contain ${output} vip=10.0.2.1
|
||||
Should Not Contain ${output} vip=0.0.0.0
|
||||
|
||||
# --- Reload resilience ---
|
||||
|
||||
Counters survive reload
|
||||
[Documentation] Shared-memory zone persists across nginx -s reload.
|
||||
${before} = Get Request Count cl1
|
||||
Docker Exec ${SERVER} nginx -s reload
|
||||
Sleep 2s Wait for new workers
|
||||
${after} = Get Request Count cl1
|
||||
Should Be True ${after} >= ${before}
|
||||
... Counters dropped after reload: before=${before} after=${after}
|
||||
|
||||
Traffic after reload
|
||||
[Documentation] New requests are counted after reload.
|
||||
Send Fast Requests ${CLIENT1} 10.0.1.1 3
|
||||
Wait For Flush
|
||||
${output} = Scrape With Filter source_tag=cl1
|
||||
Should Contain ${output} source_tag="cl1"
|
||||
|
||||
# --- Counter correctness ---
|
||||
|
||||
Request count accuracy
|
||||
[Documentation] 10 requests per client yields exactly 10 delta.
|
||||
${before_cl1} = Get Request Count cl1
|
||||
${before_cl2} = Get Request Count cl2
|
||||
Send Fast Requests ${CLIENT1} 10.0.1.1 10
|
||||
Send Fast Requests ${CLIENT2} 10.0.2.1 10
|
||||
Wait For Flush
|
||||
${after_cl1} = Get Request Count cl1
|
||||
${after_cl2} = Get Request Count cl2
|
||||
${delta_cl1} = Evaluate ${after_cl1} - ${before_cl1}
|
||||
${delta_cl2} = Evaluate ${after_cl2} - ${before_cl2}
|
||||
Should Be Equal As Integers ${delta_cl1} 10
|
||||
Should Be Equal As Integers ${delta_cl2} 10
|
||||
|
||||
*** Keywords ***
|
||||
|
||||
# --- Lab lifecycle ---
|
||||
|
||||
Deploy Lab
|
||||
Run ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file} --cleanup 2>&1 || true
|
||||
${rc} ${output} = Run And Return Rc And Output
|
||||
... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file}
|
||||
Log ${output}
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
Wait Until Keyword Succeeds 90s 3s Server Is Ready
|
||||
Wait Until Keyword Succeeds 60s 3s Client Can Reach Server ${CLIENT1} 10.0.1.1
|
||||
Wait Until Keyword Succeeds 60s 3s Client Can Reach Server ${CLIENT2} 10.0.2.1
|
||||
|
||||
Server Is Ready
|
||||
${rc} ${output} = Run And Return Rc And Output
|
||||
... curl -sf ${SCRAPE_URL}
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
|
||||
Client Can Reach Server
|
||||
[Arguments] ${client} ${server_ip}
|
||||
${rc} ${output} = Run And Return Rc And Output
|
||||
... docker exec ${client} curl -sf http://${server_ip}:8080/
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
|
||||
Cleanup Lab
|
||||
Run docker logs ${SERVER} > ${EXECDIR}/tests/out/server-docker.log 2>&1
|
||||
Run docker exec ${SERVER} cat /var/log/nginx/access.log > ${EXECDIR}/tests/out/server-access.log 2>&1
|
||||
Run docker exec ${SERVER} cat /var/log/nginx/error.log > ${EXECDIR}/tests/out/server-error.log 2>&1
|
||||
Run docker exec ${SERVER} cat /var/log/nginx/logtail-udp.log > ${EXECDIR}/tests/out/server-logtail-udp.log 2>&1
|
||||
Run docker exec ${SERVER} ip addr > ${EXECDIR}/tests/out/server-ip-addr.log 2>&1
|
||||
Run docker exec ${SERVER} ip route > ${EXECDIR}/tests/out/server-ip-route.log 2>&1
|
||||
Run ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file} --cleanup
|
||||
|
||||
# --- Traffic generation ---
|
||||
|
||||
Send Fast Requests
|
||||
[Arguments] ${client} ${server_ip} ${count}
|
||||
FOR ${i} IN RANGE ${count}
|
||||
Docker Exec ${client} curl -sf http://${server_ip}:8080/
|
||||
END
|
||||
|
||||
Send Slow Requests
|
||||
[Arguments] ${client} ${server_ip} ${count}
|
||||
FOR ${i} IN RANGE ${count}
|
||||
Docker Exec ${client} curl -sf http://${server_ip}:8080/slow
|
||||
END
|
||||
|
||||
Wait For Flush
|
||||
Sleep 2s
|
||||
|
||||
# --- Scraping ---
|
||||
|
||||
Scrape Prometheus
|
||||
${rc} ${output} = Run And Return Rc And Output
|
||||
... curl -sf ${SCRAPE_URL}
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
RETURN ${output}
|
||||
|
||||
Scrape With Filter
|
||||
[Arguments] ${filter}
|
||||
${rc} ${output} = Run And Return Rc And Output
|
||||
... curl -sf '${SCRAPE_URL}?${filter}'
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
RETURN ${output}
|
||||
|
||||
Get Request Count
|
||||
[Arguments] ${source}
|
||||
${output} = Scrape With Filter source_tag=${source}
|
||||
${matches} = Get Regexp Matches ${output}
|
||||
... nginx_ipng_requests_total\\{[^}]*\\}\\s+(\\d+) 1
|
||||
${total} = Set Variable 0
|
||||
FOR ${m} IN @{matches}
|
||||
${total} = Evaluate ${total} + ${m}
|
||||
END
|
||||
RETURN ${total}
|
||||
|
||||
# --- Container helpers ---
|
||||
|
||||
Docker Exec
|
||||
[Arguments] ${container} ${cmd}
|
||||
${rc} ${output} = Run And Return Rc And Output
|
||||
... docker exec ${container} ${cmd}
|
||||
Should Be Equal As Integers ${rc} 0
|
||||
RETURN ${output}
|
||||
|
||||
Docker Exec Ignore Rc
|
||||
[Arguments] ${container} ${cmd}
|
||||
${rc} ${output} = Run And Return Rc And Output
|
||||
... docker exec ${container} ${cmd}
|
||||
RETURN ${output}
|
||||
23
tests/01-module/lab/client/start.sh
Normal file
23
tests/01-module/lab/client/start.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Client container entrypoint: installs curl, waits for containerlab
|
||||
# to attach the data-plane veth, configures the IP, removes the mgmt
|
||||
# default route so traffic to the server goes through eth1 (data-plane),
|
||||
# and stays alive for docker-exec commands from the Robot test.
|
||||
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl iproute2 > /dev/null 2>&1
|
||||
|
||||
# Wait for containerlab to attach eth1.
|
||||
echo "Waiting for eth1 ..."
|
||||
while ! ip link show eth1 > /dev/null 2>&1; do
|
||||
sleep 0.2
|
||||
done
|
||||
ip link set eth1 up
|
||||
ip addr add ${MY_IP} dev eth1
|
||||
|
||||
# Remove the default route so packets to 10.0.x.0/24 go out eth1
|
||||
# (the connected route) instead of through the mgmt bridge.
|
||||
ip route del default 2>/dev/null || true
|
||||
|
||||
exec sleep infinity
|
||||
54
tests/01-module/lab/ipng-stats.clab.yml
Normal file
54
tests/01-module/lab/ipng-stats.clab.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Containerlab topology for nginx-ipng-stats-plugin end-to-end tests.
|
||||
#
|
||||
# Three nodes:
|
||||
# server — nginx with the module, a slow Python backend, two data-plane interfaces
|
||||
# client1 — sends traffic via eth1 (attributed to source_tag=cl1)
|
||||
# client2 — sends traffic via eth2 (attributed to source_tag=cl2)
|
||||
#
|
||||
# Links:
|
||||
# server:eth1 ←→ client1:eth1 (10.0.1.0/24)
|
||||
# server:eth2 ←→ client2:eth1 (10.0.2.0/24)
|
||||
|
||||
name: ipng-stats-test
|
||||
|
||||
mgmt:
|
||||
network: ipng-stats-test-net
|
||||
ipv4-subnet: 172.20.40.0/24
|
||||
|
||||
topology:
|
||||
nodes:
|
||||
server:
|
||||
kind: linux
|
||||
image: debian:trixie-slim
|
||||
mgmt-ipv4: 172.20.40.2
|
||||
binds:
|
||||
- ../../../build:/opt/build:ro
|
||||
- ./server/nginx.conf:/opt/config/nginx.conf:ro
|
||||
- ./server/slow-backend.py:/opt/config/slow-backend.py:ro
|
||||
- ./server/start.sh:/start.sh:ro
|
||||
cmd: bash /start.sh
|
||||
|
||||
client1:
|
||||
kind: linux
|
||||
image: debian:trixie-slim
|
||||
mgmt-ipv4: 172.20.40.11
|
||||
binds:
|
||||
- ./client/start.sh:/start.sh:ro
|
||||
cmd: bash /start.sh
|
||||
env:
|
||||
MY_IP: 10.0.1.2/24
|
||||
|
||||
client2:
|
||||
kind: linux
|
||||
image: debian:trixie-slim
|
||||
mgmt-ipv4: 172.20.40.12
|
||||
binds:
|
||||
- ./client/start.sh:/start.sh:ro
|
||||
cmd: bash /start.sh
|
||||
env:
|
||||
MY_IP: 10.0.2.2/24
|
||||
|
||||
links:
|
||||
- endpoints: ["server:eth1", "client1:eth1"]
|
||||
- endpoints: ["server:eth2", "client2:eth1"]
|
||||
58
tests/01-module/lab/server/nginx.conf
Normal file
58
tests/01-module/lab/server/nginx.conf
Normal file
@@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Test nginx configuration for the ipng_stats module.
|
||||
|
||||
load_module /usr/lib/nginx/modules/ngx_http_ipng_stats_module.so;
|
||||
|
||||
error_log stderr notice;
|
||||
|
||||
events {
|
||||
worker_connections 128;
|
||||
}
|
||||
|
||||
http {
|
||||
ipng_stats_zone ipng:1m;
|
||||
ipng_stats_flush_interval 500ms;
|
||||
ipng_stats_default_source direct;
|
||||
|
||||
log_format tagged '$remote_addr src=$ipng_source_tag vip=$server_addr '
|
||||
'"$request" $status $body_bytes_sent';
|
||||
access_log /var/log/nginx/access.log tagged;
|
||||
|
||||
# Global logtail — fires for ALL requests regardless of server block.
|
||||
log_format logtail '$host\t$remote_addr\t$ipng_source_tag\t$server_addr\t'
|
||||
'$request_method\t$request_uri\t$status\t$body_bytes_sent\t'
|
||||
'$request_time';
|
||||
ipng_stats_logtail logtail udp://127.0.0.1:9514 buffer=4k flush=500ms;
|
||||
|
||||
server {
|
||||
# Mgmt-only listener for direct traffic (tagged "direct").
|
||||
listen 172.20.40.2:8080;
|
||||
|
||||
# Per-interface listeners for attributed traffic.
|
||||
listen 10.0.1.1:8080 device=eth1 ipng_source_tag=cl1;
|
||||
listen 10.0.2.1:8080 device=eth2 ipng_source_tag=cl2;
|
||||
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
return 200 "ok $server_addr\n";
|
||||
}
|
||||
|
||||
location /notfound {
|
||||
return 404 "nope\n";
|
||||
}
|
||||
|
||||
location /slow {
|
||||
proxy_pass http://127.0.0.1:29080/;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 172.20.40.2:9113;
|
||||
|
||||
location = /.well-known/ipng/statsz {
|
||||
ipng_stats;
|
||||
allow all;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
tests/01-module/lab/server/slow-backend.py
Normal file
22
tests/01-module/lab/server/slow-backend.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Minimal HTTP server that sleeps 50 ms before responding.
|
||||
# Used by the test harness to produce measurable request durations.
|
||||
|
||||
import http.server
|
||||
import socketserver
|
||||
import time
|
||||
|
||||
class SlowHandler(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
time.sleep(0.05)
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/plain")
|
||||
self.end_headers()
|
||||
self.wfile.write(b"slow\n")
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
with socketserver.TCPServer(("127.0.0.1", 29080), SlowHandler) as srv:
|
||||
srv.serve_forever()
|
||||
42
tests/01-module/lab/server/start.sh
Normal file
42
tests/01-module/lab/server/start.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Server container entrypoint: installs nginx + module, waits for
|
||||
# containerlab to create data-plane interfaces, starts the slow
|
||||
# Python backend, and runs nginx in the foreground.
|
||||
|
||||
# Suppress automatic service start/restart during apt/dpkg.
|
||||
printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d
|
||||
chmod +x /usr/sbin/policy-rc.d
|
||||
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq nginx python3 procps iproute2 ncat > /dev/null 2>&1
|
||||
|
||||
# Install the module .deb built by `make pkg-deb`.
|
||||
dpkg -i /opt/build/libnginx-mod-http-ipng-stats_*.deb 2>/dev/null || true
|
||||
|
||||
# Re-enable module symlink in case postinst disabled it.
|
||||
ln -sf /etc/nginx/modules-available/50-mod-http-ipng-stats.conf \
|
||||
/etc/nginx/modules-enabled/50-mod-http-ipng-stats.conf
|
||||
|
||||
# Remove the policy block now that packages are installed.
|
||||
rm -f /usr/sbin/policy-rc.d
|
||||
|
||||
# Wait for containerlab to attach the data-plane veth pairs.
|
||||
for iface in eth1 eth2; do
|
||||
echo "Waiting for $iface ..."
|
||||
while ! ip link show "$iface" > /dev/null 2>&1; do
|
||||
sleep 0.2
|
||||
done
|
||||
ip link set "$iface" up
|
||||
done
|
||||
|
||||
ip addr add 10.0.1.1/24 dev eth1
|
||||
ip addr add 10.0.2.1/24 dev eth2
|
||||
|
||||
# Slow backend: 50 ms sleep per request.
|
||||
python3 /opt/config/slow-backend.py &
|
||||
|
||||
# UDP logtail listener — captures datagrams to a file for test validation.
|
||||
ncat -u -l -k 127.0.0.1 9514 --recv-only >> /var/log/nginx/logtail-udp.log &
|
||||
|
||||
exec nginx -g 'daemon off;' -c /opt/config/nginx.conf
|
||||
1
tests/requirements.txt
Normal file
1
tests/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
robotframework
|
||||
39
tests/rf-run.sh
Executable file
39
tests/rf-run.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Test runner for nginx-ipng-stats-plugin robot tests.
|
||||
# Usage: ./rf-run.sh <runtime> <test_path>
|
||||
# runtime: docker (default)
|
||||
# test_path: path to .robot file or directory (default: all tests)
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAB_BIN="${CLAB_BIN:-containerlab}"
|
||||
RUNTIME="${1:-docker}"
|
||||
TEST="${2:-${SCRIPT_DIR}}"
|
||||
|
||||
mkdir -p "${SCRIPT_DIR}/out"
|
||||
|
||||
# Create venv if needed
|
||||
if [ ! -d "${SCRIPT_DIR}/.venv" ]; then
|
||||
python3 -m venv "${SCRIPT_DIR}/.venv"
|
||||
"${SCRIPT_DIR}/.venv/bin/pip" install -q -r "${SCRIPT_DIR}/requirements.txt"
|
||||
fi
|
||||
|
||||
source "${SCRIPT_DIR}/.venv/bin/activate"
|
||||
|
||||
get_logname() {
|
||||
local name
|
||||
name="$(basename "$1" .robot)"
|
||||
if [ -d "$1" ]; then
|
||||
name="$(basename "$1")"
|
||||
fi
|
||||
echo "$name"
|
||||
}
|
||||
|
||||
robot --consolecolors on -r none \
|
||||
--variable CLAB_BIN:"${CLAB_BIN}" \
|
||||
--variable runtime:"${RUNTIME}" \
|
||||
-l "${SCRIPT_DIR}/out/$(get_logname "${TEST}")-${RUNTIME}-log" \
|
||||
--output "${SCRIPT_DIR}/out/$(get_logname "${TEST}")-${RUNTIME}-out.xml" \
|
||||
"${TEST}"
|
||||
Reference in New Issue
Block a user