feat(workloadmonitor): bucket metrics endpoint override - #3443
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
📝 WalkthroughWalkthroughThe 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. ChangesSeaweedFS bucket metrics
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
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]>
68aa5ce to
95e4faf
Compare
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
cmd/cozystack-controller/main.go (1)
110-113: 🔒 Security & Privacy | 🔵 TrivialCredentials 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 renderedDeployment(kubectl get deploy -o yaml), incozystackController.seaweedfsMetricsEndpoint(packages/system/cozystack-controller/values.yaml, often committed), and in the container's/proc/1/cmdline. Consider sourcing credentials from aSecret(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
📒 Files selected for processing (5)
cmd/cozystack-controller/main.gointernal/controller/workloadmonitor_controller.gointernal/controller/workloadmonitor_controller_test.gopackages/system/cozystack-controller/templates/deployment.yamlpackages/system/cozystack-controller/values.yaml
IvanHunters
left a comment
There was a problem hiding this comment.
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/...andgo vet ./internal/controller/...clean;go test ./internal/controller/...for the touched tests passes;gofmt -lclean on all three Go files; charthelm unittestpasses. - Non-vacuity (Phase 5d): reverted the retention-seeding loop in
reconcileBucketClaimForMonitorto its pre-fix shape and re-ran —TestReconcileRetainsLastKnownSizesOnQueryFailureandTestReconcileRetainsLastKnownSizesWhenBucketMissingFromResultboth 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 novalues.schema.json/README.mdand noApplicationDefinition, so themake generateconvention 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:
resolvePrometheusURLreturns 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:
queryAllBucketMetricsnow returns errors, andreconcileBucketClaimForMonitorseedsresourcesfrom the persistedworkload.Status.Resourcesbefore overlaying fresh metrics. NoteWorkloadhas NO+kubebuilder:subresource:statusmarker and its CRD shipssubresources: {}(api/v1alpha1/workload_types.go:52,packages/system/cozystack-controller/definitions/cozystack.io_workloads.yaml:88), so.statusis a regular field thatctrl.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):
reconcileBucketClaimForMonitorretains 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 reports0for 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/httpredacts the password to***in the*url.Errorit returns fromClient.Do, so the Event/log carryhttp://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 errorsetupLog.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;patchmarker is already satisfied by the shipped ClusterRolecozystack-controller(packages/system/cozystack-controller/templates/rbac.yaml:44-45, cluster-scopedevents: [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-hawere loaded but are not applicable: no status-condition synthesis, noclient.Patch(..., MergeFrom(...)), no annotation-based reconcile state in this diff.
Recommended follow-ups
- Verify SeaweedFS's
SeaweedFS_s3_bucket_size_bytesbehaviour for an existing empty bucket (reports0vs 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
BucketMetricsUnavailableWarning Event is actually recorded (e.g. viarecord.NewFakeRecorder), and cover the non-NotFoundnamespace-read error branch inresolvePrometheusURL— both are currently unexercised.
…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 ```
What this PR does
The cozystack-controller meters S3 bucket sizes for billing by querying SeaweedFS
SeaweedFS_s3_bucket_(size|physical_size)_bytesmetrics from a VictoriaMetrics vmselect it locates via thenamespace.cozystack.io/monitoringlabel 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: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/queryto 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 anAuthorizationheader 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/monitoringlabel 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.resourceswith it — silently wiping the recorded bucket sizes, which billing reads as an empty bucket. Now:queryAllBucketMetricsreturns errors instead of swallowing them into an empty map, so "the bucket is empty" and "the endpoint could not be queried" are distinguishable.s3-storage-bytesands3-physical-storage-bytesvalues instead of dropping them.BucketMetricsUnavailablewarning event on the WorkloadMonitor, an error-level log line, and an error returned fromReconcile, 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.mdagainst 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, nohack/or layout change.Release note
Summary by CodeRabbit