Return native Engine NotFound faults and clear Web UI session on 401.

Include the bad id in host/datacenter/cluster errors, expire stale auth in the
console, and document Ingress annotations that preserve fault bodies.
This commit is contained in:
2026-07-22 06:52:04 +03:00
parent 9c94df119b
commit af26ad4141
18 changed files with 456 additions and 61 deletions
+5 -2
View File
@@ -73,8 +73,11 @@ See [Kubernetes / Helm](kubernetes.md) and
| `secrets.ticketSigningKey` | Must be rotated for shared clusters |
| `service.port` | ClusterIP port (default `8080`) |
`gateway.*` / `ingress.*` in `values.yaml` are reserved; the chart currently
exposes FastAPI directly (no Compose-style nginx gateway).
`gateway.*` is unused (no Compose-style nginx gateway in the chart).
`ingress.*` is optional — see
[`values-ingress-example.yaml`](../helm/ovirt-api-simulator/values-ingress-example.yaml)
and [Troubleshooting](troubleshooting.md) for annotations that preserve Engine
fault bodies.
## Contract packs
+6 -3
View File
@@ -19,9 +19,12 @@ Published image (when pushed):
| **seed** Job (optional) | `minimal` or `demo` lab data |
> The Compose stack publishes Engine HTTPS + UI via nginx `api-gateway`
> ([ports.md](ports.md)). The Helm chart **does not yet** ship that gateway:
> `gateway.*` / `ingress.*` keys in `values.yaml` are reserved and unused.
> Access the simulator Service on `:8080` (port-forward or your own Ingress).
> ([ports.md](ports.md)). The Helm chart serves the simulator Service on `:8080`.
> Optional Ingress: enable with
> [`values-ingress-example.yaml`](../helm/ovirt-api-simulator/values-ingress-example.yaml)
> (`proxy-intercept-errors: "false"`, `custom-http-errors: "502,503"` so Engine
> fault bodies are not rewritten to branded HTML). See
> [Troubleshooting](troubleshooting.md).
## Prerequisites
@@ -0,0 +1,92 @@
# Prompt: fix API error bodies + Web UI session on 401 (other simulators)
Copy everything below the line into a chat opened on **ovirt_api_simulator**,
**vmware_api_simulator**, or **openstack_api_simulator**. Adapt product names
(API path prefixes, cookie/CSRF names, Helm chart paths) to that repo.
---
## Task
Fix the same class of production issues already fixed in
`proxmox_api_simulator` (reference implementation). Do **not** claim the
simulator is “incomplete” or “not supported”; keep durable, realistic API
behavior.
### Symptoms seen behind Ingress
1. Client calls a path with a **wrong / missing resource id** (e.g. bad node /
host / datacenter name). Instead of the simulators JSON/XML error, the
browser or curl receives a **branded HTML 404** (“page not found”) from the
cluster Ingress / custom error pages.
2. Client sends a mutation (**POST/PUT/DELETE**) that the Ingress rejects or
rewrites → **nginx HTML 405** instead of the simulators auth/validation
error.
3. In the **Web UI**, after the API returns **401** (expired session), the
response panel says unauthorized but the **header still shows the signed-in
user** (does not switch to Guest / signed-out).
Root cause for (1)/(2) is usually **ingress-nginx**
`custom-http-errors` / `proxy-intercept-errors` replacing upstream bodies.
Root cause for (3) is UI code that renders 401 text but does not clear
ticket/token/cookie/localStorage and call the same path as logout.
### What to implement in THIS repo
#### 1. Clearer “resource not found” API errors
- Find the shared helper that validates that a node/host/cluster/datacenter
exists (equivalent of Proxmox `require_node`).
- On miss, return the **native API error shape** for this product with a
message that includes the bad id, e.g. Proxmox style:
`No such node ('pve01')` plus a field-level `errors` map when the product
uses one.
- Update unit tests that matched the old message string.
- Ensure the app always returns `Content-Type` appropriate for the API
(JSON/XML) and never an HTML error page from the app itself.
#### 2. Web UI: treat HTTP 401 as session expiry
- When any in-app API helper gets **401** and a session is currently stored:
- clear ticket/token/csrf/username (whatever this UI uses);
- clear auth cookies;
- clear persisted auth in localStorage/sessionStorage;
- update the header auth pill / badge to the signed-out / Guest state;
- show a short toast like “Session expired — sign in again”;
- still show the 401 explanation in the response panel.
- Refactor logout to share one `clearSession(...)` helper so logout and
expiry stay in sync. Do not leave stale “signed in as …” UI after 401.
#### 3. Helm / Ingress example + docs
- In the charts ingress example values (and kubernetes troubleshooting docs):
- `nginx.ingress.kubernetes.io/proxy-intercept-errors: "false"`
- `nginx.ingress.kubernetes.io/custom-http-errors: "502,503"`
(do **not** list 404/405 so API bodies are preserved)
- Document: if clients still see branded HTML 404/405, the cluster controller
is rewriting responses — fix Ingress annotations, not the simulator handlers.
- Document a correct authenticated mutation curl for this product (cookie /
token + CSRF if required + the body encoding this API expects — often
form-urlencoded, not bare JSON).
### Out of scope
- Do not add “not implemented in the simulator” user-facing messages.
- Do not change unrelated product semantics.
- Do not force-push or commit unless the user asks.
### Done when
- Missing resource → product-shaped API error with the id in the message
(verified by unit test and/or curl against the app, not only via Ingress).
- UI 401 → Guest / signed-out header + cleared storage/cookies.
- Ingress example + troubleshooting docs warn about HTML error-page rewrite
and show the correct curl pattern.
- Lint/tests for touched areas pass.
### Reference (proxmox_api_simulator)
- `app/handlers/common.py``require_node` / `node_metadata` message
- `app/web/index.html``clearSession` / `expireSession` / `showResponse` 401
- `helm/proxmox-api-simulator/values-ingress-example.yaml` — annotations
- `docs/troubleshooting.md` — Ingress HTML 404/405 + curl notes
+5 -2
View File
@@ -73,8 +73,11 @@ Docker Compose подставляет многие из них для серви
| `secrets.ticketSigningKey` | Нужно ротировать на общих кластерах |
| `service.port` | Порт ClusterIP (по умолчанию `8080`) |
`gateway.*` / `ingress.*` в `values.yaml` зарезервированы; чарт сейчас отдаёт
FastAPI напрямую (без nginx gateway как в Compose).
`gateway.*` не используется (нет nginx gateway как в Compose).
`ingress.*` опционален — см.
[`values-ingress-example.yaml`](../../helm/ovirt-api-simulator/values-ingress-example.yaml)
и [Устранение неполадок](troubleshooting.md) для annotations, сохраняющих
тела fault Engine.
## Контрактные packs
+6 -4
View File
@@ -19,10 +19,12 @@
| **seed** Job (опционально) | Лабораторные данные `minimal` или `demo` |
> Compose публикует Engine HTTPS + UI через nginx `api-gateway`
> ([ports.md](ports.md)). Helm-чарт **пока не** включает этот gateway:
> ключи `gateway.*` / `ingress.*` в `values.yaml` зарезервированы и не
> используются. Доступ — к Service симулятора на `:8080` (port-forward или
> свой Ingress).
> ([ports.md](ports.md)). Helm-чарт отдаёт Service симулятора на `:8080`.
> Опциональный Ingress:
> [`values-ingress-example.yaml`](../../helm/ovirt-api-simulator/values-ingress-example.yaml)
> (`proxy-intercept-errors: "false"`, `custom-http-errors: "502,503"`, чтобы
> fault Engine не подменялся брендированным HTML). См.
> [Устранение неполадок](troubleshooting.md).
## Требования
+43
View File
@@ -38,6 +38,49 @@ make seed-demo
Проверьте, что `OVIRT_SERIES` соответствует ожидаемому pack
([api-versions.md](api-versions.md)).
## HTTP 401 / Web UI всё ещё показывает пользователя
Токен Engine SSO истёк или отозван (например после `demo/unload` / reseed).
В Web UI HTTP **401** очищает локальную сессию и показывает **Guest** в
шапке; войдите снова из Environment (`admin@internal` / `secret`).
## Ingress отдаёт брендированный HTML 404 / nginx 405 вместо fault Engine
Симулятор отвечает на ошибки API **fault** XML/JSON (`reason` / `detail`).
Если видите HTML «page not found» или plain nginx **405**, **Ingress /
reverse proxy** подменил тело ответа (часто через `custom-http-errors` у
ingress-nginx).
Исправление на Ingress для этого host (см.
`helm/ovirt-api-simulator/values-ingress-example.yaml`):
```yaml
annotations:
nginx.ingress.kubernetes.io/proxy-intercept-errors: "false"
nginx.ingress.kubernetes.io/custom-http-errors: "502,503"
```
Проверьте с `Accept: application/json`. Отсутствующий host должен выглядеть так:
```json
{"fault": {"reason": "NotFound", "detail": "No such host ('…')"}}
```
### Корректная authenticated mutation (стиль Engine)
Bearer (или Basic) и JSON-тело (не form-urlencoded):
```bash
# после POST /ovirt-engine/sso/oauth/token → access_token
TOKEN=
curl -sk -X POST "https://HOST/ovirt-engine/api/vms" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Version: 4" \
-d '{"vm":{"name":"lab-vm","cluster":{"name":"Default"}}}'
```
## Падения клиентских suites
Убедитесь, что стек поднят и засеян, затем сначала smoke:
+43
View File
@@ -36,6 +36,49 @@ make seed-demo
Set `Version: 4` (or `3`) or use `/ovirt-engine/api/v4/...`. Confirm
`OVIRT_SERIES` matches the pack you expect ([api-versions.md](api-versions.md)).
## HTTP 401 / Web UI still shows signed-in user
Engine SSO token expired or was revoked (e.g. after `demo/unload` / reseed).
In the Web UI, HTTP **401** clears the local session and shows **Guest** in the
header; sign in again from Environment (`admin@internal` / `secret`).
## Ingress returns branded HTML 404 / nginx 405 instead of Engine fault
The simulator answers API errors as Engine **fault** XML/JSON
(`reason` / `detail`). If you see a site HTML “page not found” or plain nginx
**405** page, the **Ingress / reverse proxy** replaced the upstream body (often
via `custom-http-errors` on the ingress-nginx controller).
Fix on the Ingress for this host (see
`helm/ovirt-api-simulator/values-ingress-example.yaml`):
```yaml
annotations:
nginx.ingress.kubernetes.io/proxy-intercept-errors: "false"
nginx.ingress.kubernetes.io/custom-http-errors: "502,503"
```
Then re-check with `Accept: application/json`. A missing host should look like:
```json
{"fault": {"reason": "NotFound", "detail": "No such host ('…')"}}
```
### Correct authenticated mutation (Engine-style)
Use Bearer (or Basic) auth and JSON body (not form-urlencoded):
```bash
# after POST /ovirt-engine/sso/oauth/token → access_token
TOKEN=
curl -sk -X POST "https://HOST/ovirt-engine/api/vms" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Version: 4" \
-d '{"vm":{"name":"lab-vm","cluster":{"name":"Default"}}}'
```
## Client suite failures
Ensure the stack is up and seeded, then run smoke first: