100 lines
5.0 KiB
Markdown
100 lines
5.0 KiB
Markdown
# Full-sync HTTP control API
|
|
|
|
The handler imports no queue or storage implementations. Inject the five
|
|
contract interfaces in `Dependencies`: `RunLister`, `RunReader`,
|
|
`FailurePager`, `ReplayRequester`, and `ActionReader`. The replay service owns durable
|
|
idempotency and execution; the HTTP layer does not cache requests or launch work.
|
|
|
|
Use `NewRunReader(live, archive, rounds)` to compose `LiveRunReader`,
|
|
`ArchivedRunReader`, and `RoundStatsReader` into a `RunReader`. Live metadata
|
|
wins and returns `source: "live"`; only `ErrNotFound` from live metadata permits
|
|
archive fallback, returning `source: "archive"`. Live storage errors and round
|
|
lookup errors propagate. Archive and round readers may be nil to disable those
|
|
lookups. A failure listing always calls `FailurePager` directly, never archives.
|
|
|
|
## Routes and responses
|
|
|
|
All routes require `Authorization: Bearer <configured token>`. Missing, wrong,
|
|
or ambiguous credentials return 401 with a `WWW-Authenticate` challenge. An
|
|
empty configured token is a startup configuration error. Responses use JSON and
|
|
`Cache-Control: no-store`.
|
|
|
|
| Method | Route | Success body |
|
|
| --- | --- | --- |
|
|
| GET | `/internal/v1/full-sync-runs?syncKind=&limit=` | 200 `contract.RunList` |
|
|
| GET | `/internal/v1/full-sync-runs/{runId}` | 200 `contract.RunView` |
|
|
| GET | `/internal/v1/full-sync-runs/{runId}/failures?cursor=&limit=` | 200 `contract.FailurePage` |
|
|
| POST | `/internal/v1/full-sync-runs/{runId}/replay` | 202 `contract.ReplayAccepted` |
|
|
| GET | `/internal/v1/full-sync-actions/{actionId}` | 200 `contract.ReplayAction` |
|
|
|
|
Run list results merge active Redis metadata with flat `run-<runId>.json`
|
|
archives under the configured archive root. Active Redis metadata wins when an
|
|
ID exists in both sources. `syncKind` accepts `traffic_usage` or `sim_status`;
|
|
`limit` defaults to 50 and is capped at 100.
|
|
|
|
Failure limits use the contract rules: absent, empty, or zero selects 100;
|
|
positive values up to 500 are accepted; other values are rejected. Repeated
|
|
cursor/limit parameters and malformed query encoding are rejected. Cursors are
|
|
opaque and passed unchanged after URL decoding. The pager validates cursor
|
|
contents and run binding. Empty pages serialize `items: []` and preserve
|
|
`nextCursor`; only an empty next cursor signals completion.
|
|
|
|
Replay requires a single `Idempotency-Key` header and a JSON body such as
|
|
`{"requestedBy":"operator"}`. The body is limited to 64 KiB, must contain one
|
|
JSON object with no unknown fields, and must satisfy `ReplayRequest.Validate`.
|
|
If supplied, Content-Type must be `application/json` (parameters are allowed).
|
|
The exact `requestedBy` value is preserved for fingerprinting. Repeated accepted
|
|
requests return the service's existing action/target and current status with
|
|
202. The adapter constructs `statusUrl` and the `Location` header from actionId.
|
|
|
|
All application errors use this envelope, without storage error details:
|
|
|
|
```json
|
|
{"error":{"code":"invalid_argument","message":"Invalid request"}}
|
|
```
|
|
|
|
| Condition | HTTP status | Error code |
|
|
| --- | --- | --- |
|
|
| Invalid path, query, header, body, or `ErrInvalidArgument` | 400 | `invalid_argument` |
|
|
| Missing/wrong bearer credentials | 401 | `unauthorized` |
|
|
| Unknown route/resource or `ErrNotFound` | 404 | `not_found` |
|
|
| Unsupported route method | 405 | `method_not_allowed` |
|
|
| `ErrConflict` (including changed idempotent request), `ErrLeaseLost` | 409 | `conflict` |
|
|
| `ErrFailureDetailsUnavailable` | 410 | `failure_details_unavailable` |
|
|
| Oversized replay body | 413 | `body_too_large` |
|
|
| `ErrBusy` | 503 | `busy` |
|
|
| Other service errors or unencodable service output | 500 | `internal_error` |
|
|
|
|
## Lifecycle
|
|
|
|
`NewHandler(token, deps)` can be mounted by an existing HTTP server. For an
|
|
independent listener, construct `NewServer(Config{Addr: address, BearerToken:
|
|
token}, deps)` and call `Start(workerCtx)`. `Start` binds synchronously and
|
|
returns address-in-use and other startup errors directly. `Addr()` exposes the
|
|
bound address, including an OS-assigned port when configured with port zero.
|
|
|
|
Call `Wait()` after Start, normally in the worker's supervision goroutine. It
|
|
returns serving or shutdown failures; it also returns startup failures if Start
|
|
failed. The server is single-use and rejects a second Start. Wait can have
|
|
multiple callers but must not be called before Start.
|
|
|
|
Canceling `workerCtx` closes the listener and drains active HTTP requests.
|
|
Active request contexts stay usable during the drain. `ShutdownTimeout` defaults
|
|
to ten seconds; if it expires, active connections are closed and Wait returns
|
|
an error wrapping `context.DeadlineExceeded`. Normal cancellation with a
|
|
successful drain returns nil. No worker wiring or configuration-file changes
|
|
are part of this package.
|
|
|
|
## Verification
|
|
|
|
```text
|
|
gofmt -l internal/fullsync/httpapi
|
|
go test ./internal/fullsync/...
|
|
go test ./internal/fullsync/httpapi -cover
|
|
go vet ./internal/fullsync/...
|
|
```
|
|
|
|
Tests use contract mocks and local HTTP listeners, requiring no Redis or archive
|
|
files. `go test -race ./internal/fullsync/...` additionally requires a supported
|
|
C compiler and CGO enabled.
|