# DEV Ground Rule §Precondition.2 — API spec (OpenAPI) # # Enumerates every HTTP endpoint exposed by the CQA Test App, with the HTTP # status codes each actually returns (verified against a running instance). # # IMPORTANT: this service is a "200-envelope" API for most data endpoints — the # HTTP status is 200 and a logical result `code` (200/400/403/500) is carried in # the JSON body. Only flask_restful (reqparse) endpoints and the App Runner # blueprint return real HTTP 4xx/5xx codes. # # Triples whose success path needs a live external backend (Flava Object # Storage, databases, Athenz ZTS, egress proxy, the explorer subprocess) are # marked `x-requires-external: true`. They are exercised by tests/e2e/ # test_api_external.py (gated behind E2E_WITH_BACKENDS=1). openapi: 3.0.3 info: title: CQA Test App API version: 1.6.1 description: Flask connectivity / platform test application. servers: - url: http://localhost:8000 description: Local dev (override with E2E_BASE_URL) paths: # ── Core / health ────────────────────────────────────────────────────────── /: get: { summary: Home page, operationId: getRoot, responses: { "200": { description: OK } } } /test: get: { summary: Ping test, operationId: getTest, responses: { "200": { description: OK } } } /monitor/l7check: get: { summary: L7 health check, operationId: getL7Check, responses: { "200": { description: OK } } } /headers: get: { summary: Echo selected headers, operationId: getHeaders, responses: { "200": { description: OK } } } /swagger.json: get: { summary: Swagger JSON spec, operationId: getSwaggerJson, responses: { "200": { description: OK } } } /run_cmd: post: summary: Run a shell command (reqparse — JSON body required) operationId: postRunCmd responses: "200": { description: Command executed } "400": { description: Empty cmd } "415": { description: Non-JSON content type } /webhook: post: summary: Echo a JSON webhook payload operationId: postWebhook responses: "200": { description: OK } /run_iperf: post: summary: Run an iperf3 test (reqparse — JSON body required) operationId: postRunIperf responses: "400": { description: Invalid protocol / params } "415": { description: Non-JSON content type } /athenz_access_token: post: summary: Get an Athenz access token (reqparse — JSON body required) operationId: postAthenzAccessToken responses: "400": { description: Missing required arguments } "415": { description: Non-JSON content type } "500": { description: Athenz error (no cert material) } /test_egp: post: summary: Test egress proxy connectivity operationId: postTestEgp responses: # On proxy/remote failure the handler still returns HTTP 200 with an # error string in the body, so any non-empty config reaches 200. "200": { description: Proxy/remote result } "500": { description: Missing environment variables } /start_least_conn_processes: post: summary: Start least-connection LB test processes (binds :8080/:9080) operationId: postStartLeastConn responses: "200": { description: Processes started } /explorer/{path}: get: summary: Proxy to the local explorer HTTP server (:10346 subprocess) operationId: getExplorer parameters: - { name: path, in: path, required: true, schema: { type: string } } responses: # The :10346 explorer subprocess is started by run_cqa_test_app.py. "200": { description: Proxied response } /{file_path}: get: summary: Render an arbitrary markdown wiki file operationId: getMarkdownFile parameters: - { name: file_path, in: path, required: true, schema: { type: string } } responses: "200": { description: Rendered HTML } "404": { description: File not found } # ── UI pages ──────────────────────────────────────────────────────────────── /test_fos_ui/: get: summary: FOS test UI (template currently raises) operationId: getFosUi responses: "500": { description: Template render error } /test_fos_crud_ui/: get: { summary: FOS CRUD UI, operationId: getFosCrudUi, responses: { "200": { description: OK } } } /test_fos_dual_env_ui/: get: { summary: FOS dual-env UI, operationId: getFosDualEnvUi, responses: { "200": { description: OK } } } /dbs_test_ui/: get: { summary: DBS test UI, operationId: getDbsUi, responses: { "200": { description: OK } } } /test_mysql_dual_env_ui/: get: { summary: MySQL dual-env UI, operationId: getMysqlDualEnvUi, responses: { "200": { description: OK } } } /function_test_ui/: get: { summary: Function test UI, operationId: getFunctionUi, responses: { "200": { description: OK } } } /app_runner_test_ui/: get: { summary: App Runner test UI, operationId: getAppRunnerUi, responses: { "200": { description: OK } } } /egp_test_ui/: get: { summary: Egress proxy UI, operationId: getEgpUi, responses: { "200": { description: OK } } } # ── Flava Object Storage (200-envelope; body carries logical code) ────────── /test_fos/buckets/{bucket_name}/{object_name}: put: summary: Put test data into a FOS object operationId: putTestFos parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } - { name: object_name, in: path, required: true, schema: { type: string } } responses: "200": { description: Envelope (body code 200 on success / 403 without creds) } /get_fos_object/{bucket_name}/{object_name}: get: summary: Get a FOS object operationId: getFosObject parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } - { name: object_name, in: path, required: true, schema: { type: string } } responses: "200": { description: Envelope (body code 200/403/404) } /test_fos_crud/{bucket_name}/{object_name}: post: summary: Run FOS create/read/update/delete test operationId: postTestFosCrud parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } - { name: object_name, in: path, required: true, schema: { type: string } } responses: "200": { description: Envelope (body code 200/403) } /create_fos_object/{bucket_name}/{object_name}: put: summary: Upload a FOS object (reqparse — JSON body with data) operationId: putCreateFosObject parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } - { name: object_name, in: path, required: true, schema: { type: string } } responses: "200": { description: Uploaded, x-requires-external: true } "400": { description: Missing data } /delete_fos_object/{bucket_name}/{object_name}: delete: summary: Delete a FOS object operationId: deleteFosObject parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } - { name: object_name, in: path, required: true, schema: { type: string } } responses: "200": { description: Envelope, x-requires-external: true } "404": { description: Object not found, x-requires-external: true } /list_objects/{bucket_name}: get: summary: List FOS objects in a bucket operationId: getListObjects parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } responses: "200": { description: Envelope (body code 200/403) } /update_bucket/{bucket_name}/{action}: post: summary: Enable/disable bucket static website operationId: postUpdateBucket parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } - { name: action, in: path, required: true, schema: { type: string } } responses: "200": { description: Envelope (body code 200/400/403) } /download_objects/{bucket_name}/{object_name}: get: summary: Download a FOS object to the explorer dir operationId: getDownloadObjects parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } - { name: object_name, in: path, required: true, schema: { type: string } } responses: "200": { description: Envelope, x-requires-external: true } /write_to_dual_env/{bucket_name}/{object_name}: post: summary: Write a FOS object to both ssk_prod and prod operationId: postWriteDualEnv parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } - { name: object_name, in: path, required: true, schema: { type: string } } responses: "200": { description: Envelope (body code 200 on success / 500 without creds) } /list_from_dual_env/{bucket_name}: get: summary: List FOS objects from both ssk_prod and prod operationId: getListDualEnv parameters: - { name: bucket_name, in: path, required: true, schema: { type: string } } responses: "200": { description: Envelope (body code 200/500) } # ── Databases (200-envelope) ──────────────────────────────────────────────── /connect: post: summary: Connect to a database product operationId: postConnect responses: "200": { description: Envelope (body code 200/400/500) } /execute_query: post: summary: Execute a query on a connected database operationId: postExecuteQuery responses: "200": { description: Envelope (body code 200/400) } /execute_opensearch_query: post: summary: Execute an OpenSearch API query operationId: postExecuteOpensearch responses: "200": { description: Envelope (body code 200/500) } /connect_dual_mysql: post: summary: Connect to both MySQL databases operationId: postConnectDualMysql responses: "200": { description: Envelope (body code 200/400/500) } /execute_dual_mysql_query: post: summary: Execute a query on both MySQL databases operationId: postExecuteDualMysql responses: "200": { description: Envelope (body code 200/400/500) } # ── App Runner blueprint (real HTTP status codes) ─────────────────────────── /api/app_runner/env_vars: get: summary: List all environment variables operationId: getEnvVars responses: "200": { description: OK } /api/app_runner/env_vars/{key}: get: summary: Get a single environment variable operationId: getEnvVar parameters: - { name: key, in: path, required: true, schema: { type: string } } responses: "200": { description: Found } "404": { description: Not found } /api/app_runner/get_access_token: get: summary: Get an IAM access token for a product domain operationId: getAppRunnerAccessToken parameters: - { name: product_domain, in: query, required: false, schema: { type: string } } responses: "200": { description: Token retrieved, x-requires-external: true } "400": { description: No product domain provided } "500": { description: Failed to retrieve token } /api/app_runner/send_request: get: summary: Send an HTTP GET to a host operationId: getAppRunnerSendRequest parameters: - { name: host, in: query, required: false, schema: { type: string } } responses: # Reachable locally by pointing host at a separate local process # (e.g. the :10346 explorer) to avoid a single-thread self-deadlock. "200": { description: Connected } "400": { description: No host URL provided } "500": { description: Request failed } /api/app_runner/validate_volume: post: summary: Validate a file exists in a mounted volume operationId: postValidateVolume responses: "200": { description: File exists } "400": { description: Missing required parameters } "404": { description: File does not exist } components: schemas: Error: type: object required: [error] properties: error: { type: string }