Skip to content

Commit a20798a

Browse files
Copilotlquereljmacd
authored
Prefix all admin API endpoints with /api/v1/ (open-telemetry#2486)
Adds `/api/v1/` versioning prefix to all admin API endpoints to support future API evolution without breaking changes. # Change Summary All API routes are nested under `/api/v1` in a single place (`lib.rs`) using Axum's `Router::nest`. UI routes (`/`, `/dashboard`, `/static/*`) remain at root. ```rust // Before let app = Router::new() .merge(health::routes()) // /status, /livez, /readyz .merge(telemetry::routes()) // /telemetry/metrics, /metrics .merge(pipeline_group::routes()) .merge(pipeline::routes()) .merge(dashboard::routes()); // After let api_routes = Router::new() .merge(health::routes()) // → /api/v1/status, /api/v1/livez, /api/v1/readyz .merge(telemetry::routes()) // → /api/v1/telemetry/metrics, /api/v1/metrics .merge(pipeline_group::routes()) .merge(pipeline::routes()); let app = Router::new() .nest("/api/v1", api_routes) .merge(dashboard::routes()); // unchanged ``` **UI updated:** - `metrics-api.js`: endpoint candidates updated to `/api/v1/telemetry/metrics` and `/api/v1/metrics` - `polling-controller.js`: health/status fetch paths updated to `/api/v1/livez`, `/api/v1/readyz`, `/api/v1/status` **Docs updated:** - `crates/admin/README.md` and `docs/admin/architecture.md` reflect new paths **Validation crate updated:** - `crates/validation/src/simulate.rs`: `wait_for_ready`, `fetch_metrics`, and `shutdown_pipeline` now use `/api/v1/readyz`, `/api/v1/telemetry/metrics`, and `/api/v1/pipeline-groups/shutdown` respectively **Pipeline perf test infrastructure updated:** - All YAML/Jinja2 test suite configs under `tools/pipeline_perf_test/` updated from `/telemetry/metrics` → `/api/v1/telemetry/metrics` and `/pipeline-groups/` → `/api/v1/pipeline-groups/` (14 files across `continuous/`, `nightly/`, and `templates/` directories) ## What issue does this PR close? ## How are these changes tested? All 9 existing admin unit tests pass. The UI polling logic is covered by existing JS module tests. The full `cargo xtask check` was run per AGENTS.md requirements: structure checks ✅, `cargo fmt --all` ✅, `cargo clippy --workspace --all-targets -- -D warnings` ✅. The `otap-df-validation` integration tests (`no_processor`, `debug_processor`, `attribute_processor_pipeline`, `filter_processor_pipeline`) now pass with the updated endpoint paths. ## Are there any user-facing changes? Yes. All admin API endpoints now require the `/api/v1/` prefix: | Old | New | |-----|-----| | `/status` | `/api/v1/status` | | `/livez` | `/api/v1/livez` | | `/readyz` | `/api/v1/readyz` | | `/metrics` | `/api/v1/metrics` | | `/telemetry/metrics` | `/api/v1/telemetry/metrics` | | `/telemetry/metrics/aggregate` | `/api/v1/telemetry/metrics/aggregate` | | `/pipeline-groups/status` | `/api/v1/pipeline-groups/status` | | `/pipeline-groups/shutdown` | `/api/v1/pipeline-groups/shutdown` | | `/pipeline-groups/{g}/pipelines/{p}/status` | `/api/v1/pipeline-groups/{g}/pipelines/{p}/status` | The embedded UI continues to work as its fetch paths are updated in lockstep. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lquerel <657994+lquerel@users.noreply.github.com> Co-authored-by: Laurent Quérel <l.querel@f5.com> Co-authored-by: jmacd <3629705+jmacd@users.noreply.github.com> Co-authored-by: Joshua MacDonald <jmacd@users.noreply.github.com>
1 parent ff90344 commit a20798a

24 files changed

Lines changed: 101 additions & 94 deletions

rust/otap-dataflow/crates/admin/README.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,21 @@ For the admin docs landing page, see
2020

2121
### Telemetry
2222

23-
- `GET /telemetry/live-schema`
24-
- `GET /telemetry/metrics`
25-
- `GET /telemetry/metrics/aggregate`
26-
- `GET /metrics` (alias for `/telemetry/metrics`)
23+
- `GET /api/v1/telemetry/live-schema`
24+
- `GET /api/v1/telemetry/metrics`
25+
- `GET /api/v1/telemetry/metrics/aggregate`
26+
- `GET /api/v1/metrics` (alias for `/api/v1/telemetry/metrics`)
2727

2828
### Health and status
2929

30-
- `GET /status`
31-
- `GET /livez`
32-
- `GET /readyz`
33-
- `GET /pipeline-groups/status`
34-
- `GET /pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/status`
35-
- `GET /pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/livez`
36-
- `GET /pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/readyz`
37-
- `POST /pipeline-groups/shutdown`
30+
- `GET /api/v1/status`
31+
- `GET /api/v1/livez`
32+
- `GET /api/v1/readyz`
33+
- `GET /api/v1/pipeline-groups/status`
34+
- `GET /api/v1/pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/status`
35+
- `GET /api/v1/pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/livez`
36+
- `GET /api/v1/pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/readyz`
37+
- `POST /api/v1/pipeline-groups/shutdown`
3838

3939
## Embedded UI layout (crate-relative)
4040

@@ -54,7 +54,7 @@ Assets are embedded in the binary via `include_dir` and served by
5454
## Runtime notes
5555

5656
- UI polling cadence is 2 seconds.
57-
- UI metrics polling uses same-origin `/telemetry/metrics` then `/metrics`.
57+
- UI metrics polling uses same-origin `/api/v1/telemetry/metrics` then `/api/v1/metrics`.
5858
- Supported optional query param: `keep_all_zeroes=true|false`.
5959

6060
For operational semantics, metric-name contracts, graph rules, and testing
@@ -89,7 +89,8 @@ guidance, see [`docs/admin/architecture.md`](../../docs/admin/architecture.md).
8989
- [ ] Protect `POST /pipeline-groups/shutdown` with stricter access controls
9090
than read-only endpoints.
9191
- [ ] Apply the same hardened response headers to API endpoints
92-
(`/status`, `/livez`, `/readyz`, `/telemetry/*`, `/metrics`), not only UI/static.
92+
(`/api/v1/status`, `/api/v1/livez`, `/api/v1/readyz`,
93+
`/api/v1/telemetry/*`, `/api/v1/metrics`), not only UI/static.
9394
- [ ] Harden CSP further by removing `style-src 'unsafe-inline'` (move toward
9495
nonce/hash-based style policies).
9596
- [ ] Add rate limiting / throttling to protect admin and telemetry endpoints.
@@ -104,4 +105,4 @@ guidance, see [`docs/admin/architecture.md`](../../docs/admin/architecture.md).
104105
- strong authentication/authorization
105106
- network ACLs / source allow-listing
106107
- route-level restrictions for mutating endpoints such as
107-
`/pipeline-groups/shutdown`
108+
`/api/v1/pipeline-groups/shutdown`

rust/otap-dataflow/crates/admin/src/health.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
//! Global health and status endpoints.
55
//!
6-
//! - GET `/status` - list all pipelines and their status
7-
//! - GET `/livez` - liveness probe
8-
//! - GET `/readyz` - readiness probe
6+
//! - GET `/api/v1/status` - list all pipelines and their status
7+
//! - GET `/api/v1/livez` - liveness probe
8+
//! - GET `/api/v1/readyz` - readiness probe
99
1010
use crate::AppState;
1111
use axum::extract::State;

rust/otap-dataflow/crates/admin/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,14 @@ pub async fn run(
5858
ctrl_msg_senders: Arc::new(Mutex::new(ctrl_msg_senders)),
5959
};
6060

61-
let app = Router::new()
61+
let api_routes = Router::new()
6262
.merge(health::routes())
6363
.merge(telemetry::routes())
6464
.merge(pipeline_group::routes())
65-
.merge(pipeline::routes())
65+
.merge(pipeline::routes());
66+
67+
let app = Router::new()
68+
.nest("/api/v1", api_routes)
6669
.merge(dashboard::routes())
6770
.layer(ServiceBuilder::new())
6871
.with_state(app_state);

rust/otap-dataflow/crates/admin/src/pipeline.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
//! Pipeline endpoints.
55
//! Status: Not implemented.
66
//!
7-
//! - GET `/pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}`
7+
//! - GET `/api/v1/pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}`
88
//! Get the configuration of the specified pipeline.
9-
//! - GET `/pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/status`
9+
//! - GET `/api/v1/pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/status`
1010
//! Get the status of the specified pipeline.
11-
//! - POST `/pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/shutdown`
11+
//! - POST `/api/v1/pipeline-groups/{pipeline_group_id}/pipelines/{pipeline_id}/shutdown`
1212
//! Shutdown a specific pipeline
1313
//! - 202 Accepted if the stop request was accepted and is being processed (async operation)
1414
//! - 400 Bad Request if the pipeline is already stopped

rust/otap-dataflow/crates/admin/src/pipeline_group.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33

44
//! Pipeline group endpoints.
55
//!
6-
//! - GET `/pipeline-groups/:id/pipelines` - list active pipelines and their status (ToDo)
7-
//! - POST `/pipeline-groups/shutdown` - shutdown all pipelines in all groups
6+
//! - GET `/api/v1/pipeline-groups/:id/pipelines` - list active pipelines and their status (ToDo)
7+
//! - POST `/api/v1/pipeline-groups/shutdown` - shutdown all pipelines in all groups
88
//! - Query parameters:
99
//! - `wait` (bool, default: false) - if true, block until all pipelines have stopped
1010
//! - `timeout_secs` (u64, default: 60) - maximum seconds to wait when `wait=true`
1111
//!
1212
//! Example (fire-and-forget):
1313
//! ```sh
14-
//! curl -X POST http://localhost:8080/pipeline-groups/shutdown
14+
//! curl -X POST http://localhost:8080/api/v1/pipeline-groups/shutdown
1515
//! ```
1616
//! Example (wait for graceful shutdown with 30s timeout):
1717
//! ```sh
18-
//! curl -X POST "http://localhost:8080/pipeline-groups/shutdown?wait=true&timeout_secs=30"
18+
//! curl -X POST "http://localhost:8080/api/v1/pipeline-groups/shutdown?wait=true&timeout_secs=30"
1919
//! ```
2020
//!
2121
//! - 200 OK if `wait=true` and all pipelines stopped successfully
@@ -113,7 +113,8 @@ async fn shutdown_all_pipelines(
113113
sender
114114
.try_send_shutdown(
115115
deadline,
116-
"Shutdown requested via the `/pipeline-groups/shutdown` endpoint.".to_owned(),
116+
"Shutdown requested via the `/api/v1/pipeline-groups/shutdown` endpoint."
117+
.to_owned(),
117118
)
118119
.err()
119120
})

