mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 17:41:12 +00:00
7d53cef0cf
ci/woodpecker/push/02-build-push Pipeline failed
New replay_api component: FastAPI service wrapping the offline
gps-denied-replay pipeline. POST tlog+video (multipart) → either
sync 200 with result/map/report URLs, or async 202 + job id with
/jobs/{id} polling. Magic-byte validation, bearer auth, in-memory
JobRegistry with concurrency + queue caps (429 on overflow).
Helper accuracy_report.py promoted from tests/ to src/ because the
API needs the Markdown report writer at runtime; all AZ-699 imports
re-pointed. OpenAPI spec exported to docs.
18/18 unit tests pass (AC-1 sync, AC-2 async, AC-3 state machine,
AC-5 auth, AC-6 health, AC-8 concurrency, AC-9 magic-byte). Full
unit suite: 2251 pass, 86 skip, 1 pre-existing C12 cold-start flake
(unchanged). mypy --strict clean on the new surface.
Co-authored-by: Cursor <cursoragent@cursor.com>
180 lines
6.3 KiB
YAML
180 lines
6.3 KiB
YAML
# Tier-1 docker-compose entrypoint for the gps-denied-onboard blackbox e2e harness.
|
|
#
|
|
# Spec sources (single source of truth):
|
|
# _docs/02_document/tests/environment.md § Docker Environment
|
|
# _docs/02_tasks/todo/AZ-406_test_infrastructure.md
|
|
#
|
|
# Layout note: AZ-406 introduces this file; later test-task batches may add
|
|
# per-scenario override files alongside it (e.g. negative path injectors).
|
|
# This base file MUST stay self-contained — every override is purely additive.
|
|
#
|
|
# Build context (`build.context: ../..`) is the repo root, so the SUT image
|
|
# build sees `src/`, `cpp/`, `docker/Dockerfile`, and `pyproject.toml`.
|
|
|
|
services:
|
|
|
|
gps-denied-onboard:
|
|
build:
|
|
context: ../..
|
|
dockerfile: docker/companion-tier1.Dockerfile
|
|
args:
|
|
BUILD_VINS_MONO: "OFF"
|
|
image: gps-denied-onboard:e2e
|
|
networks: [e2e-net]
|
|
volumes:
|
|
- tile-cache-fixture:/var/azaion/tile-cache:ro
|
|
- fdr-output:/var/azaion/fdr
|
|
environment:
|
|
ONBOARD_FC_ADAPTER: ${FC_ADAPTER:-ardupilot}
|
|
ONBOARD_VIO_STRATEGY: ${VIO_STRATEGY:-okvis2}
|
|
MAVLINK_SIGNING_PASSKEY_FILE: /run/secrets/mavlink_passkey
|
|
secrets:
|
|
- mavlink_passkey
|
|
depends_on:
|
|
- mock-suite-sat-service
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "from gps_denied_onboard.healthcheck import check; check()"]
|
|
interval: 5s
|
|
retries: 12
|
|
|
|
ardupilot-plane-sitl:
|
|
image: ardupilot/ardupilot-sitl:plane-stable
|
|
networks: [e2e-net]
|
|
command: ["--vehicle=ArduPlane", "--gps-type=14"]
|
|
environment:
|
|
# GPS_TYPE=14 selects MAV (external positioning) per ArduPilot SITL params.
|
|
AP_PARAM_GPS_TYPE: "14"
|
|
|
|
inav-sitl:
|
|
image: inavflight/inav-sitl:9.0.0
|
|
networks: [e2e-net]
|
|
# iNav SITL exposes MSP on TCP 5760 (UART1) per docs/SITL/SITL.md
|
|
|
|
mock-suite-sat-service:
|
|
build: ../fixtures/mock-suite-sat
|
|
image: mock-suite-sat-service:e2e
|
|
networks: [e2e-net]
|
|
environment:
|
|
MOCK_SUITE_SAT_AUDIT_PATH: /audit
|
|
volumes:
|
|
- mock-audit:/audit
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8080/mock/health', timeout=2).status==200 else 1)"]
|
|
interval: 5s
|
|
retries: 12
|
|
|
|
# AZ-701 — operator-side replay HTTP API.
|
|
#
|
|
# Profile-gated so the default `docker compose up` flow (the
|
|
# blackbox e2e suite) is unaffected. To start the API alongside
|
|
# the suite, run:
|
|
# docker compose --profile replay-api up replay-api
|
|
# The container exposes /healthz on 8080 and refuses /replay
|
|
# uploads without a bearer token unless REPLAY_API_AUTH_REQUIRED
|
|
# is explicitly set to false (dev only — WARN logged).
|
|
replay-api:
|
|
profiles: ["replay-api"]
|
|
build:
|
|
context: ../..
|
|
dockerfile: docker/replay-api.Dockerfile
|
|
image: gps-denied-replay-api:e2e
|
|
networks: [e2e-net]
|
|
ports:
|
|
- "${REPLAY_API_HOST_PORT:-8080}:8080"
|
|
environment:
|
|
REPLAY_API_AUTH_REQUIRED: "${REPLAY_API_AUTH_REQUIRED:-true}"
|
|
REPLAY_API_BEARER_TOKEN: "${REPLAY_API_BEARER_TOKEN:-}"
|
|
REPLAY_API_MAX_CONCURRENT_JOBS: "${REPLAY_API_MAX_CONCURRENT_JOBS:-1}"
|
|
REPLAY_API_MAX_QUEUED_JOBS: "${REPLAY_API_MAX_QUEUED_JOBS:-8}"
|
|
REPLAY_API_STORAGE_ROOT: /var/azaion/replay_api
|
|
volumes:
|
|
- replay-api-storage:/var/azaion/replay_api
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"]
|
|
interval: 10s
|
|
retries: 5
|
|
|
|
mavproxy-listener:
|
|
image: ardupilot/mavproxy:latest
|
|
networks: [e2e-net]
|
|
command:
|
|
- "--master=udp:0.0.0.0:14551"
|
|
- "--logfile=/var/log/tlogs/${RUN_ID:-local}.tlog"
|
|
- "--out=udp:e2e-runner:14552"
|
|
volumes:
|
|
- tlog-output:/var/log/tlogs
|
|
|
|
e2e-runner:
|
|
build: ../runner
|
|
image: gps-denied-onboard-e2e-runner:latest
|
|
networks: [e2e-net]
|
|
environment:
|
|
RUN_ID: ${RUN_ID:-local}
|
|
FC_ADAPTER: ${FC_ADAPTER:-ardupilot}
|
|
VIO_STRATEGY: ${VIO_STRATEGY:-okvis2}
|
|
TIER: tier1-docker
|
|
MAVLINK_PASSKEY_PATH: /test-fixtures/secrets/mavlink-test-passkey.txt
|
|
MOCK_SUITE_SAT_URL: http://mock-suite-sat-service:8080
|
|
AP_SITL_HOST: ardupilot-plane-sitl
|
|
INAV_SITL_HOST: inav-sitl
|
|
MAVPROXY_LISTENER_HOST: mavproxy-listener
|
|
volumes:
|
|
- ../../_docs/00_problem/input_data:/test-data:ro
|
|
- ../../_docs/00_problem/input_data/expected_results:/expected:ro
|
|
- ../fixtures:/test-fixtures:ro
|
|
- ../tests:/test-suite:ro
|
|
- fdr-output:/fdr:ro
|
|
- tlog-output:/tlogs:ro
|
|
- e2e-results:/e2e-results
|
|
- mock-audit:/mock-audit:ro
|
|
command:
|
|
- "pytest"
|
|
- "/test-suite"
|
|
- "--csv=/e2e-results/run-${RUN_ID:-local}/report.csv"
|
|
- "--csv-columns=test_id,test_name,traces_to,fc_adapter,vio_strategy,tier,started_at_utc,execution_time_ms,result,error_message,evidence_paths"
|
|
- "--evidence-out=/e2e-results/run-${RUN_ID:-local}/evidence"
|
|
depends_on:
|
|
gps-denied-onboard:
|
|
condition: service_healthy
|
|
mock-suite-sat-service:
|
|
condition: service_healthy
|
|
ardupilot-plane-sitl:
|
|
condition: service_started
|
|
inav-sitl:
|
|
condition: service_started
|
|
mavproxy-listener:
|
|
condition: service_started
|
|
|
|
networks:
|
|
e2e-net:
|
|
driver: bridge
|
|
# CRITICAL: enforces RESTRICT-SAT-1 / NFT-SEC-02 / NFT-SEC-05 at the network layer.
|
|
# The SUT, mock, runner, and SITLs can talk to each other but none of them can
|
|
# reach the public internet (no DNS, no egress). The e2e-runner verifies this
|
|
# at runtime by attempting a TCP connect to 1.1.1.1:443 (AC-5).
|
|
internal: true
|
|
|
|
volumes:
|
|
# Size cap follows AC-NEW-3: each FDR file ≤ 64 GB. The volume layer cap is
|
|
# belt-and-suspenders; the SUT enforces the cap internally per NFT-LIM-02.
|
|
# `--storage-opt size=64g` requires overlay2 with xfs backing on the host —
|
|
# most CI runners and macOS Docker Desktop hosts lack that combination, so
|
|
# this base file uses the documented fallback (a plain named volume) and
|
|
# relies on the SUT-internal cap. CI runners with overlay2+xfs can override
|
|
# via a docker-compose.override.yml that re-introduces the tmpfs driver_opts.
|
|
fdr-output: {}
|
|
tile-cache-fixture: {}
|
|
tlog-output: {}
|
|
mock-audit: {}
|
|
replay-api-storage: {}
|
|
e2e-results:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
device: ${PWD}/../../e2e-results
|
|
o: bind
|
|
|
|
secrets:
|
|
mavlink_passkey:
|
|
file: ./secrets/mavlink_passkey
|