Rework Ceph collector as a Prometheus complement - #23357
Conversation
|
There was a problem hiding this comment.
8 issues found across 31 files
Confidence score: 3/5
- In
src/go/plugin/go.d/collector/ceph/collect_osd.go, pagination can run indefinitely whenX-Total-Countis missing and pages repeat, which can stall the OSD run and block later collection cycles for the job — add a hard pagination bound and/or repeated-page detection to guarantee termination. - In
src/go/plugin/go.d/collector/ceph/collect_pools.go, pool identity/state reuse can apply the wrong synthetic overflow/full chart set to a pool namedother(or vice versa), leading to misleading pool metrics over time — track role transitions explicitly or replace entity charts when the role changes. - In
src/go/plugin/go.d/collector/ceph/charts.go, a startup-time Dashboard outage can skip creatingceph.dashboard_api_status, so the API-unavailable alert cannot trigger until a later successful probe — register the status chart up front with a default unavailable state. - In
src/go/plugin/go.d/collector/ceph/cephfunc/router.goandsrc/go/plugin/go.d/collector/ceph/func_deps.go, function exposure/snapshot consistency can drift (disabled RGW methods still shown, OSD inventory can mix across paginated changes), causing confusing 404s and inconsistent function outputs — gate published methods by config and preserve a stable pagination snapshot/total across a run.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/go/plugin/go.d/collector/ceph/collect_pools.go">
<violation number="1" location="src/go/plugin/go.d/collector/ceph/collect_pools.go:81">
P2: A pool named `other` can reuse a prior synthetic overflow state (or vice versa) across polls, leaving it with the wrong chart set. Track overflow/full state or replace the existing entity charts when that role changes; otherwise a newly selected real `other` emits dimensions with no charts, while a new overflow may retain full charts.</violation>
</file>
<file name="src/go/plugin/go.d/collector/ceph/charts.go">
<violation number="1" location="src/go/plugin/go.d/collector/ceph/charts.go:54">
P2: A Dashboard outage on a fresh collector startup produces no `ceph.dashboard_api_status` chart, so the default API-unavailable alert cannot fire until one successful identity probe has already occurred. Consider registering an unlabeled/status chart before the identity probe, or otherwise creating it on the failure path without consuming the later FSID-labeled cluster-chart initialization.</violation>
</file>
<file name="src/go/plugin/go.d/collector/ceph/metadata.yaml">
<violation number="1" location="src/go/plugin/go.d/collector/ceph/metadata.yaml:844">
P3: The Function UI columns document OSD and RGW quota `utilization` with `unit: percent`, but the runtime `cephfunc` schema registers these columns with unit `%`. Keep the metadata and the emitted Function schema consistent so the documented column type/unit matches what operators actually see in the Function table.</violation>
</file>
<file name="src/go/plugin/go.d/collector/ceph/collect_osd.go">
<violation number="1" location="src/go/plugin/go.d/collector/ceph/collect_osd.go:116">
P2: A Dashboard response that omits `X-Total-Count` and ignores pagination can make an OSD collection run forever, preventing subsequent collection cycles. Consider detecting repeated page identities (or otherwise bounding headerless pagination) and return an error before continuing.</violation>
</file>
<file name="src/go/plugin/go.d/collector/ceph/cephfunc/router.go">
<violation number="1" location="src/go/plugin/go.d/collector/ceph/cephfunc/router.go:28">
P2: Disabled RGW Functions remain published in the Function UI and fail only after invocation with 404. Bind each method's availability to the job configuration (or avoid publishing disabled methods) so opt-in methods are not advertised before enabled.</violation>
</file>
<file name="src/go/plugin/go.d/collector/ceph/testdata/v20.2.2/dashboard_contract.json">
<violation number="1" location="src/go/plugin/go.d/collector/ceph/testdata/v20.2.2/dashboard_contract.json:244">
P3: The RGW account fixture's fields don't line up with what the collector extracts for an account quota row: the fixture provides `id`/`name`, but lookupRGWQuota reads `account_id`/`account` for the Account attribution and `stats` for usage. As a result the contract test's account target never exercises those fields (Account comes out empty, no usage), so the fixture doesn't validate the account contract it's meant to guard. Consider aligning the fixture's key names/usage block with the fields the parser reads, or the mismatch stays invisible to the test.</violation>
</file>
<file name="src/go/plugin/go.d/collector/ceph/func_deps.go">
<violation number="1" location="src/go/plugin/go.d/collector/ceph/func_deps.go:163">
P2: OSD Function results can represent a mixed inventory snapshot when OSD membership changes during pagination: a later `X-Total-Count` overwrites the earlier value and rows may have been skipped or shifted. Preserve the first advertised total and reject/retry when a later page differs, matching `fetchAllOSDs`.</violation>
</file>
<file name="src/go/plugin/go.d/collector/ceph/client.go">
<violation number="1" location="src/go/plugin/go.d/collector/ceph/client.go:1">
P3: The set of headers that the Ceph client forbids (authorization, cookie, host) is now enforced in two separate places: newCephClient in client.go and validateConfig in init.go. Keeping the same rule duplicated in two layers makes it easy for the two copies to drift (e.g., adding a new managed header in one place but not the other), and one of them is functionally redundant since newCephClient is always constructed after validation. Consider keeping the single source of truth in validateConfig (or in the client) and having the other path rely on it.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Agent as Netdata Agent Runtime
participant Coll as Ceph Collector
participant Client as Ceph API Client
participant Standby as Standby MGR Dashboard
participant Active as Active MGR Dashboard
participant Router as Ceph Function Router
participant UI as Netdata UI - Function Caller
Note over Agent, UI: Ceph Prometheus producers and per-node ceph-exporter remain responsible for continuous telemetry. This Dashboard job is complementary and opt-in by default.
Agent->>Coll: Periodic Collect/Check
Coll->>Client: Ensure cluster identity (GET /api/health/get_cluster_fsid)
Client->>Client: Cached active MGR base + JWT
opt No cached active base - NEW secure discovery
Client->>Standby: Discovery probe (no Authorization/Cookie)
Standby-->>Client: 303 redirect to active MGR
Client->>Client: Validate origin in allowed_redirect_origins
alt Untrusted origin or not_follow_redirects
Client-->>Coll: Fail without forwarding credentials
else Trusted origin
Client->>Active: Discovery probe (no credentials)
Active-->>Client: 401
Client->>Active: POST /api/auth (JSON username/password)
Active-->>Client: JWT
end
end
Client->>Active: GET FSID with Bearer JWT
Active-->>Client: fsid
Client-->>Coll: active MGR base + fsid
Coll->>Coll: Publish dashboard_api_status chart with fsid label
opt Optional legacy features enabled via collect.*
Coll->>Client: Feature-gated Dashboard requests (health/minimal, paginated OSDs, pool stats)
Client->>Active: Bearer JWT requests
Active-->>Client: Response data
Client-->>Coll: Metric sections
end
UI->>Agent: Invoke on-demand Function
Agent->>Router: Handle(method, params)
Router->>Router: Check method enabled, limit, timeout
alt Disabled method (RGW functions default off)
Router-->>UI: 404 NotFound
else Enabled method
Router->>Client: Bounded Dashboard queries for requested rows
Client->>Active: Bearer JWT
Active-->>Client: Row data
Router->>Router: Sort, cap, truncate to limit
Router-->>UI: Table Function response
end
Note over Client, Active: Runtime failover: 401 invalidates JWT (relogin once). Redirect invalidates active base and rediscovers.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| seen := make(map[string]bool, len(selected)) | ||
| for _, sample := range selected { | ||
| seen[sample.key] = true | ||
| if _, ok := c.seenPools[sample.key]; !ok { |
There was a problem hiding this comment.
P2: A pool named other can reuse a prior synthetic overflow state (or vice versa) across polls, leaving it with the wrong chart set. Track overflow/full state or replace the existing entity charts when that role changes; otherwise a newly selected real other emits dimensions with no charts, while a new overflow may retain full charts.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/ceph/collect_pools.go, line 81:
<comment>A pool named `other` can reuse a prior synthetic overflow state (or vice versa) across polls, leaving it with the wrong chart set. Track overflow/full state or replace the existing entity charts when that role changes; otherwise a newly selected real `other` emits dimensions with no charts, while a new overflow may retain full charts.</comment>
<file context>
@@ -3,56 +3,157 @@
+ seen := make(map[string]bool, len(selected))
+ for _, sample := range selected {
+ seen[sample.key] = true
+ if _, ok := c.seenPools[sample.key]; !ok {
+ c.seenPools[sample.key] = &entityState{}
+ c.addPoolCharts(sample.key, !sample.overflow)
</file context>
| ) | ||
|
|
||
| var clusterCharts = collectorapi.Charts{ | ||
| dashboardAPIStatusChart.Copy(), |
There was a problem hiding this comment.
P2: A Dashboard outage on a fresh collector startup produces no ceph.dashboard_api_status chart, so the default API-unavailable alert cannot fire until one successful identity probe has already occurred. Consider registering an unlabeled/status chart before the identity probe, or otherwise creating it on the failure path without consuming the later FSID-labeled cluster-chart initialization.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/ceph/charts.go, line 54:
<comment>A Dashboard outage on a fresh collector startup produces no `ceph.dashboard_api_status` chart, so the default API-unavailable alert cannot fire until one successful identity probe has already occurred. Consider registering an unlabeled/status chart before the identity probe, or otherwise creating it on the failure path without consuming the later FSID-labeled cluster-chart initialization.</comment>
<file context>
@@ -48,6 +51,7 @@ const (
)
var clusterCharts = collectorapi.Charts{
+ dashboardAPIStatusChart.Copy(),
clusterStatusChart.Copy(),
clusterHostsCountChart.Copy(),
</file context>
| func (c *Collector) fetchAllOSDs(ctx context.Context) ([]apiOsdResponse, error) { | ||
| var all []apiOsdResponse | ||
| total := -1 | ||
| for offset := 0; ; offset += osdPageSize { |
There was a problem hiding this comment.
P2: A Dashboard response that omits X-Total-Count and ignores pagination can make an OSD collection run forever, preventing subsequent collection cycles. Consider detecting repeated page identities (or otherwise bounding headerless pagination) and return an error before continuing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/ceph/collect_osd.go, line 116:
<comment>A Dashboard response that omits `X-Total-Count` and ignores pagination can make an OSD collection run forever, preventing subsequent collection cycles. Consider detecting repeated page identities (or otherwise bounding headerless pagination) and return an error before continuing.</comment>
<file context>
@@ -3,64 +3,285 @@
+func (c *Collector) fetchAllOSDs(ctx context.Context) ([]apiOsdResponse, error) {
+ var all []apiOsdResponse
+ total := -1
+ for offset := 0; ; offset += osdPageSize {
+ query := url.Values{
+ "offset": {strconv.Itoa(offset)},
</file context>
| return &router{deps: deps, config: config} | ||
| } | ||
|
|
||
| func Methods() []funcapi.FunctionConfig { |
There was a problem hiding this comment.
P2: Disabled RGW Functions remain published in the Function UI and fail only after invocation with 404. Bind each method's availability to the job configuration (or avoid publishing disabled methods) so opt-in methods are not advertised before enabled.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/ceph/cephfunc/router.go, line 28:
<comment>Disabled RGW Functions remain published in the Function UI and fail only after invocation with 404. Bind each method's availability to the job configuration (or avoid publishing disabled methods) so opt-in methods are not advertised before enabled.</comment>
<file context>
@@ -0,0 +1,428 @@
+ return &router{deps: deps, config: config}
+}
+
+func Methods() []funcapi.FunctionConfig {
+ return []funcapi.FunctionConfig{
+ {ID: MethodHealth, Name: "Ceph Health", UpdateEvery: 10, Help: "Detailed Ceph health checks and RCA messages."},
</file context>
| if err != nil || parsed < 0 { | ||
| return cephfunc.OSDResult{}, errors.New("OSD inventory returned an invalid X-Total-Count") | ||
| } | ||
| total = parsed |
There was a problem hiding this comment.
P2: OSD Function results can represent a mixed inventory snapshot when OSD membership changes during pagination: a later X-Total-Count overwrites the earlier value and rows may have been skipped or shifted. Preserve the first advertised total and reject/retry when a later page differs, matching fetchAllOSDs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/ceph/func_deps.go, line 163:
<comment>OSD Function results can represent a mixed inventory snapshot when OSD membership changes during pagination: a later `X-Total-Count` overwrites the earlier value and rows may have been skipped or shifted. Preserve the first advertised total and reject/retry when a later page differs, matching `fetchAllOSDs`.</comment>
<file context>
@@ -0,0 +1,962 @@
+ if err != nil || parsed < 0 {
+ return cephfunc.OSDResult{}, errors.New("OSD inventory returned an invalid X-Total-Count")
+ }
+ total = parsed
+ }
+ pageLength := len(page)
</file context>
| - {name: total_bytes, type: integer, unit: bytes, visibility: hidden, description: Total OSD capacity.} | ||
| - {name: used_bytes, type: integer, unit: bytes, description: Used OSD capacity.} | ||
| - {name: available_bytes, type: integer, unit: bytes, description: Available OSD capacity.} | ||
| - {name: utilization, type: float, unit: percent, description: OSD utilization.} |
There was a problem hiding this comment.
P3: The Function UI columns document OSD and RGW quota utilization with unit: percent, but the runtime cephfunc schema registers these columns with unit %. Keep the metadata and the emitted Function schema consistent so the documented column type/unit matches what operators actually see in the Function table.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/ceph/metadata.yaml, line 844:
<comment>The Function UI columns document OSD and RGW quota `utilization` with `unit: percent`, but the runtime `cephfunc` schema registers these columns with unit `%`. Keep the metadata and the emitted Function schema consistent so the documented column type/unit matches what operators actually see in the Function table.</comment>
<file context>
@@ -211,218 +595,394 @@ modules:
+ - {name: total_bytes, type: integer, unit: bytes, visibility: hidden, description: Total OSD capacity.}
+ - {name: used_bytes, type: integer, unit: bytes, description: Used OSD capacity.}
+ - {name: available_bytes, type: integer, unit: bytes, description: Available OSD capacity.}
+ - {name: utilization, type: float, unit: percent, description: OSD utilization.}
+ - {name: read_bytes_per_sec, type: float, unit: bytes/s, visibility: hidden, description: Current read rate.}
+ - {name: write_bytes_per_sec, type: float, unit: bytes/s, visibility: hidden, description: Current write rate.}
</file context>
| "max_objects": 100, | ||
| "max_size": 1000 | ||
| }, | ||
| "id": "RGW00000000000000001", |
There was a problem hiding this comment.
P3: The RGW account fixture's fields don't line up with what the collector extracts for an account quota row: the fixture provides id/name, but lookupRGWQuota reads account_id/account for the Account attribution and stats for usage. As a result the contract test's account target never exercises those fields (Account comes out empty, no usage), so the fixture doesn't validate the account contract it's meant to guard. Consider aligning the fixture's key names/usage block with the fields the parser reads, or the mismatch stays invisible to the test.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/ceph/testdata/v20.2.2/dashboard_contract.json, line 244:
<comment>The RGW account fixture's fields don't line up with what the collector extracts for an account quota row: the fixture provides `id`/`name`, but lookupRGWQuota reads `account_id`/`account` for the Account attribution and `stats` for usage. As a result the contract test's account target never exercises those fields (Account comes out empty, no usage), so the fixture doesn't validate the account contract it's meant to guard. Consider aligning the fixture's key names/usage block with the fields the parser reads, or the mismatch stays invisible to the test.</comment>
<file context>
@@ -0,0 +1,348 @@
+ "max_objects": 100,
+ "max_size": 1000
+ },
+ "id": "RGW00000000000000001",
+ "name": "account-a",
+ "tenant": "tenant"
</file context>
| @@ -0,0 +1,528 @@ | |||
| // SPDX-License-Identifier: GPL-3.0-or-later | |||
There was a problem hiding this comment.
P3: The set of headers that the Ceph client forbids (authorization, cookie, host) is now enforced in two separate places: newCephClient in client.go and validateConfig in init.go. Keeping the same rule duplicated in two layers makes it easy for the two copies to drift (e.g., adding a new managed header in one place but not the other), and one of them is functionally redundant since newCephClient is always constructed after validation. Consider keeping the single source of truth in validateConfig (or in the client) and having the other path rely on it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/ceph/client.go, line 1:
<comment>The set of headers that the Ceph client forbids (authorization, cookie, host) is now enforced in two separate places: newCephClient in client.go and validateConfig in init.go. Keeping the same rule duplicated in two layers makes it easy for the two copies to drift (e.g., adding a new managed header in one place but not the other), and one of them is functionally redundant since newCephClient is always constructed after validation. Consider keeping the single source of truth in validateConfig (or in the client) and having the other path rely on it.</comment>
<file context>
@@ -0,0 +1,528 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package ceph
</file context>



Summary
Rework the native Ceph Dashboard collector as a complement to Ceph's Prometheus producers rather than a second overlapping continuous telemetry path.
Why
The existing collector predates the current Ceph telemetry architecture. It continuously polls Dashboard endpoints for data now exposed more directly by the MGR Prometheus module and per-node
ceph-exporter, while also containing correctness and compatibility problems on current Ceph releases.Ceph's exporters refresh their own caches independently of scraper cadence. Matching Netdata scrape intervals to those configured producer periods avoids duplicate or skipped cached samples; it does not change producer refresh work. Dashboard REST requests are different because each request performs work in the active MGR, so overlapping or potentially expensive native features are now opt-in.
User-visible changes
collect:section now enable onlycollect.dashboard_api_status.falseand remain individually re-enableable with their shipped chart identities preserved.Distributed deployment model
ceph-exporteronce on each Ceph nodeValidation
go test -race ./plugin/go.d/collector/ceph/... -count=1go vet ./plugin/go.d/collector/ceph/...go test ./plugin/go.d/collector/... -count=1FUNCTION_UI_SCHEMA.json303redirect: 141 metrics across 77 enabled charts in both casesRelated work
Complements #23330. This change does not modify the Prometheus profiles from that pull request.
Summary by cubic
Reworked the
cephgo.dcollector to complement Ceph’s Prometheus producers. Default is a lightweight Dashboard API/auth/active‑MGR status probe with on-demand Functions and hardened active‑MGR discovery, plus OSD/pool correctness fixes.New Features
ceph.dashboard_api_statusis enabled by default.allowed_redirect_origins; no credential forwarding to unknown hosts.Migration
collect:now publish onlydashboard_api_status; enable needed legacy charts undercollect.*.ceph-exporteron each node and themgrPrometheus endpoint once.allowed_redirect_originsfor HA clusters and supplybearer_token_fileorusername/password.Written for commit fee3d22. Summary will update on new commits.