Repository navigation
Expand file tree
/
Copy pathopenapi.yaml
More file actions
252 lines (233 loc) · 8.77 KB
/
Copy pathopenapi.yaml
File metadata and controls
252 lines (233 loc) · 8.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
openapi: 3.1.0
info:
title: Orders API
version: "1.0.0"
summary: Accepts orders asynchronously and serves them once processed.
description: |
`POST /orders` validates an order, publishes it to SQS and answers `202` immediately.
A worker consumes the queue, stores the order in PostgreSQL and writes a receipt to S3.
`GET /orders/{id}` returns `404` until that has happened, which usually takes well under a second.
Every response carries `X-App-Version` (the git SHA of the build). During a canary release this
shows which version served a request.
**Public vs internal.** Only the operations tagged `orders`, `diagnostics` and `meta` are routed by
the public load balancer. `operations` endpoints (`/healthz`, `/readyz`, `/metrics`) exist on every
pod but are reachable only inside the cluster (kubelet probes, Prometheus).
This file is embedded in the binary and served at `GET /openapi.yaml`. Tests fail if the routes in
code and the operations here diverge, or if a handler returns a status or body the spec does not describe.
license:
name: MIT
identifier: MIT
servers:
- url: https://api.demo.bulbashenko.com
description: Demo environment (exists only while the platform is up)
- url: http://localhost:8080
description: Local stack (`make local-up`)
tags:
- name: orders
description: Business operations
- name: diagnostics
description: Demo helpers (version, CPU load)
- name: meta
description: The API description itself
- name: operations
description: Cluster-internal health and metrics endpoints (not routed publicly)
paths:
/orders:
post:
tags: [orders]
operationId: createOrder
summary: Place an order
description: |
Validates the order and queues it. Processing is asynchronous: poll `GET /orders/{id}` with the
returned id. Delivery is at-least-once and processing is idempotent on the id.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CreateOrderRequest" }
example: { item: coffee, qty: 2 }
responses:
"202":
description: Accepted and queued
headers:
X-App-Version: { $ref: "#/components/headers/XAppVersion" }
content:
application/json:
schema: { $ref: "#/components/schemas/OrderAccepted" }
example: { id: f359e6b5f0870f1a50c3411f270e8d05, status: queued }
"400": { $ref: "#/components/responses/BadRequest" }
"500": { $ref: "#/components/responses/InjectedFault" }
"503":
description: The queue is unavailable; nothing was accepted, retry later
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
example: { error: could not queue order }
/orders/{id}:
get:
tags: [orders]
operationId: getOrder
summary: Get a processed order
parameters:
- name: id
in: path
required: true
description: Order id returned by `POST /orders`
schema: { type: string, pattern: "^[0-9a-f]{32}$" }
responses:
"200":
description: The order has been processed
headers:
X-App-Version: { $ref: "#/components/headers/XAppVersion" }
content:
application/json:
schema: { $ref: "#/components/schemas/Order" }
"404":
description: Unknown id, or accepted but not processed yet
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
example: { error: order not found or not processed yet }
"500":
description: Database error, or an injected fault
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
/version:
get:
tags: [diagnostics]
operationId: getVersion
summary: Build version serving this request
responses:
"200":
description: Version (git SHA)
headers:
X-App-Version: { $ref: "#/components/headers/XAppVersion" }
content:
application/json:
schema:
type: object
required: [version]
properties:
version: { type: string, examples: [8b7ef62] }
/burn:
get:
tags: [diagnostics]
operationId: burnCpu
summary: Spend CPU time (load-testing helper)
description: Hashes in a loop for `ms` milliseconds so a load test can drive the HPA and Karpenter.
parameters:
- name: ms
in: query
required: false
description: Milliseconds of CPU to burn; invalid or missing means 50, values above 500 are capped
schema: { type: integer, default: 50 }
responses:
"200":
description: Done
headers:
X-App-Version: { $ref: "#/components/headers/XAppVersion" }
content:
application/json:
schema:
type: object
required: [burned_ms, hash]
properties:
burned_ms: { type: integer, minimum: 1, maximum: 500 }
hash: { type: string, description: First bytes of the final hash (proves work was done) }
"500": { $ref: "#/components/responses/InjectedFault" }
/openapi.yaml:
get:
tags: [meta]
operationId: getOpenAPI
summary: This document
responses:
"200":
description: OpenAPI description of this API
content:
application/yaml:
schema: { type: object, description: An OpenAPI 3.1 document }
/healthz:
get:
tags: [operations]
operationId: liveness
summary: Liveness probe (cluster-internal)
description: 200 while the process runs. A failing liveness probe makes the kubelet restart the container.
responses:
"200": { description: Alive }
/readyz:
get:
tags: [operations]
operationId: readiness
summary: Readiness probe (cluster-internal)
description: |
200 when the database answers a ping within 1s. Returns 503 as soon as shutdown begins, so the
load balancer stops sending traffic before the server closes.
responses:
"200": { description: Ready }
"503":
description: Not ready (database unreachable or shutting down)
content:
text/plain:
schema: { type: string }
/metrics:
get:
tags: [operations]
operationId: metrics
summary: Prometheus metrics (cluster-internal)
description: |
RED metrics `http_requests_total{method,route,code}` and `http_request_duration_seconds{method,route}`
(the route is the pattern, not the raw path, so label cardinality stays bounded), plus Go runtime metrics.
responses:
"200":
description: Prometheus text exposition format
content:
text/plain:
schema: { type: string }
components:
headers:
XAppVersion:
description: Git SHA of the build that served the request
schema: { type: string }
responses:
BadRequest:
description: The request body is not valid JSON or violates the constraints
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
example: { error: qty must be between 1 and 1000 }
InjectedFault:
description: |
Returned for a fraction of requests when the deployment sets `FAULT_RATE` > 0. It exists to
produce a "bad release" for the canary-analysis demo and never happens with the default of 0.
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
example: { error: injected fault }
schemas:
CreateOrderRequest:
type: object
required: [item, qty]
properties:
item: { type: string, minLength: 1, maxLength: 100, examples: [coffee] }
qty: { type: integer, minimum: 1, maximum: 1000, examples: [2] }
OrderAccepted:
type: object
required: [id, status]
properties:
id: { type: string, pattern: "^[0-9a-f]{32}$" }
status: { type: string, enum: [queued] }
Order:
type: object
required: [id, item, qty, created_at, processed_at]
properties:
id: { type: string, pattern: "^[0-9a-f]{32}$" }
item: { type: string }
qty: { type: integer }
created_at: { type: string, format: date-time, description: When the api accepted the order }
processed_at: { type: string, format: date-time, description: When the worker stored it }
Error:
type: object
required: [error]
properties:
error: { type: string }