Skip to content

feat(workloadmonitor): bucket metrics endpoint override - #3443

Merged
Timofei Larkin (lllamnyp) merged 1 commit into
mainfrom
feat/seaweedfs-metrics-endpoint-override
Jul 24, 2026
Merged

Timofei Larkin (lllamnyp) merged 1 commit into
mainfrom
feat/seaweedfs-metrics-endpoint-override

Conversation

@lllamnyp

@lllamnyp Timofei Larkin (lllamnyp) commented Jul 23, 2026 •

Copy link
Copy Markdown
Member

What this PR does

The cozystack-controller meters S3 bucket sizes for billing by querying SeaweedFS SeaweedFS_s3_bucket_(size|physical_size)_bytes metrics from a VictoriaMetrics vmselect it locates via the namespace.cozystack.io/monitoring label on the tenant namespace. That discovery hardcodes the assumption that the monitoring stack scraping SeaweedFS runs in the same cluster. Some deployments host SeaweedFS — API and metrics both — in a separate cluster, so bucket sizes must be fetched from a remote Prometheus-compatible endpoint that discovery can never produce.

Configuration surface

A single controller flag, --seaweedfs-metrics-endpoint, exposed as a chart value:

cozystackController:
  seaweedfsMetricsEndpoint: "https://vm.example.com/path/to/prometheus"