rust/otap-dataflow/crates/admin/src/telemetry.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
//! Telemetry endpoints.
55
//!
6-
//! - /telemetry/live-schema - current semantic conventions registry
7-
//! - /telemetry/logs - retained internal logs from the in-memory log tap
8-
//! - /telemetry/metrics - current aggregated metrics in JSON, line protocol, or Prometheus text format
9-
//! - /telemetry/metrics/aggregate - aggregated metrics grouped by metric set name and optional attributes
6+
//! - /api/v1/telemetry/live-schema - current semantic conventions registry
7+
//! - /api/v1/telemetry/logs - retained internal logs from the in-memory log tap
8+
//! - /api/v1/telemetry/metrics - current aggregated metrics in JSON, line protocol, or Prometheus text format
9+
//! - /api/v1/telemetry/metrics/aggregate - aggregated metrics grouped by metric set name and optional attributes
1010
1111
use crate::AppState;
1212
use axum::extract::{Query, State};
@@ -94,7 +94,7 @@ pub enum OutputFormat {
9494
Prometheus,
9595
}
9696

97-
/// Query parameters for /telemetry/metrics
97+
/// Query parameters for /api/v1/telemetry/metrics
9898
#[derive(Debug, Default, Deserialize)]
9999
pub struct MetricsQuery {
100100
/// When true, reset metrics after reading. Default: false.
@@ -108,7 +108,7 @@ pub struct MetricsQuery {
108108
keep_all_zeroes: bool,
109109
}
110110

111-
/// Query parameters for /telemetry/metrics/aggregate
111+
/// Query parameters for /api/v1/telemetry/metrics/aggregate
112112
#[derive(Debug, Default, Deserialize)]
113113
pub struct AggregateQuery {
114114
/// When true, reset metrics after reading. Default: false.
@@ -122,7 +122,7 @@ pub struct AggregateQuery {
122122
format: Option<OutputFormat>,
123123
}
124124

125-
/// Query parameters for /telemetry/logs
125+
/// Query parameters for /api/v1/telemetry/logs
126126
#[derive(Debug, Default, Deserialize)]
127127
pub struct LogsQuery {
128128
/// Return logs strictly newer than this sequence number.
@@ -271,7 +271,7 @@ pub async fn get_logs(
271271
Ok(Json(logs_response(&state.metrics_registry, result)))
272272
}
273273

274-
/// Handler for the `/telemetry/metrics` endpoint.
274+
/// Handler for the `/api/v1/telemetry/metrics` endpoint.
275275
/// Supports multiple output formats and optional reset.
276276
///
277277
/// Query parameters:
@@ -346,7 +346,7 @@ pub async fn get_metrics(
346346
}
347347
}
348348

349-
/// Handler for the /telemetry/metrics/aggregate endpoint.
349+
/// Handler for the /api/v1/telemetry/metrics/aggregate endpoint.
350350
/// Aggregates metrics by metric set name and optionally by a list of attributes.
351351
///
352352
/// Query parameters:

rust/otap-dataflow/crates/admin/ui/js/metrics-api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function dedupeUrls(urls) {
1313
// Build same-origin metrics endpoint candidates.
1414
// The embedded UI should read from its own admin origin.
1515
export function buildMetricsCandidates({ query }) {
16-
const localPaths = ["/telemetry/metrics", "/metrics"];
16+
const localPaths = ["/api/v1/telemetry/metrics", "/api/v1/metrics"];
1717
const localUrls = localPaths.map((path) => `${path}?${query}`);
1818
return dedupeUrls(localUrls);
1919
}

rust/otap-dataflow/crates/admin/ui/js/polling-controller.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ export async function runHealthPoll({
236236
const checkedAt = Date.now();
237237
try {
238238
const [livezProbe, readyzProbe] = await Promise.all([
239-
probeHealthEndpoint("/livez", healthRequestTimeoutMs),
240-
probeHealthEndpoint("/readyz", healthRequestTimeoutMs),
239+
probeHealthEndpoint("/api/v1/livez", healthRequestTimeoutMs),
240+
probeHealthEndpoint("/api/v1/readyz", healthRequestTimeoutMs),
241241
]);
242242
livezProbe.checkedAt = checkedAt;
243243
readyzProbe.checkedAt = checkedAt;
@@ -269,7 +269,7 @@ export async function runStatusPoll({
269269
}, statusRequestTimeoutMs);
270270

271271
try {
272-
const response = await fetch("/status", {
272+
const response = await fetch("/api/v1/status", {
273273
method: "GET",
274274
cache: "no-store",
275275
signal: controller.signal,

rust/otap-dataflow/crates/validation/src/simulate.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ async fn wait_for_ready(
7878
max_retry: usize,
7979
retry_cooldown: Duration,
8080
) -> Result<(), ValidationError> {
81-
let readyz_url = format!("{base}/readyz");
81+
let readyz_url = format!("{base}/api/v1/readyz");
8282
let mut last_error: Option<String> = None;
8383
for _attempt in 0..max_retry {
8484
match client.get(&readyz_url).send().await {
@@ -102,15 +102,17 @@ async fn wait_for_ready(
102102

103103
async fn fetch_metrics(client: &Client, base: &str) -> Result<MetricsSnapshot, ValidationError> {
104104
client
105-
.get(format!("{base}/telemetry/metrics"))
105+
.get(format!("{base}/api/v1/telemetry/metrics"))
106106
.query(&[
107107
("reset", "false"),
108108
("keep_all_zeroes", "true"),
109109
("format", "json"),
110110
])
111111
.send()
112112
.await
113-
.map_err(|_| ValidationError::Http(format!("No Response from {base}/telemetry/metrics")))?
113+
.map_err(|_| {
114+
ValidationError::Http(format!("No Response from {base}/api/v1/telemetry/metrics"))
115+
})?
114116
.error_for_status()
115117
.map_err(|e| ValidationError::Http(e.to_string()))?
116118
.json()
@@ -161,7 +163,7 @@ async fn wait_for_validation_finished(
161163
/// shutdown pipeline after running
162164
async fn shutdown_pipeline(client: &Client, base: &str) -> Result<(), ValidationError> {
163165
let _ = client
164-
.post(format!("{base}/pipeline-groups/shutdown"))
166+
.post(format!("{base}/api/v1/pipeline-groups/shutdown"))
165167
.send()
166168
.await
167169
.map_err(|e| ValidationError::Http(e.to_string()))?

rust/otap-dataflow/docs/admin/architecture.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Request flow:
2525

2626
1. `GET /` or `GET /dashboard` serves the embedded UI shell.
2727
2. Browser loads static assets from `/static/*`.
28-
3. UI polls `/telemetry/metrics` (or `/metrics` alias) with:
28+
3. UI polls `/api/v1/telemetry/metrics` (or `/api/v1/metrics` alias) with:
2929
- `format=json`
3030
- `reset=false`
3131
- optional `keep_all_zeroes=true|false`
@@ -34,8 +34,8 @@ Request flow:
3434

3535
When calling endpoints directly outside browser-relative paths, use:
3636

37-
- `http://<admin-host>:<admin-port>/telemetry/metrics`
38-
- `http://<admin-host>:<admin-port>/metrics`
37+
- `http://<admin-host>:<admin-port>/api/v1/telemetry/metrics`
38+
- `http://<admin-host>:<admin-port>/api/v1/metrics`
3939

4040
## Main design principles
4141

@@ -60,8 +60,8 @@ When calling endpoints directly outside browser-relative paths, use:
6060

6161
`metrics-api.js` builds same-origin candidates only:
6262

63-
1. `/telemetry/metrics?...`
64-
2. `/metrics?...`
63+
1. `/api/v1/telemetry/metrics?...`
64+
2. `/api/v1/metrics?...`
6565

6666
`fetchMetricsFromCandidates()` probes candidates in order and caches the first
6767
successful URL for the next cycle.

0 commit comments

Comments
 (0)