Skip to content

feat(queryapi): serve metric queries with server-enforced project isolation - #208

Merged
0xmc merged 9 commits into
mainfrom
feat/queryapi-prometheus-backend
Oct 15, 2026
Merged

0xmc merged 9 commits into
mainfrom
feat/queryapi-prometheus-backend

Conversation

@0xmc

@0xmc 0xmc commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Closes #207.

queryapi now serves its five Prometheus-shaped metric routes instead of returning 501. Every metric query is confined to the caller's project by the server, with the project taken only from the verified identity, the same rule the logs path follows.

How tenancy is enforced

  • storage.MetricStore sits alongside LogStore. The prometheus backend is a narrow net/http client for any Prometheus-query-API server, not a reverse proxy.
  • Each backend request is built from typed fields and carries extra_label=<project label>=<project>. Client parameters are never copied, so a caller can't add, replace, or remove the filter. A request with no project sends nothing.
  • extra_label is a VictoriaMetrics extension, and servers that don't implement it ignore it. So tenancy is an explicit, required flag (--prometheus-tenancy=extra-label) rather than something implied by the backend URL. Startup fails without it.

Proof it refuses

task test-integration-prometheus seeds two projects into VictoriaMetrics v1.114.0 and, as one project, checks query, query_range, series, labels, and label/{name}/values, with the result cache on. Every negative check is paired with a positive one.

With the extra_label line removed, every endpoint's isolation check fails (the other project's series, labels, and values come back, and sum returns both projects' total). With it restored, all pass. The task fails unless the test reports a pass, so a skipped or renamed test can't go green. CI runs it as Test (queryapi integration-prometheus).

Other changes

  • Prometheus HTTP API parameters: fractional Unix seconds, float-second steps, --max-range, the 11,000-point limit, a timeout capped at --query-timeout, and the Prometheus error envelope. Backend failures are logged, not returned.
  • VictoriaMetrics returns query timeouts as HTTP 422 with a numeric errorType; they now surface as 503 timeout.
  • /readyz gains metrics-storage. The logs check is renamed storage → logs-storage to match, which changes /readyz?verbose output.
  • --logs-storage replaces --storage, which stays as a deprecated alias for one release. The deployment keeps the STORAGE env var, so overlays need no edit.
  • New flags: --metrics-storage (default fake), --prometheus-url, --prometheus-tenancy, --prometheus-project-label, --max-range, --max-response-bytes.
  • OpenAPI updated to 0.4.0 for real PromQL responses.

Rollout

Merging changes nothing in-cluster: --metrics-storage defaults to fake. Pointing queryapi at VictoriaMetrics, moving cloud-portal over, and closing the direct path to VictoriaMetrics happen in infra and cloud-portal.

Follow-ups

  • Tighten the timeout match to VictoriaMetrics's message prefix, so a malformed query that echoes "timeout exceeded" reports bad_data.
  • PromQL parse-and-rewrite as a second tenancy mode.

🤖 Generated with Claude Code

0xmc and others added 9 commits October 15, 2026 14:20
--logs-storage names the log backend now that a metric backend is
coming. --storage stays for one release as a deprecated alias, and
startup fails if both are set to different values. The deployment
keeps the STORAGE env var, so overlays need no edit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MetricStore is the Prometheus query API counterpart to LogStore. Each
method returns the backend's data payload unchanged and must refuse a
context with no project before contacting the backend. The fake
answers every query with one series for the caller's project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Prometheus clients send fractional Unix seconds and steps as bare
seconds, which the Loki parser misreads. Timestamps parse exactly to
the nanosecond without a float. Durations must be positive, so a step
that rounds to zero or overflows is rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The five metric routes stop returning 501. Handlers validate
Prometheus API parameters, bound query_range by --max-range and
11,000 points per series, pass the backend's data through unchanged,
and map failures to the Prometheus error envelope. /readyz gains a
metrics storage check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The store builds every backend request from typed fields and adds
extra_label=<project label>=<project>, taking the project only from
the verified identity. Client parameters are never copied, so a
caller cannot add or replace the tenancy filter.

extra_label is a VictoriaMetrics extension that other servers ignore,
so tenancy is an explicit mode rather than implied by the backend URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--metrics-storage=prometheus needs --prometheus-url and
--prometheus-tenancy=extra-label. The tenancy flag has no default, so
isolation is never enabled or lost by accident; startup fails without
it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seeds two projects into a real VictoriaMetrics and asserts that each
endpoint returns none of the other project's series, labels or values,
pairing every negative check with a positive one. With extra_label
removed, every endpoint's check fails.

The task fails unless the test reports a pass, so a skipped or renamed
test cannot pass silently, and CI runs it on every pull request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VictoriaMetrics returns a query timeout as HTTP 422 with a numeric
errorType, so it surfaced as a 400 bad_data. Timeout-shaped errors on
422 or 503 now map to 503 timeout.

The integration test now runs with the result cache on and checks
that one project's cached results never reach another.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Store and Metrics read as different kinds of thing. Fields, test
helpers and the fake stores now say LogStore and MetricStore, and the
logs readiness check is logs-storage to match metrics-storage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@0xmc
0xmc requested a review from a team as a code owner October 15, 2026 18:29
@0xmc
0xmc merged commit 694f425 into main Oct 15, 2026
29 checks passed
@0xmc
0xmc deleted the feat/queryapi-prometheus-backend branch October 15, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

queryapi: serve metric queries with server-enforced project isolation

2 participants