master
go 511 lines 14.5 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package wireguard
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "os"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
15 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
16
17 "github.com/stretchr/testify/assert"
18 "github.com/stretchr/testify/require"
19 "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
20 )
21
22 var (
23 dataConfigJSON, _ = os.ReadFile("testdata/config.json")
24 dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
25 )
26
27 func Test_testDataIsValid(t *testing.T) {
28 for name, data := range map[string][]byte{
29 "dataConfigJSON": dataConfigJSON,
30 "dataConfigYAML": dataConfigYAML,
31 } {
32 assert.NotNil(t, data, name)
33 }
34 }
35
36 func TestCollector_ConfigurationSerialize(t *testing.T) {
37 collecttest.TestConfigurationSerialize(t, &Collector{}, dataConfigJSON, dataConfigYAML)
38 }
39
40 func TestCollector_Init(t *testing.T) {
41 assert.NoError(t, New().Init(context.Background()))
42 }
43
44 func TestCollector_Charts(t *testing.T) {
45 assert.Len(t, *New().Charts(), 0)
46
47 }
48
49 func TestCollector_Cleanup(t *testing.T) {
50 tests := map[string]struct {
51 prepare func(w *Collector)
52 wantClose bool
53 }{
54 "after New": {
55 wantClose: false,
56 prepare: func(*Collector) {},
57 },
58 "after Init": {
59 wantClose: false,
60 prepare: func(c *Collector) { _ = c.Init(context.Background()) },
61 },
62 "after Check": {
63 wantClose: true,
64 prepare: func(c *Collector) { _ = c.Init(context.Background()); _ = c.Check(context.Background()) },
65 },
66 "after Collect": {
67 wantClose: true,
68 prepare: func(c *Collector) { _ = c.Init(context.Background()); _ = c.Collect(context.Background()) },
69 },
70 }
71
72 for name, test := range tests {
73 t.Run(name, func(t *testing.T) {
74 collr := New()
75 m := &mockClient{}
76 collr.newWGClient = func() (wgClient, error) { return m, nil }
77
78 test.prepare(collr)
79
80 require.NotPanics(t, func() { collr.Cleanup(context.Background()) })
81
82 if test.wantClose {
83 assert.True(t, m.closeCalled)
84 } else {
85 assert.False(t, m.closeCalled)
86 }
87 })
88 }
89 }
90
91 func TestCollector_Check(t *testing.T) {
92 tests := map[string]struct {
93 wantFail bool
94 prepare func(w *Collector)
95 }{
96 "success when devices and peers found": {
97 wantFail: false,
98 prepare: func(collr *Collector) {
99 m := &mockClient{}
100 d1 := prepareDevice(1)
101 d1.Peers = append(d1.Peers, preparePeer("11"))
102 d1.Peers = append(d1.Peers, preparePeer("12"))
103 m.devices = append(m.devices, d1)
104 collr.client = m
105 },
106 },
107 "success when devices and no peers found": {
108 wantFail: false,
109 prepare: func(collr *Collector) {
110 m := &mockClient{}
111 m.devices = append(m.devices, prepareDevice(1))
112 collr.client = m
113 },
114 },
115 "fail when no devices and no peers found": {
116 wantFail: true,
117 prepare: func(collr *Collector) {
118 collr.client = &mockClient{}
119 },
120 },
121 "fail when error on retrieving devices": {
122 wantFail: true,
123 prepare: func(collr *Collector) {
124 collr.client = &mockClient{errOnDevices: true}
125 },
126 },
127 "fail when error on creating client": {
128 wantFail: true,
129 prepare: func(collr *Collector) {
130 collr.newWGClient = func() (wgClient, error) { return nil, errors.New("mock.newWGClient() error") }
131 },
132 },
133 }
134
135 for name, test := range tests {
136 t.Run(name, func(t *testing.T) {
137 collr := New()
138 require.NoError(t, collr.Init(context.Background()))
139 test.prepare(collr)
140
141 if test.wantFail {
142 assert.Error(t, collr.Check(context.Background()))
143 } else {
144 assert.NoError(t, collr.Check(context.Background()))
145 }
146 })
147 }
148 }
149
150 func TestCollector_Collect(t *testing.T) {
151 type testCaseStep struct {
152 prepareMock func(*mockClient)
153 check func(*testing.T, *Collector)
154 }
155 tests := map[string][]testCaseStep{
156 "several devices no peers": {
157 {
158 prepareMock: func(m *mockClient) {
159 m.devices = append(m.devices, prepareDevice(1))
160 m.devices = append(m.devices, prepareDevice(2))
161 },
162 check: func(t *testing.T, collr *Collector) {
163 mx := collr.Collect(context.Background())
164
165 expected := map[string]int64{
166 "device_wg1_peers": 0,
167 "device_wg1_receive": 0,
168 "device_wg1_transmit": 0,
169 "device_wg2_peers": 0,
170 "device_wg2_receive": 0,
171 "device_wg2_transmit": 0,
172 }
173
174 copyLatestHandshake(mx, expected)
175 assert.Equal(t, expected, mx)
176 assert.Equal(t, len(deviceChartsTmpl)*2, len(*collr.Charts()))
177 },
178 },
179 },
180 "several devices several peers each": {
181 {
182 prepareMock: func(m *mockClient) {
183 d1 := prepareDevice(1)
184 d1.Peers = append(d1.Peers, preparePeer("11"))
185 d1.Peers = append(d1.Peers, preparePeer("12"))
186 m.devices = append(m.devices, d1)
187
188 d2 := prepareDevice(2)
189 d2.Peers = append(d2.Peers, preparePeer("21"))
190 d2.Peers = append(d2.Peers, preparePeer("22"))
191 m.devices = append(m.devices, d2)
192 },
193 check: func(t *testing.T, collr *Collector) {
194 mx := collr.Collect(context.Background())
195
196 expected := map[string]int64{
197 "device_wg1_peers": 2,
198 "device_wg1_receive": 0,
199 "device_wg1_transmit": 0,
200 "device_wg2_peers": 2,
201 "device_wg2_receive": 0,
202 "device_wg2_transmit": 0,
203 "peer_wg1_cGVlcjExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_latest_handshake_ago": 60,
204 "peer_wg1_cGVlcjExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_receive": 0,
205 "peer_wg1_cGVlcjExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_transmit": 0,
206 "peer_wg1_cGVlcjEyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_latest_handshake_ago": 60,
207 "peer_wg1_cGVlcjEyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_receive": 0,
208 "peer_wg1_cGVlcjEyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_transmit": 0,
209 "peer_wg2_cGVlcjIxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_latest_handshake_ago": 60,
210 "peer_wg2_cGVlcjIxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_receive": 0,
211 "peer_wg2_cGVlcjIxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_transmit": 0,
212 "peer_wg2_cGVlcjIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_latest_handshake_ago": 60,
213 "peer_wg2_cGVlcjIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_receive": 0,
214 "peer_wg2_cGVlcjIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_transmit": 0,
215 }
216
217 copyLatestHandshake(mx, expected)
218 assert.Equal(t, expected, mx)
219 assert.Equal(t, len(deviceChartsTmpl)*2+len(peerChartsTmpl)*4, len(*collr.Charts()))
220 },
221 },
222 },
223 "peers without last handshake time": {
224 {
225 prepareMock: func(m *mockClient) {
226 d1 := prepareDevice(1)
227 d1.Peers = append(d1.Peers, preparePeer("11"))
228 d1.Peers = append(d1.Peers, preparePeer("12"))
229 d1.Peers = append(d1.Peers, prepareNoLastHandshakePeer("13"))
230 d1.Peers = append(d1.Peers, prepareNoLastHandshakePeer("14"))
231 m.devices = append(m.devices, d1)
232 },
233 check: func(t *testing.T, collr *Collector) {
234 mx := collr.Collect(context.Background())
235
236 expected := map[string]int64{
237 "device_wg1_peers": 4,
238 "device_wg1_receive": 0,
239 "device_wg1_transmit": 0,
240 "peer_wg1_cGVlcjExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_latest_handshake_ago": 60,
241 "peer_wg1_cGVlcjExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_receive": 0,
242 "peer_wg1_cGVlcjExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_transmit": 0,
243 "peer_wg1_cGVlcjEyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_latest_handshake_ago": 60,
244 "peer_wg1_cGVlcjEyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_receive": 0,
245 "peer_wg1_cGVlcjEyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_transmit": 0,
246 }
247
248 copyLatestHandshake(mx, expected)
249 assert.Equal(t, expected, mx)
250 assert.Equal(t, len(deviceChartsTmpl)+len(peerChartsTmpl)*2, len(*collr.Charts()))
251 },
252 },
253 },
254 "device added at runtime": {
255 {
256 prepareMock: func(m *mockClient) {
257 m.devices = append(m.devices, prepareDevice(1))
258 },
259 check: func(t *testing.T, collr *Collector) {
260 _ = collr.Collect(context.Background())
261 assert.Equal(t, len(deviceChartsTmpl)*1, len(*collr.Charts()))
262 },
263 },
264 {
265 prepareMock: func(m *mockClient) {
266 m.devices = append(m.devices, prepareDevice(2))
267 },
268 check: func(t *testing.T, collr *Collector) {
269 mx := collr.Collect(context.Background())
270
271 expected := map[string]int64{
272 "device_wg1_peers": 0,
273 "device_wg1_receive": 0,
274 "device_wg1_transmit": 0,
275 "device_wg2_peers": 0,
276 "device_wg2_receive": 0,
277 "device_wg2_transmit": 0,
278 }
279 copyLatestHandshake(mx, expected)
280 assert.Equal(t, expected, mx)
281 assert.Equal(t, len(deviceChartsTmpl)*2, len(*collr.Charts()))
282
283 },
284 },
285 },
286 "device removed at run time, no cleanup occurred": {
287 {
288 prepareMock: func(m *mockClient) {
289 m.devices = append(m.devices, prepareDevice(1))
290 m.devices = append(m.devices, prepareDevice(2))
291 },
292 check: func(t *testing.T, collr *Collector) {
293 _ = collr.Collect(context.Background())
294 },
295 },
296 {
297 prepareMock: func(m *mockClient) {
298 m.devices = m.devices[:len(m.devices)-1]
299 },
300 check: func(t *testing.T, collr *Collector) {
301 _ = collr.Collect(context.Background())
302 assert.Equal(t, len(deviceChartsTmpl)*2, len(*collr.Charts()))
303 assert.Equal(t, 0, calcObsoleteCharts(collr.Charts()))
304 },
305 },
306 },
307 "device removed at run time, cleanup occurred": {
308 {
309 prepareMock: func(m *mockClient) {
310 m.devices = append(m.devices, prepareDevice(1))
311 m.devices = append(m.devices, prepareDevice(2))
312 },
313 check: func(t *testing.T, collr *Collector) {
314 _ = collr.Collect(context.Background())
315 },
316 },
317 {
318 prepareMock: func(m *mockClient) {
319 m.devices = m.devices[:len(m.devices)-1]
320 },
321 check: func(t *testing.T, collr *Collector) {
322 collr.cleanupEvery = time.Second
323 time.Sleep(time.Second)
324 _ = collr.Collect(context.Background())
325 assert.Equal(t, len(deviceChartsTmpl)*2, len(*collr.Charts()))
326 assert.Equal(t, len(deviceChartsTmpl)*1, calcObsoleteCharts(collr.Charts()))
327 },
328 },
329 },
330 "peer added at runtime": {
331 {
332 prepareMock: func(m *mockClient) {
333 m.devices = append(m.devices, prepareDevice(1))
334 },
335 check: func(t *testing.T, collr *Collector) {
336 _ = collr.Collect(context.Background())
337 assert.Equal(t, len(deviceChartsTmpl)*1, len(*collr.Charts()))
338 },
339 },
340 {
341 prepareMock: func(m *mockClient) {
342 d1 := m.devices[0]
343 d1.Peers = append(d1.Peers, preparePeer("11"))
344 },
345 check: func(t *testing.T, collr *Collector) {
346 mx := collr.Collect(context.Background())
347
348 expected := map[string]int64{
349 "device_wg1_peers": 1,
350 "device_wg1_receive": 0,
351 "device_wg1_transmit": 0,
352 "peer_wg1_cGVlcjExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_latest_handshake_ago": 60,
353 "peer_wg1_cGVlcjExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_receive": 0,
354 "peer_wg1_cGVlcjExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=_transmit": 0,
355 }
356 copyLatestHandshake(mx, expected)
357 assert.Equal(t, expected, mx)
358 assert.Equal(t, len(deviceChartsTmpl)*1+len(peerChartsTmpl)*1, len(*collr.Charts()))
359 },
360 },
361 },
362 "peer removed at run time, no cleanup occurred": {
363 {
364 prepareMock: func(m *mockClient) {
365 d1 := prepareDevice(1)
366 d1.Peers = append(d1.Peers, preparePeer("11"))
367 d1.Peers = append(d1.Peers, preparePeer("12"))
368 m.devices = append(m.devices, d1)
369 },
370 check: func(t *testing.T, collr *Collector) {
371 _ = collr.Collect(context.Background())
372 },
373 },
374 {
375 prepareMock: func(m *mockClient) {
376 d1 := m.devices[0]
377 d1.Peers = d1.Peers[:len(d1.Peers)-1]
378 },
379 check: func(t *testing.T, collr *Collector) {
380 _ = collr.Collect(context.Background())
381 assert.Equal(t, len(deviceChartsTmpl)*1+len(peerChartsTmpl)*2, len(*collr.Charts()))
382 assert.Equal(t, 0, calcObsoleteCharts(collr.Charts()))
383 },
384 },
385 },
386 "peer removed at run time, cleanup occurred": {
387 {
388 prepareMock: func(m *mockClient) {
389 d1 := prepareDevice(1)
390 d1.Peers = append(d1.Peers, preparePeer("11"))
391 d1.Peers = append(d1.Peers, preparePeer("12"))
392 m.devices = append(m.devices, d1)
393 },
394 check: func(t *testing.T, collr *Collector) {
395 _ = collr.Collect(context.Background())
396 },
397 },
398 {
399 prepareMock: func(m *mockClient) {
400 d1 := m.devices[0]
401 d1.Peers = d1.Peers[:len(d1.Peers)-1]
402 },
403 check: func(t *testing.T, collr *Collector) {
404 collr.cleanupEvery = time.Second
405 time.Sleep(time.Second)
406 _ = collr.Collect(context.Background())
407 assert.Equal(t, len(deviceChartsTmpl)*1+len(peerChartsTmpl)*2, len(*collr.Charts()))
408 assert.Equal(t, len(peerChartsTmpl)*1, calcObsoleteCharts(collr.Charts()))
409 },
410 },
411 },
412 "fails if no devices found": {
413 {
414 prepareMock: func(m *mockClient) {},
415 check: func(t *testing.T, collr *Collector) {
416 assert.Equal(t, map[string]int64(nil), collr.Collect(context.Background()))
417 },
418 },
419 },
420 "fails if error on getting devices list": {
421 {
422 prepareMock: func(m *mockClient) {
423 m.errOnDevices = true
424 },
425 check: func(t *testing.T, collr *Collector) {
426 assert.Equal(t, map[string]int64(nil), collr.Collect(context.Background()))
427 },
428 },
429 },
430 }
431
432 for name, test := range tests {
433 t.Run(name, func(t *testing.T) {
434 collr := New()
435 require.NoError(t, collr.Init(context.Background()))
436 m := &mockClient{}
437 collr.client = m
438
439 for i, step := range test {
440 t.Run(fmt.Sprintf("step[%d]", i), func(t *testing.T) {
441 step.prepareMock(m)
442 step.check(t, collr)
443 })
444 }
445 })
446 }
447 }
448
449 type mockClient struct {
450 devices []*wgtypes.Device
451 errOnDevices bool
452 closeCalled bool
453 }
454
455 func (m *mockClient) Devices() ([]*wgtypes.Device, error) {
456 if m.errOnDevices {
457 return nil, errors.New("mock.Devices() error")
458 }
459 return m.devices, nil
460 }
461
462 func (m *mockClient) Close() error {
463 m.closeCalled = true
464 return nil
465 }
466
467 func prepareDevice(num uint8) *wgtypes.Device {
468 return &wgtypes.Device{
469 Name: fmt.Sprintf("wg%d", num),
470 }
471 }
472
473 func preparePeer(s string) wgtypes.Peer {
474 b := make([]byte, 32)
475 b = append(b[:0], fmt.Sprintf("peer%s", s)...)
476 k, _ := wgtypes.NewKey(b[:32])
477
478 return wgtypes.Peer{
479 PublicKey: k,
480 LastHandshakeTime: time.Now().Add(-time.Minute),
481 ReceiveBytes: 0,
482 TransmitBytes: 0,
483 }
484 }
485
486 func prepareNoLastHandshakePeer(s string) wgtypes.Peer {
487 p := preparePeer(s)
488 var lh time.Time
489 p.LastHandshakeTime = lh
490 return p
491 }
492
493 func copyLatestHandshake(dst, src map[string]int64) {
494 for k, v := range src {
495 if strings.HasSuffix(k, "latest_handshake_ago") {
496 if _, ok := dst[k]; ok {
497 dst[k] = v
498 }
499 }
500 }
501 }
502
503 func calcObsoleteCharts(charts *collectorapi.Charts) int {
504 var num int
505 for _, c := range *charts {
506 if c.Obsolete {
507 num++
508 }
509 }
510 return num
511 }