feat(plugins.d): add FUNCTION_DEL protocol command#21685
feat(plugins.d): add FUNCTION_DEL protocol command#21685ktsaou wants to merge 5 commits intonetdata:masterfrom
Conversation
…on unregistration Add the FUNCTION_DEL protocol command to allow plugins (especially go.d.plugin) to dynamically unregister functions without restarting. This enables proper cleanup when collectors are disabled via DYNCFG. Changes: - Add `unregistered` flag to rrd_host_function struct - Add centralized `rrd_function_is_available()` helper for all availability checks - Update all 6 export functions to use the availability helper - Add `rrd_function_del()` with ownership validation and streaming support - Add STREAM_CAP_FUNCTION_DEL capability for parent/child negotiation - Add parser handler for FUNCTION_DEL keyword (ID 44, worker job +41) - Stream deletions directly to parent (not via flag mechanism) - Support multi-hop streaming topology - Add `internal` parameter for trusted internal callers (dyncfg, health) Protocol format: FUNCTION_DEL [GLOBAL] "function_name" Behavior: - Functions remain in dictionary but become unavailable via flag - Re-registration clears the flag (unlimited register/unregister cycles) - API returns 503 with "This function has been unregistered by the plugin" - Graceful degradation: old parents without capability don't receive deletions
|
@ilyam8 this needs testing. It touches a lot of things... |
There was a problem hiding this comment.
1 issue found across 17 files
Confidence score: 5/5
- Minor documentation cleanup needed:
src/plugins.d/README.mdhas a duplicateFUNCTION_DELentry in the command list, which could cause small confusion for readers. - Low severity (3/10) and limited to README content, so merge risk is minimal.
- Pay close attention to
src/plugins.d/README.md- remove the duplicate command entry.
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="src/plugins.d/README.md">
<violation number="1" location="src/plugins.d/README.md:129">
P3: Remove the duplicate `FUNCTION_DEL` entry from the command list so each command appears only once.</violation>
</file>
Architecture diagram
sequenceDiagram
participant P as External Plugin (go.d.plugin)
participant C as Netdata Core (Parser/Registry)
participant DB as RRDHOST Functions (Dictionary)
participant S as Stream Sender (Streaming)
participant Parent as Parent Netdata
participant API as API v1 (Functions)
Note over P, Parent: Registration & Unregistration Flow
P->>C: FUNCTION "my_func" ...
C->>DB: rrd_function_add()
DB-->>DB: CHANGED: Reset 'unregistered' flag if exists
rect rgb(23, 37, 84)
Note right of P: Dynamic Cleanup (e.g., via DYNCFG)
P->>C: NEW: FUNCTION_DEL [GLOBAL] "my_func"
C->>DB: NEW: rrd_function_del(name, internal_flag)
alt Ownership Validation
DB->>DB: NEW: Check if current collector == registered collector
else internal_flag == true
DB->>DB: Bypass ownership check (trusted caller)
end
alt Authorized
DB->>DB: NEW: Set 'unregistered' flag = true
C->>S: NEW: stream_send_function_del()
opt Parent Cap: FUNCDEL
S->>Parent: NEW: FUNCTION_DEL GLOBAL "my_func"
end
else Unauthorized (Collector Mismatch)
DB-->>C: Return Error (Log Warning)
end
end
Note over API, DB: Runtime Access Flow
API->>DB: rrd_function_is_available()
alt unregistered == true
DB-->>API: return false
API-->>API: NEW: Return HTTP 503 (Service Unavailable)
else unregistered == false AND collector_running == true
DB-->>API: return true
API->>P: Execute Function
end
Note over API, DB: Discovery Flow
API->>DB: GET /api/v1/functions
DB->>DB: CHANGED: rrd_function_is_available() check during iteration
Note right of DB: NEW: Exporters/JSON filters out 'unregistered' entries
DB-->>API: Return JSON (excluding deleted functions)
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
@ilyam8 btw, I think the go.d.plugin registers functions per module, not per job. This means that this PR is not actually needed, since functions are always registered independently of the jobs. This PR however enables go.d.plugin to improve its framework and register/unregister job-level functions, which will allow each function to have its own required parameters (nothing shared between jobs). |
There was a problem hiding this comment.
Pull request overview
Adds a new FUNCTION_DEL plugins.d/streaming protocol command so plugins can dynamically unregister previously registered functions (without restarting), by marking functions as unavailable and filtering them from exports/listings.
Changes:
- Core: add an
unregisteredflag to function records, introducerrd_function_is_available(), and filter unavailable functions across exporters/lookups. - Protocol: add
FUNCTION_DELkeyword (ID 44) plusSTREAM_CAP_FUNCTION_DEL(“FUNCDEL”) and a sender helper to stream deletions upstream. - Plugins.d: extend parser/keyword tables and documentation to recognize and describe
FUNCTION_DEL.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/streaming/stream-capabilities.h | Adds STREAM_CAP_FUNCTION_DEL capability bit. |
| src/streaming/stream-capabilities.c | Registers “FUNCDEL” capability name and advertises it in our capabilities. |
| src/streaming/protocol/commands.h | Declares stream_send_function_del() API for streaming deletions. |
| src/streaming/protocol/command-function-del.c | Implements sender-side emission of FUNCTION_DEL GLOBAL ... when capability is present. |
| src/plugins.d/pluginsd_parser.c | Routes FUNCTION_DEL keyword to the new handler. |
| src/plugins.d/pluginsd_functions.h | Declares pluginsd_function_del(). |
| src/plugins.d/pluginsd_functions.c | Parses FUNCTION_DEL [GLOBAL] name and calls rrd_function_del(...). |
| src/plugins.d/gperf-hashtable.h | Regenerates keyword hash table and assigns ID 44 to FUNCTION_DEL. |
| src/plugins.d/gperf-config.txt | Adds FUNCTION_DEL keyword definition and mapping for gperf generation. |
| src/plugins.d/README.md | Documents the new FUNCTION_DEL command and its behavior. |
| src/libnetdata/functions_evloop/functions_evloop.h | Adds PLUGINSD_KEYWORD_FUNCTION_DEL macro. |
| src/database/rrdfunctions.h | Changes rrd_function_del() signature and exposes rrd_function_is_available(). |
| src/database/rrdfunctions.c | Implements “unregister” behavior, availability helper, ownership checks, and 503 messaging. |
| src/database/rrdfunctions-internals.h | Extends rrd_host_function with unregistered and declares availability helper. |
| src/database/rrdfunctions-exporters.c | Filters unavailable/unregistered functions from streaming/JSON/dict exporters. |
| src/daemon/dyncfg/dyncfg.c | Updates call site for new rrd_function_del() signature (internal delete). |
| CMakeLists.txt | Adds new command-function-del.c to the build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if(__atomic_load_n(&rdcf->unregistered, __ATOMIC_RELAXED)) { | ||
| __atomic_store_n(&rdcf->unregistered, false, __ATOMIC_RELAXED); | ||
| changed = true; | ||
| } |
There was a problem hiding this comment.
rrd_function_add() computes rrd_host_function.options, but rrd_functions_conflict_callback() never reconciles/updates rdcf->options when a function is re-registered. With the new FUNCTION_DEL behavior (entries stay in host->functions and re-registering will hit the conflict callback), a re-register that changes scope (GLOBAL vs LOCAL) or restricted/dyncfg flags will keep stale options and can break filtering/streaming. Consider comparing and updating rdcf->options from new_rdcf->options (like the other fields).
| if(!st) | ||
| stream_send_function_del(host, key); | ||
|
|
||
| if(!st) | ||
| rrdhost_flag_set(host, RRDHOST_FLAG_GLOBAL_FUNCTIONS_UPDATED); |
There was a problem hiding this comment.
rrd_function_del() only streams FUNCTION_DEL upstream for global functions (when st == NULL). Chart-scoped functions are streamed to parents during CHART definition, so unregistering a chart function on a multi-hop topology will leave stale functions on the parent(s). If FUNCTION_DEL is meant to support multi-hop generally, consider streaming deletions for chart functions too (e.g., send a GLOBAL FUNCTION_DEL by name, or otherwise ensure the parent can reliably apply the deletion).
@ktsaou I've expanded the go.d.plugin's function framework.
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| struct rrd_host_function *t; | ||
| dfe_start_read(rrdset_functions_view, t) { | ||
| if(!rrd_collector_running(t->collector)) continue; | ||
| if(!rrd_function_is_available(t, NULL)) continue; |
There was a problem hiding this comment.
The rrd_function_is_available call passes NULL for the host parameter, which bypasses the rrdhost_state_id check. This is inconsistent with similar functions like stream_sender_send_rrdset_functions (line 12) and chart_functions2json (which calls functions2json with st->rrdhost). Since rrdinstance_acquired_rrdhost() is available to get the host from the instance acquired object in the caller (src/web/api/formatters/jsonwrap.c:57), the host pointer should be obtained and passed here to ensure proper state validation, especially after this PR introduces function unregistration.
Summary
FUNCTION_DELprotocol command to allow plugins to dynamically unregister functions without restartingChanges
Core:
unregisteredflag torrd_host_functionstructrrd_function_is_available()helper used by all availability checksProtocol:
FUNCTION_DEL [GLOBAL] "name"command (keyword ID 44)STREAM_CAP_FUNCTION_DELcapability (bit 27, name "FUNCDEL")Safety:
internalparameter for trusted callers (dyncfg, health)Protocol Format
API Behavior
/api/v1/functionslistingFUNCTIONcommand restores availabilityTest plan
Summary by cubic
Adds the FUNCTION_DEL protocol command so plugins can unregister functions at runtime. This lets go.d.plugin clean up functions when collectors are disabled via DYNCFG, without restarting.
Written for commit ca6ca7d. Summary will update on new commits.