master
go 355 lines 9.72 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package jobmgr
4
5 import (
6 "bytes"
7 "context"
8 "strings"
9 "sync"
10 "testing"
11 "time"
12
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15
16 "github.com/netdata/netdata/go/plugins/pkg/funcapi"
17 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
18 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
19 "github.com/netdata/netdata/go/plugins/plugin/framework/confgroup"
20 "github.com/netdata/netdata/go/plugins/plugin/framework/functions"
21 "github.com/netdata/netdata/go/plugins/plugin/framework/vnodes"
22 )
23
24 func TestRunProcessConfGroups_ChannelCloseDoesNotSpin(t *testing.T) {
25 tests := map[string]struct {
26 closeInput bool
27 cancelBefore bool
28 wantExit bool
29 }{
30 "closed channel exits loop": {
31 closeInput: true,
32 wantExit: true,
33 },
34 "canceled context exits loop": {
35 cancelBefore: true,
36 wantExit: true,
37 },
38 }
39
40 for name, tc := range tests {
41 t.Run(name, func(t *testing.T) {
42 mgr := New(Config{PluginName: testPluginName})
43 ctx, cancel := context.WithCancel(context.Background())
44 t.Cleanup(cancel)
45 mgr.ctx = ctx
46
47 in := make(chan []*confgroup.Group)
48 if tc.closeInput {
49 close(in)
50 }
51 if tc.cancelBefore {
52 cancel()
53 }
54
55 done := make(chan struct{})
56 go func() {
57 mgr.runProcessConfGroups(in)
58 close(done)
59 }()
60
61 select {
62 case <-done:
63 assert.True(t, tc.wantExit)
64 case <-time.After(200 * time.Millisecond):
65 t.Fatal("runProcessConfGroups did not exit")
66 }
67 })
68 }
69 }
70
71 func TestRunNotifyRunningJobs_TickOutsideLock(t *testing.T) {
72 mgr := New(Config{PluginName: testPluginName})
73
74 ctx, cancel := context.WithCancel(context.Background())
75 defer cancel()
76 mgr.ctx = ctx
77
78 job := &lockProbeJob{
79 fullName: "success_lockprobe",
80 moduleName: "success",
81 name: "lockprobe",
82 tickStarted: make(chan struct{}),
83 tickRelease: make(chan struct{}),
84 }
85
86 mgr.runningJobs.lock()
87 mgr.runningJobs.add(job.FullName(), job)
88 mgr.runningJobs.unlock()
89
90 done := make(chan struct{})
91 go func() {
92 mgr.runNotifyRunningJobs()
93 close(done)
94 }()
95
96 select {
97 case <-job.tickStarted:
98 case <-time.After(2 * time.Second):
99 t.Fatal("tick did not start")
100 }
101
102 stopDone := make(chan struct{})
103 go func() {
104 mgr.stopRunningJob(job.FullName())
105 close(stopDone)
106 }()
107
108 select {
109 case <-stopDone:
110 case <-time.After(300 * time.Millisecond):
111 t.Fatal("stopRunningJob blocked while Tick was in progress")
112 }
113
114 close(job.tickRelease)
115 cancel()
116
117 select {
118 case <-done:
119 case <-time.After(2 * time.Second):
120 t.Fatal("runNotifyRunningJobs did not stop")
121 }
122 }
123
124 func TestRun_DoesNotRegisterModuleMethodsBeforeAnyJobStarts(t *testing.T) {
125 fnReg := &recordingFunctionRegistry{}
126 mgr := New(Config{PluginName: testPluginName, FnReg: fnReg})
127
128 mgr.modules = collectorapi.Registry{
129 "mod": collectorapi.Creator{
130 Methods: func() []funcapi.MethodConfig {
131 return []funcapi.MethodConfig{{ID: "a"}}
132 },
133 },
134 }
135
136 ctx, cancel := context.WithCancel(context.Background())
137 defer cancel()
138
139 in := make(chan []*confgroup.Group)
140 done := make(chan struct{})
141 go func() {
142 mgr.Run(ctx, in)
143 close(done)
144 }()
145
146 waitCtx, waitCancel := context.WithTimeout(context.Background(), time.Second)
147 defer waitCancel()
148 require.True(t, mgr.WaitStarted(waitCtx), "manager did not report started")
149
150 cancel()
151 close(in)
152
153 select {
154 case <-done:
155 case <-time.After(2 * time.Second):
156 t.Fatal("manager did not stop after cancel")
157 }
158
159 assert.Empty(t, fnReg.registeredNames(), "static methods must not be registered before first started job")
160 }
161
162 func TestStartRunningJob_RegistersModuleMethodsOnFirstStartedJob(t *testing.T) {
163 fnReg := &recordingFunctionRegistry{}
164 mgr := New(Config{PluginName: testPluginName, FnReg: fnReg})
165 creator := collectorapi.Creator{
166 Methods: func() []funcapi.MethodConfig {
167 return []funcapi.MethodConfig{{ID: "a"}, {ID: "b"}}
168 },
169 }
170 mgr.modules = collectorapi.Registry{"mod": creator}
171 mgr.funcCtl.RegisterModules(mgr.modules)
172
173 job := &lockProbeJob{fullName: "mod_job1", moduleName: "mod", name: "job1"}
174 mgr.startRunningJob(job)
175
176 assert.ElementsMatch(t, []string{"mod:a", "mod:b"}, fnReg.registeredNames())
177 }
178
179 func TestStartRunningJob_DoesNotReregisterModuleMethods(t *testing.T) {
180 fnReg := &recordingFunctionRegistry{}
181 mgr := New(Config{PluginName: testPluginName, FnReg: fnReg})
182 creator := collectorapi.Creator{
183 Methods: func() []funcapi.MethodConfig {
184 return []funcapi.MethodConfig{{ID: "a"}, {ID: "b"}}
185 },
186 }
187 mgr.modules = collectorapi.Registry{"mod": creator}
188 mgr.funcCtl.RegisterModules(mgr.modules)
189
190 job1 := &lockProbeJob{fullName: "mod_job1", moduleName: "mod", name: "job1"}
191 mgr.startRunningJob(job1)
192
193 job2 := &lockProbeJob{fullName: "mod_job2", moduleName: "mod", name: "job2"}
194 mgr.startRunningJob(job2)
195
196 registered := fnReg.registeredNames()
197 assert.Len(t, registered, 2)
198 assert.ElementsMatch(t, []string{"mod:a", "mod:b"}, registered)
199 }
200
201 func TestRun_RegistersDyncfgConfigPrefixes(t *testing.T) {
202 fnReg := &recordingFunctionRegistry{}
203 mgr := New(Config{PluginName: testPluginName, FnReg: fnReg})
204
205 ctx, cancel := context.WithCancel(context.Background())
206 defer cancel()
207
208 in := make(chan []*confgroup.Group)
209 done := make(chan struct{})
210 go func() {
211 mgr.Run(ctx, in)
212 close(done)
213 }()
214
215 waitCtx, waitCancel := context.WithTimeout(context.Background(), time.Second)
216 defer waitCancel()
217 require.True(t, mgr.WaitStarted(waitCtx), "manager did not report started")
218
219 cancel()
220 close(in)
221
222 select {
223 case <-done:
224 case <-time.After(2 * time.Second):
225 t.Fatal("manager did not stop after cancel")
226 }
227
228 assert.Equal(t, []registeredPrefix{
229 {name: "config", prefix: mgr.dyncfgVnodePrefixValue()},
230 {name: "config", prefix: mgr.dyncfgSecretStorePrefixValue()},
231 {name: "config", prefix: mgr.dyncfgCollectorPrefixValue()},
232 }, fnReg.registeredPrefixes())
233 }
234
235 func TestRun_PublishesVnodesAndSecretstoresBeforeCollectorTemplates(t *testing.T) {
236 fnReg := &recordingFunctionRegistry{}
237 var buf bytes.Buffer
238
239 mgr := New(Config{
240 PluginName: testPluginName,
241 FnReg: fnReg,
242 Out: &buf,
243 Modules: collectorapi.Registry{
244 "mod": collectorapi.Creator{},
245 },
246 })
247
248 ctx, cancel := context.WithCancel(context.Background())
249 defer cancel()
250
251 in := make(chan []*confgroup.Group)
252 done := make(chan struct{})
253 go func() {
254 mgr.Run(ctx, in)
255 close(done)
256 }()
257
258 waitCtx, waitCancel := context.WithTimeout(context.Background(), time.Second)
259 defer waitCancel()
260 require.True(t, mgr.WaitStarted(waitCtx), "manager did not report started")
261
262 cancel()
263 close(in)
264
265 select {
266 case <-done:
267 case <-time.After(2 * time.Second):
268 t.Fatal("manager did not stop after cancel")
269 }
270
271 output := buf.String()
272
273 vnodeIdx := strings.Index(output, "CONFIG "+mgr.dyncfgVnodePrefixValue()+" create accepted template /collectors/"+testPluginName+"/Vnodes")
274 secretIdx := strings.Index(output, "CONFIG "+mgr.dyncfgSecretStoreID(string(secretstore.KindVault))+" create accepted template /collectors/"+testPluginName+"/SecretStores")
275 collectorIdx := strings.Index(output, "CONFIG "+mgr.dyncfgModID("mod")+" create accepted template /collectors/"+testPluginName+"/Jobs")
276
277 require.NotEqual(t, -1, vnodeIdx, "vnode template publication not found")
278 require.NotEqual(t, -1, secretIdx, "secretstore template publication not found")
279 require.NotEqual(t, -1, collectorIdx, "collector template publication not found")
280 assert.Less(t, vnodeIdx, collectorIdx, "vnode publication must happen before collector template publication")
281 assert.Less(t, secretIdx, collectorIdx, "secretstore publication must happen before collector template publication")
282 }
283
284 type lockProbeJob struct {
285 fullName string
286 moduleName string
287 name string
288
289 tickOnce sync.Once
290 stopOnce sync.Once
291 tickStarted chan struct{}
292 tickRelease chan struct{}
293 }
294
295 func (j *lockProbeJob) FullName() string { return j.fullName }
296 func (j *lockProbeJob) ModuleName() string { return j.moduleName }
297 func (j *lockProbeJob) Name() string { return j.name }
298 func (j *lockProbeJob) Collector() any { return nil }
299 func (j *lockProbeJob) Start() {}
300 func (j *lockProbeJob) Stop() { j.stopOnce.Do(func() {}) }
301 func (j *lockProbeJob) Tick(_ int) {
302 j.tickOnce.Do(func() {
303 close(j.tickStarted)
304 <-j.tickRelease
305 })
306 }
307 func (j *lockProbeJob) AutoDetection() error { return nil }
308 func (j *lockProbeJob) AutoDetectionEvery() int { return 0 }
309 func (j *lockProbeJob) RetryAutoDetection() bool { return false }
310 func (j *lockProbeJob) Cleanup() {}
311 func (j *lockProbeJob) IsRunning() bool { return true }
312 func (j *lockProbeJob) Panicked() bool { return false }
313 func (j *lockProbeJob) Vnode() vnodes.VirtualNode { return vnodes.VirtualNode{} }
314 func (j *lockProbeJob) UpdateVnode(_ *vnodes.VirtualNode) {}
315
316 type recordingFunctionRegistry struct {
317 mu sync.Mutex
318 registered []string
319 prefixes []registeredPrefix
320 }
321
322 func (r *recordingFunctionRegistry) Register(name string, _ func(functions.Function)) {
323 r.mu.Lock()
324 r.registered = append(r.registered, name)
325 r.mu.Unlock()
326 }
327
328 func (r *recordingFunctionRegistry) Unregister(string) {}
329 func (r *recordingFunctionRegistry) RegisterPrefix(name, prefix string, _ func(functions.Function)) {
330 r.mu.Lock()
331 r.prefixes = append(r.prefixes, registeredPrefix{name: name, prefix: prefix})
332 r.mu.Unlock()
333 }
334 func (r *recordingFunctionRegistry) UnregisterPrefix(string, string) {}
335
336 func (r *recordingFunctionRegistry) registeredNames() []string {
337 r.mu.Lock()
338 defer r.mu.Unlock()
339 out := make([]string, len(r.registered))
340 copy(out, r.registered)
341 return out
342 }
343
344 func (r *recordingFunctionRegistry) registeredPrefixes() []registeredPrefix {
345 r.mu.Lock()
346 defer r.mu.Unlock()
347 out := make([]registeredPrefix, len(r.prefixes))
348 copy(out, r.prefixes)
349 return out
350 }
351
352 type registeredPrefix struct {
353 name string
354 prefix string
355 }