Editable inputs and command conventions¶
All policy and traffic operations in this walkthrough use ordinary curl, jq, Docker and iproute2 commands. Copy a request JSON file, change the fields relevant to your deployment, POST it, read the installed state and verify actual traffic before accepting the change.
| Download | Purpose | What to customize |
|---|---|---|
| rule.json | Complete shared-model service request | VIP/host/port/model, backend endpoint, capacity and band policy |
| backend.py | Synthetic receipt and hold/release server | Lab-only HTTP port and bounded hold behavior if your isolated topology differs |
The same complete rule is written out in onboarding; downloading the JSON is optional. Python is used only to run the synthetic backend. Configuration, tenant/key creation, concurrent requests, readiness checks, fault injection and cleanup are shown directly in the chapters.
Before you start¶
Keep the selected DOCS checkout and private LAB from setup.
Focus of this page: The backend is the only executable fixture; rule.json is editable input.
Topology and execution locations¶
flowchart TD
A["Support namespace<br/>192.0.2.10"] -->|"sa to ga"| G["share-gateway<br/>inference 8080 / API 11111"]
B["Research namespace<br/>198.51.100.10"] -->|"sb to gb"| G
G -->|"ge to se"| E["Receipt backend namespace<br/>203.0.113.1:8080"]
G -.->|"key and objective store"| P["share-pg<br/>Docker bridge / 5432"]
O["Host Bash<br/>private LAB files"] -.->|"management via share-a"| G
O -.->|"independent receipt via share-backend"| E
style G fill:#e1f5fe,stroke:#0288d1,color:#1f2937
style E fill:#e8f5e9,stroke:#43a047,color:#1f2937
style P fill:#fff3e0,stroke:#fb8c00,color:#1f2937
One disposable Linux host contains two Gateway/DB containers and four network namespaces. Support reaches the Gateway on 192.0.2.254; research uses its 198.51.100.254 interface. The backend is a host Python process in share-backend, not an inference engine. PostgreSQL uses its inspected Docker-bridge address.
| Where | What happens here |
|---|---|
| Host root Bash | Create namespaces, save LAB inputs, start/stop exact owned processes. |
| share-a / share-b | Send inference as support/research; management curls use share-a. |
| share-gateway | Apply policy and forward requests; management is port 11111, inference port 8080. |
| share-backend | Read /receipt/ and /order directly, bypassing Gateway routing. |
| share-pg | Keep keys/objectives; pause only in the explicit recovery experiment. |
Reading path and reference¶
How to run and adapt a chapter¶
Use the root Bash session and variables from setup. Keep the outer LAB variable set to the same private run directory. The blocks run in one dedicated root Bash and retain variables and background jobs between steps. Do not open a new shell for each box: later wait commands must run in the shell that started the clients. An unexpected failure exits the dedicated lab shell; use the original terminal for diagnosis and cleanup. For diagnosis or cleanup from a new terminal, set LAB to the saved directory and source its private env file. Do not resume a half-run contention experiment in that new shell: its clients belong to the old shell. Clean up the owned run and start a new LAB for a complete retry. Never skip a failed block or run only its trailing lines. Read each experiment first, then run its short Bash steps consecutively in the same shell. The hold fixture releases after 60 seconds and clients time out after 55 seconds; pausing while reading in the middle of a contention experiment invalidates its backlog. Keep those bounds when increasing request counts, or deliberately adjust the fixture/client bounds and revalidate the contention state.
Each management request shows its HTTP method, exact API path, JSON request file, response file and expected status. POST /config/loadbalancer receives serviceArguments plus endpoints; GET /config/loadbalancer/all returns lbAttr. Only request fields belong in a POST. Effective GET fields such as fc_effective are observations, not configuration inputs.
Each policy experiment derives its request from $LAB/base-rule.json with an explicit jq merge into serviceArguments. This resets the other knobs to the visible base for a controlled comparison. If you want to change only part of your own deployment, retain its original request JSON and edit that instead; do not use this lab's base to replace an unrelated service. Review omitted/zero semantics in UC-1 common conventions.
Read the request and the independent receipt¶
curl --config "$LAB/keys/support.curl" reads a private key header without printing it. Change that file to select another workload credential. --data-binary @...request.json sends the exact bytes shown in the preceding heredoc; edit model, messages, max_tokens or an authorized objective there. --http1.1 selects the waiting-queue path this lab measures. A trailing & starts a held request, $! captures its PID, and wait checks its eventual curl exit.
For each named request, LAB contains:
| File | Observation |
|---|---|
| NAME.request.json | Exact submitted payload; editable retry input |
| NAME.headers | Actual response headers, including admission metadata when enabled |
| NAME.http | HTTP status, separate from the body |
| NAME.response.json | Actual Gateway/backend response body |
| NAME.receipt.json | Independent backend count, actual decoded request and headers |
| NAME.pid | Background curl PID for held requests |
The receipt is fetched directly inside share-backend, bypassing Gateway routing. A successful request should have count 1; an admission denial count 0. Receipt-server failure must stop the check, never become count 0. HTTP000 or a nonzero curl exit is a transport/client failure, not a valid policy denial. For a management store write, 503 ai_key_store_unavailable means not sent; 504 ai_key_store_outcome_unknown requires readback before retrying. Inspect its code and message fields. Inference denial bodies use their own error field and codes; do not substitute one response contract for the other. Expected error responses intentionally use curl without --fail so their bodies remain available; the following test checks their exact status.
X-Test-Nonce, X-Test-Hold and the backend /receipt/ and /release/ endpoints are synthetic lab controls, not product policy APIs. Every experiment uses unique request names. Keep LAB private: keys, prompts and observed headers must not be copied to public issues. The chapters show explicit jq checks for resolved tenant/class and typed errors; inspect the saved JSON when adapting a condition.
Policy units and defaults¶
Triples always follow critical:standard:sheddable. Positive objective priority is critical, zero is standard, negative is sheddable. Magnitude matters for permission raises (including negative-to-less-negative), not for creating more than three scheduling bands.
| Field | Lab value / unit | Meaning |
|---|---|---|
| priority_class | critical / standard / sheddable | Maximum authority; a critical cap alone is not a critical default |
| default_objective, allowed_objectives | names | Default urgency and explicitly authorized raises |
| ai_objective_strict | true | Invalid/unauthorized hints are denied; false falls back to default |
| fc_wake_policy | strict / weighted / inherit | Band wake selection, default strict |
| fc_band_weights | 8:3:1 | Positive weights for weighted mode, total at most 64 |
| fc_tenant_wake | fifo / rr / inherit | Within-band waiter selection; RR uses resolved tenant |
| fc_tenant_max_share_pct | percent | Per-tenant inflight and queue-depth share of pool; excess may return 429 admission_tenant_share; not per-key/GPU allocation |
| fc_band_min_share_pct | 0:25:0, percent | Minimum inflight under demand; not idle reservation |
| fc_critical_reserve_pct | 25, percent | Capacity other bands cannot consume even when critical is idle |
| fc_band_queue_pct | 0:0:25, percent | Band waiting-population cap, not reserved queue slots |
| fc_band_wait_ms | 0:150:100, ms | Per-band waiting deadline; zero uses rule window |
| fc_max_queue_wait_ms | 30000, ms | Rule waiting window |
| fc_llmd_headers | on | Sanitized class/fairness forwarding and TTL handling |
| fc_shed_queue | on / off / inherit | Whether sheddable requests may wait |
A base-rule POST can return 409 for an identical no-op; do not treat arbitrary 409 as successful apply. Every exercise changes a field before expecting 200. Rejected invalid configurations must preserve prior traffic. Tenant ratelimit POST normally returns 204, key creation 201, rule/objective/binding POST 200. Auth disabled uses enum disabled, never off. Read UC-1 replacement semantics before restoring existing tenant quotas: a class-only tenant POST (tenant_id plus class/default/grant fields, as shown here) leaves rate limits unchanged. A POST that includes aggregate rate-limit fields uses their replacement semantics, where omitted aggregate values can reset limits; model rows have their own upsert behavior. Review the whole payload before applying it to an existing tenant.
Check your result¶
Understand which files hold the request, Gateway response and independent receipt before adapting commands.
Keep the configured policy, HTTP status, resolved identity, and independent backend receipt together. A rejected request must have receipt 0; a successful request must have receipt 1. A missing receipt response is a failed observation, never a zero.
Next steps and cleanup¶
Continue in the documented order with the same LAB. Remove an experiment's binding or temporary rule before the next experiment; stop the shared lab only at the final cleanup chapter. Read the lab starting point if required values are missing. Use cleanup and recovery when ending the run.