diff --git a/deployment/nginx/conf.d/flights-api-cache.conf b/deployment/nginx/conf.d/flights-api-cache.conf new file mode 100644 index 00000000..6e9aa195 --- /dev/null +++ b/deployment/nginx/conf.d/flights-api-cache.conf @@ -0,0 +1,15 @@ +# Cache zone for ui-dashboard.gnerim.ru /api/* and /map/api/* upstreams. +# Lives in /etc/nginx/conf.d/ because proxy_cache_path is only valid in the +# http {} context, not inside server {}. +# +# Why we need it: flights.test.aeroflot.ru's WAF has a per-source-IP rate +# limit (~25-30 fresh TCP connections per window) that the parallel e2e +# burst trips. Caching read-only GETs by the customer-facing nginx layer +# absorbs the burst — only one request per (URI, window) reaches the WAF. + +proxy_cache_path /var/cache/nginx/flights-api + levels=1:2 + keys_zone=flights_api:10m + max_size=200m + inactive=30m + use_temp_path=off; diff --git a/deployment/nginx/ui-dashboard.gnerim.ru.conf b/deployment/nginx/ui-dashboard.gnerim.ru.conf index 0428e34e..493aaebe 100644 --- a/deployment/nginx/ui-dashboard.gnerim.ru.conf +++ b/deployment/nginx/ui-dashboard.gnerim.ru.conf @@ -1,6 +1,9 @@ # Production vhost for ui-dashboard.gnerim.ru. # Symlink into /etc/nginx/sites-enabled/ and reload nginx. # TLS certs assumed to exist via certbot (separate process). +# +# Cache zone `flights_api` is declared in /etc/nginx/conf.d/flights-api-cache.conf +# (proxy_cache_path lives at http context, can't be in server {}). server { listen 80; @@ -37,6 +40,9 @@ server { # ssh -L tunnel to webzavod which exits via ppp0 with a corp-VPN source IP # the upstream WAF whitelists. SNI/Host are set explicitly because the # TCP target is loopback rather than the real hostname. + # + # Cached to absorb e2e bursts that would otherwise trip the upstream + # WAF rate limit. Only GET/HEAD are cached (default proxy_cache_methods). location /api/ { auth_basic off; proxy_pass https://127.0.0.1:8443; @@ -44,8 +50,19 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_ssl_server_name on; proxy_ssl_name flights.test.aeroflot.ru; + + proxy_cache flights_api; + proxy_cache_key "$scheme$host$request_uri"; + proxy_cache_valid 200 1m; + proxy_cache_valid 404 30s; + proxy_cache_lock on; + proxy_cache_use_stale error timeout updating http_403 http_500 http_502 http_503 http_504; + proxy_cache_bypass $http_cache_control; + add_header X-Cache-Status $upstream_cache_status always; } + # Map tiles — heavily cacheable (tile data rarely changes for an area). + # Longer TTL than /api/ since these are essentially static. location /map/api/ { auth_basic off; proxy_pass https://127.0.0.1:8443; @@ -53,5 +70,13 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_ssl_server_name on; proxy_ssl_name flights.test.aeroflot.ru; + + proxy_cache flights_api; + proxy_cache_key "$scheme$host$request_uri"; + proxy_cache_valid 200 24h; + proxy_cache_valid 404 5m; + proxy_cache_lock on; + proxy_cache_use_stale error timeout updating http_403 http_500 http_502 http_503 http_504; + add_header X-Cache-Status $upstream_cache_status always; } } diff --git a/deployment/setup-pve201.sh b/deployment/setup-pve201.sh index ed485e69..36fd0bd8 100755 --- a/deployment/setup-pve201.sh +++ b/deployment/setup-pve201.sh @@ -100,6 +100,25 @@ fi mkdir -p /etc/nginx/htpasswd ok "/etc/nginx/htpasswd ensured" +# Install proxy_cache zone declaration (must live in http {} context) +CACHE_CONF_SRC="$REPO_ROOT/deployment/nginx/conf.d/flights-api-cache.conf" +CACHE_CONF_DST="/etc/nginx/conf.d/flights-api-cache.conf" +if [ -f "$CACHE_CONF_DST" ] && cmp -s "$CACHE_CONF_SRC" "$CACHE_CONF_DST"; then + ok "$CACHE_CONF_DST already up-to-date" +else + cp "$CACHE_CONF_SRC" "$CACHE_CONF_DST" + ok "installed $CACHE_CONF_DST" +fi + +# Cache directory — nginx auto-creates with proper perms on first start, but +# we pre-create with the right ownership so reload picks it up cleanly. +CACHE_DIR="/var/cache/nginx/flights-api" +NGINX_USER="$(awk '/^user / {gsub(";",""); print $2}' /etc/nginx/nginx.conf 2>/dev/null | head -1)" +NGINX_USER="${NGINX_USER:-www-data}" +mkdir -p "$CACHE_DIR" +chown -R "$NGINX_USER":"$NGINX_USER" "$CACHE_DIR" +ok "$CACHE_DIR ensured (owner: $NGINX_USER)" + # ---------- 4. htpasswd ---------- step "4. htpasswd" diff --git a/playwright.config.ts b/playwright.config.ts index c4148faa..b89821bc 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -3,9 +3,16 @@ import { defineConfig } from "@playwright/test"; const baseURL = process.env.BASE_URL ?? "http://localhost:8080"; const startLocalServer = !process.env.BASE_URL; +// CI: throttle workers + retry transient flake (the upstream WAF rate-limits +// /api/* by source IP; nginx proxy_cache absorbs most repeat fetches but a +// burst can still trip 1-2 of them). +const isCI = !!process.env.CI; + export default defineConfig({ testDir: "tests/e2e", timeout: 30000, + workers: isCI ? 2 : undefined, + retries: isCI ? 2 : 0, use: { baseURL, headless: true,