The value is an arbitrary base URL of a Prometheus-compatible query API — scheme, host, optional port, and an optional path prefix — and the controller appends /api/v1/query to it, normalizing slashes so a trailing slash or a path prefix never produces a doubled or dropped slash. Basic auth can be embedded as URL userinfo (https://user:[email protected]/...); net/http turns it into an Authorization header on every request. TLS uses the system trust roots; a custom CA or bearer-token support would be a follow-up flag if a deployment needs it. The flag is validated and normalized at startup (absolute URL, http/https, no query string or fragment), so a malformed value fails the rollout instead of failing queries at runtime.

Cluster-wide scope is deliberate: where SeaweedFS and its metrics live is a property of the deployment, not of any tenant, and tenant monitoring stacks keep working and keep being discovered via the namespace.cozystack.io/monitoring label for everything else — the flag only redirects the bucket-size queries. Being a deployment argument, it is settable exclusively by platform administrators, so no new writable surface is exposed to tenants. Rejected alternatives: a WorkloadMonitor spec field (bucket monitors are rendered by the tenant-facing bucket chart, so the field would have to travel through tenant-writable values — an SSRF primitive — and it would be near-permanent CRD surface for a deployment-level fact) and per-namespace annotations (per-tenant granularity that nothing needs, plus a second admin-facing configuration mechanism to document and validate).

Backwards compatibility

Strictly opt-in: with the flag unset — the default — resolution walks the exact same label-discovery path and produces the same vmselect URL. No existing deployment changes behaviour.

Billing safety: failures no longer collapse to zero

This also fixes a latent billing bug in the existing path: any metrics query failure (endpoint down, non-200, malformed response) produced an empty result map, and the reconciler overwrote Workload.status.resources with it — silently wiping the recorded bucket sizes, which billing reads as an empty bucket. Now:

  • queryAllBucketMetrics returns errors instead of swallowing them into an empty map, so "the bucket is empty" and "the endpoint could not be queried" are distinguishable.
  • On failure — and per bucket, when a query succeeds but reports no series for a bucket that previously had a size — the reconciler retains the last known s3-storage-bytes and s3-physical-storage-bytes values instead of dropping them.
  • The failure is surfaced three ways: a BucketMetricsUnavailable warning event on the WorkloadMonitor, an error-level log line, and an error returned from Reconcile, which lands in controller-runtime's reconcile error metrics for alerting.

Unit tests cover URL validation and path joining, flag-over-discovery precedence, userinfo-based basic auth, and both retention scenarios (query failure and missing series).

Downstream repositories

Walked the trigger map in docs/agents/contributing.md against the diff (controller Go code plus the cozystack-controller chart value and arg plumbing): none of the listed triggers match — no package added or renamed, no CRD, ApplicationDefinition, or platform values change, no hack/ or layout change.

Release note

feat(workloadmonitor): a new --seaweedfs-metrics-endpoint controller flag (chart value cozystackController.seaweedfsMetricsEndpoint) points bucket size metering at an explicit Prometheus-compatible endpoint for deployments where SeaweedFS and its metrics live in a separate cluster; metrics query failures now retain the last known bucket sizes and surface an event and error instead of silently zeroing billing data

Summary by CodeRabbit

  • New Features
    • Added an optional SeaweedFS metrics endpoint configuration.
    • Metrics endpoint URLs are validated and normalized before use.
    • Added support for endpoint overrides when discovering bucket metrics.
  • Bug Fixes
    • Preserved previously recorded bucket sizes when metrics are unavailable or incomplete.
    • Metrics failures now trigger retries and visible warning events without blocking other reconciliation.
    • Improved handling of invalid or unsuccessful metrics responses.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions github-actions Bot added area/monitoring Issues or PRs related to the monitoring stack (vlogs, vmstack, grafana, workloadmonitor) kind/feature Categorizes issue or PR as related to a new feature labels Jul 23, 2026
@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The controller adds an optional SeaweedFS metrics endpoint override, validates and resolves Prometheus URLs, propagates query failures, records warning events, and preserves previously reported bucket sizes. Helm configuration and tests cover endpoint wiring, URL handling, query errors, and reconciliation behavior.

Changes

SeaweedFS bucket metrics

Layer / File(s) Summary
Endpoint configuration and resolution
cmd/cozystack-controller/main.go, internal/controller/workloadmonitor_controller.go, internal/controller/workloadmonitor_controller_test.go, packages/system/cozystack-controller/...
Adds the CLI and Helm endpoint configuration, validates and normalizes URLs, wires the override into WorkloadMonitorReconciler, and tests override precedence and label-based fallback.
Prometheus query error propagation
internal/controller/workloadmonitor_controller.go, internal/controller/workloadmonitor_controller_test.go
Makes bucket metric queries return errors for request, HTTP, body, JSON, and Prometheus-status failures while preserving successful responses and URL authentication/path behavior.
Reconciliation failure handling and size retention
internal/controller/workloadmonitor_controller.go, internal/controller/workloadmonitor_controller_test.go
Preserves existing workload size quantities, emits warning events, continues bucket reconciliation, returns metric errors, and tests failure and missing-bucket scenarios.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested labels: area/testing

Suggested reviewers: kvaps

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding an override for the bucket metrics endpoint in workload monitoring.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/seaweedfs-metrics-endpoint-override

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size/XL This PR changes 500-999 lines, ignoring generated files label Jul 23, 2026
The controller discovers where to query SeaweedFS bucket size metrics
via the namespace.cozystack.io/monitoring label, which assumes the
monitoring stack that scrapes SeaweedFS runs in the same cluster. Some
deployments host SeaweedFS and its metrics in a separate cluster, so
add a --seaweedfs-metrics-endpoint flag to cozystack-controller (and a
matching cozystackController.seaweedfsMetricsEndpoint chart value): an
explicit base URL of a Prometheus-compatible query API, with scheme,
host, optional port and path prefix; /api/v1/query is appended. Basic
auth may be embedded as URL userinfo. The flag is validated and
normalized at startup, so a malformed URL fails the rollout rather
than the queries. Empty keeps label-based discovery unchanged.

Bucket sizes feed billing, so metrics failures no longer collapse to
"no size": previously any query failure yielded an empty result map
and the reconciler overwrote Workload resources with it, silently
wiping the recorded sizes. The reconciler now retains the last known
s3-storage-bytes and s3-physical-storage-bytes values, emits a
BucketMetricsUnavailable warning event on the WorkloadMonitor, and
returns the error from Reconcile so it lands in the reconcile error
metrics.

Assisted-By: Claude <[email protected]>
Signed-off-by: Timofei Larkin <[email protected]>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmd/cozystack-controller/main.go (1)

110-113: 🔒 Security & Privacy | 🔵 Trivial

Credentials embedded as userinfo will be exposed in the pod spec and process args.

Supporting basic-auth via https://user:pass@host/... means the secret is passed as a plaintext CLI argument. It will be visible in the rendered Deployment (kubectl get deploy -o yaml), in cozystackController.seaweedfsMetricsEndpoint (packages/system/cozystack-controller/values.yaml, often committed), and in the container's /proc/1/cmdline. Consider sourcing credentials from a Secret (env/file) or documenting this exposure so operators can prefer network-level auth (mTLS/NetworkPolicy) where possible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/cozystack-controller/main.go` around lines 110 - 113, Remove the promise
of embedded basic-auth credentials from the seaweedfs-metrics-endpoint flag help
text and avoid treating userinfo credentials as a supported authentication
mechanism in the related endpoint configuration. Preserve URL support for
credential-free endpoints, and direct operators toward Secret-based credentials
or network-level authentication instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/cozystack-controller/main.go`:
- Around line 110-113: Remove the promise of embedded basic-auth credentials
from the seaweedfs-metrics-endpoint flag help text and avoid treating userinfo
credentials as a supported authentication mechanism in the related endpoint
configuration. Preserve URL support for credential-free endpoints, and direct
operators toward Secret-based credentials or network-level authentication
instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ebfe3c93-c827-49c7-9939-c7dac74cbbe9

📥 Commits

Reviewing files that changed from the base of the PR and between 2823b76 and 95e4faf.

📒 Files selected for processing (5)
  • cmd/cozystack-controller/main.go
  • internal/controller/workloadmonitor_controller.go
  • internal/controller/workloadmonitor_controller_test.go
  • packages/system/cozystack-controller/templates/deployment.yaml
  • packages/system/cozystack-controller/values.yaml

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

LGTM with non-blocking notes — the change is opt-in and backwards-compatible, the billing-safety fix is real and protected by non-vacuous tests, and the one behavioural trade-off worth naming is a deliberate design choice, not a regression.

Findings

No blocking findings. What was verified (executed, not reasoned):

  • Build/vet/tests: go build ./cmd/cozystack-controller/... ./internal/controller/... and go vet ./internal/controller/... clean; go test ./internal/controller/... for the touched tests passes; gofmt -l clean on all three Go files; chart helm unittest passes.
  • Non-vacuity (Phase 5d): reverted the retention-seeding loop in reconcileBucketClaimForMonitor to its pre-fix shape and re-ran — TestReconcileRetainsLastKnownSizesOnQueryFailure and TestReconcileRetainsLastKnownSizesWhenBucketMissingFromResult both went RED (got 0 (present=false)), so the regression tests genuinely guard the fix.
  • Chart matrix (Phase 5c): rendered both corners of the new {{- with .Values.cozystackController.seaweedfsMetricsEndpoint }} toggle. Default (empty) omits the arg; set value emits - --seaweedfs-metrics-endpoint=.... No dot-scope bug. This system chart has no values.schema.json/README.md and no ApplicationDefinition, so the make generate convention does not apply.

Claim mismatches

None. Spot-checked the load-bearing self-claims and each held under independent re-derivation:

  • "No existing deployment changes behaviour" — confirmed: resolvePrometheusURL returns the override early only when the field is non-empty; the default empty string falls through to the identical label-discovery path.
  • "Failures no longer collapse to zero" — confirmed: queryAllBucketMetrics now returns errors, and reconcileBucketClaimForMonitor seeds resources from the persisted workload.Status.Resources before overlaying fresh metrics. Note Workload has NO +kubebuilder:subresource:status marker and its CRD ships subresources: {} (api/v1alpha1/workload_types.go:52, packages/system/cozystack-controller/definitions/cozystack.io_workloads.yaml:88), so .status is a regular field that ctrl.CreateOrUpdate's plain Update persists — the retention actually works in production, and the tests' non-modelling of a Workload status subresource matches reality.
  • "Settable exclusively by platform administrators, no new tenant surface" — confirmed: plumbed only as a controller CLI arg via values.yaml + deployment.yaml; the reconciler field is set once at startup and never mutated. The rejected WorkloadMonitor-spec-field alternative (SSRF via tenant-writable values) is correctly reasoned.

Caveats

  • Success-with-missing-series retention (billing accuracy, could-not-verify): reconcileBucketClaimForMonitor retains the last known size both when the query FAILS (unambiguously correct) and when the query SUCCEEDS but returns no series for a bucket that previously had a size (internal/controller/workloadmonitor_controller.go:328-355). The old code dropped the size to nothing in the success-missing-series case. If SeaweedFS reports 0 for an existing-but-empty bucket, the new code updates to 0 correctly; if SeaweedFS instead DROPS the series for an empty bucket, retention would keep a stale non-zero size and over-bill. I could not verify SeaweedFS's empty-bucket export behaviour hermetically (no live cluster; Execution boundary). Given the fix's billing-safety intent, retention is a reasonable default, but the two cases are conflated deliberately.
  • Credential-leak concern refuted: the endpoint may embed basic-auth userinfo, and the failure path emits a Warning Event on the WorkloadMonitor, which lives in a TENANT namespace (tenant-readable). I reproduced the transport-error path locally — net/http redacts the password to *** in the *url.Error it returns from Client.Do, so the Event/log carry http://billing:***@.../api/v1/query, not the cleartext secret. The non-200 / JSON-parse / status-not-success error branches carry no URL. No tenant-facing credential leak. (The startup validation error setupLog.Error(err, "invalid --seaweedfs-metrics-endpoint") can log the raw admin-supplied string with credentials, but that is the admin's own value in the admin-only controller log — not flagged.)
  • RBAC: the new +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch marker is already satisfied by the shipped ClusterRole cozystack-controller (packages/system/cozystack-controller/templates/rbac.yaml:44-45, cluster-scoped events: [create, patch]), so the new Recorder works in tenant namespaces. No RBAC gap.
  • Upgrade impact (Phase 5b.A): controller-code-only change, no migration/CRD/default-flip. On upgrade the new binary rolls out; empty flag = prior behaviour; existing Workloads' sizes are retained on the first post-upgrade reconcile. On persistent metrics failure the requeue cadence changes from the fixed 60s to controller-runtime rate-limited backoff (returns the error) — intended, bounded, and events are deduplicated by the recorder, so no event flood.
  • Modules synth-ready-contract, patch-helper-mutation, annotation-state-ha were loaded but are not applicable: no status-condition synthesis, no client.Patch(..., MergeFrom(...)), no annotation-based reconcile state in this diff.

Recommended follow-ups

  • Verify SeaweedFS's SeaweedFS_s3_bucket_size_bytes behaviour for an existing empty bucket (reports 0 vs drops the series). If it can drop the series, consider distinguishing "query failed -> retain" from "query succeeded, series absent -> age-out/zero" so a genuinely-emptied bucket is not over-billed indefinitely.
  • Add a test asserting the BucketMetricsUnavailable Warning Event is actually recorded (e.g. via record.NewFakeRecorder), and cover the non-NotFound namespace-read error branch in resolvePrometheusURL — both are currently unexercised.

@lllamnyp
Timofei Larkin (lllamnyp) merged commit 436743e into main Jul 24, 2026
43 checks passed
@lllamnyp
Timofei Larkin (lllamnyp) deleted the feat/seaweedfs-metrics-endpoint-override branch July 24, 2026 13:25
myasnikovdaniil added a commit that referenced this pull request Sep 3, 2026
…port release-1.6] (#3558)

## What this PR does

Hand-backport of #3443 to `release-1.6`. The change merged to `main` on
2026-07-24, after v1.6.0 was cut, so it never reached any 1.6 patch and
is currently slated to wait for 1.7. This lands it in 1.6.2.

The cozystack-controller meters S3 bucket sizes for billing by querying
SeaweedFS metrics from a VictoriaMetrics vmselect it discovers via the
`namespace.cozystack.io/monitoring` label on the tenant namespace. That
discovery assumes the monitoring stack scraping SeaweedFS runs in the
same cluster. Some deployments host SeaweedFS (API and metrics both) in
a separate cluster, where discovery can never produce a reachable
endpoint. This adds a single controller flag,
`--seaweedfs-metrics-endpoint`, exposed as the chart value
`cozystackController.seaweedfsMetricsEndpoint`, to point bucket-size
metrics at a remote Prometheus-compatible query API. Empty keeps the
existing label-based discovery, so behaviour is unchanged by default.

### Backport adaptations

Clean cherry-pick of `95e4faf68`; no adaptations required. `go build
./cmd/...` and the full `internal/controller` test suite pass.

### Screenshots

Not applicable — no UI change.

### Downstream repositories

- [x] No downstream repository is affected by this change

### Release note

```release-note
feat(workloadmonitor): add --seaweedfs-metrics-endpoint to fetch S3 bucket size metrics from a remote Prometheus-compatible endpoint, for deployments where SeaweedFS and its monitoring stack run in a separate cluster
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/monitoring Issues or PRs related to the monitoring stack (vlogs, vmstack, grafana, workloadmonitor) kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants