fix(plugin/agent): log applyConfig failures in job manager (#22124)
Ilya Mashchenko committed
Apr 3, 2026 at 10:55 UTC
5c57cff6dd904db4defb8766f0e94d2c5ea64b35
3 files changed
+65
-15
src/go/plugin/agent/jobmgr/dyncfg_collector_cmds.go
+2
-7
@@ -9,7 +9,6 @@ import (
9
"time"
10
11
"github.com/netdata/netdata/go/plugins/logger"
12
- "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
12
"github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
13
"github.com/netdata/netdata/go/plugins/plugin/framework/confgroup"
14
"github.com/netdata/netdata/go/plugins/plugin/framework/dyncfg"
@@ -185,14 +184,10 @@ func (m *Manager) runDyncfgCmdTest(task dyncfgCmdTestTask) {
184
defer cancel()
185
186
secretStoreSvc := m.secretsCtl.Service()
188
-
189
- storeSnapshot := (*secretstore.Snapshot)(nil)
190
- if secretStoreSvc != nil {
191
- storeSnapshot = secretStoreSvc.Capture()
192
- }
187
+ storeSnapshot := secretStoreSvc.Capture()
188
resolveCtx := collectorSecretResolveContext(ctx, m.Logger, task.cfg)
189
if err := applyConfig(resolveCtx, task.cfg, job, m.secretResolver, secretStoreSvc, storeSnapshot); err != nil {
195
- m.Warningf("dyncfg: test: module %s: failed to apply config: %v", task.moduleName, err)
190
+ m.Warningf("dyncfg: test: module %s job %s: failed to apply config: %v", task.moduleName, task.cfg.Name(), err)
191
m.dyncfgResponder.SendCodef(task.fn, 400, "Invalid configuration. Failed to apply configuration: %v.", err)
192
return
193
}
src/go/plugin/agent/jobmgr/dyncfg_collector_test.go
+52
@@ -12,6 +12,7 @@ import (
12
"testing"
13
"time"
14
15
+ "github.com/netdata/netdata/go/plugins/logger"
16
"github.com/stretchr/testify/assert"
17
"github.com/stretchr/testify/require"
18
@@ -364,6 +365,57 @@ func TestCollectorCallbacks_ParseAndValidate_SuppressesAuditSideEffects(t *testi
365
assert.Empty(t, analyzer.registered)
366
}
367
368
+func TestCollectorCallbacks_ApplyConfigLoggingHonorsValidationMode(t *testing.T) {
369
+ tests := map[string]struct {
370
+ run func(t *testing.T, mgr *Manager, logBuf *bytes.Buffer)
371
+ wantLogMessage bool
372
+ }{
373
+ "validation suppresses expected applyConfig error logs": {
374
+ run: func(t *testing.T, mgr *Manager, _ *bytes.Buffer) {
375
+ cb := &collectorCallbacks{mgr: mgr}
376
+ cfg := prepareDyncfgCfg("success", "payload-name").Set("option_str", "one").Set("option_int", "bad")
377
+ fn := dyncfg.NewFunction(functions.Function{
378
+ UID: "validation-no-log",
379
+ ContentType: "application/json",
380
+ Payload: mustMarshalCollectorConfigPayload(t, cfg),
381
+ Args: collectorTestArgs(mgr, "success", string(dyncfg.CommandAdd), "validated"),
382
+ })
383
+
384
+ _, err := cb.ParseAndValidate(fn, "validated")
385
+ require.Error(t, err)
386
+ assert.Contains(t, err.Error(), "failed to apply configuration")
387
+ },
388
+ },
389
+ "runtime creation still logs applyConfig errors": {
390
+ run: func(t *testing.T, mgr *Manager, _ *bytes.Buffer) {
391
+ cfg := prepareDyncfgCfg("success", "runtime-job").Set("option_str", "one").Set("option_int", "bad")
392
+
393
+ _, err := mgr.createCollectorJob(cfg)
394
+ require.Error(t, err)
395
+ assert.Contains(t, err.Error(), "cannot unmarshal")
396
+ },
397
+ wantLogMessage: true,
398
+ },
399
+ }
400
+
401
+ for name, tc := range tests {
402
+ t.Run(name, func(t *testing.T) {
403
+ var logBuf bytes.Buffer
404
+ mgr := newCollectorTestManager()
405
+ mgr.Logger = logger.NewWithWriter(&logBuf)
406
+
407
+ tc.run(t, mgr, &logBuf)
408
+
409
+ const msg = "failed to apply config for"
410
+ if tc.wantLogMessage {
411
+ assert.Contains(t, logBuf.String(), msg)
412
+ } else {
413
+ assert.NotContains(t, logBuf.String(), msg)
414
+ }
415
+ })
416
+ }
417
+}
418
+
419
func TestCollectorCallbacks_Start(t *testing.T) {
420
tests := map[string]struct {
421
cfg confgroup.Config
src/go/plugin/agent/jobmgr/job_factory.go
+11
-8
@@ -101,17 +101,22 @@ func (f *jobFactory) create(cfg confgroup.Config) (runtimeJob, error) {
101
return f.createV1(cfg, creator, functionOnly, vnode)
102
}
103
104
+func (f *jobFactory) logApplyConfigError(cfg confgroup.Config, err error) {
105
+ if f.validationOnly {
106
+ return
107
+ }
108
+ f.logger.Errorf("failed to apply config for %s[%s] job: %v", cfg.Module(), cfg.Name(), err)
109
+}
110
+
111
func (f *jobFactory) createV2(cfg confgroup.Config, creator collectorapi.Creator, functionOnly bool, vnode *vnodes.VirtualNode) (runtimeJob, error) {
112
mod := creator.CreateV2()
113
if mod == nil {
114
return nil, fmt.Errorf("module %s CreateV2 returned nil", cfg.Module())
115
}
109
- storeSnapshot := (*secretstore.Snapshot)(nil)
110
- if f.secretStoreSvc != nil {
111
- storeSnapshot = f.secretStoreSvc.Capture()
112
- }
116
+ storeSnapshot := f.secretStoreSvc.Capture()
117
resolveCtx := collectorSecretResolveContext(f.ctx, f.logger, cfg)
118
if err := applyConfig(resolveCtx, cfg, mod, f.secretResolver, f.secretStoreSvc, storeSnapshot); err != nil {
119
+ f.logApplyConfigError(cfg, err)
120
return nil, err
121
}
122
@@ -146,12 +151,10 @@ func (f *jobFactory) createV1(cfg confgroup.Config, creator collectorapi.Creator
151
}
152
153
mod := creator.Create()
149
- storeSnapshot := (*secretstore.Snapshot)(nil)
150
- if f.secretStoreSvc != nil {
151
- storeSnapshot = f.secretStoreSvc.Capture()
152
- }
154
+ storeSnapshot := f.secretStoreSvc.Capture()
155
resolveCtx := collectorSecretResolveContext(f.ctx, f.logger, cfg)
156
if err := applyConfig(resolveCtx, cfg, mod, f.secretResolver, f.secretStoreSvc, storeSnapshot); err != nil {
157
+ f.logApplyConfigError(cfg, err)
158
return nil, err
159
}
160