master
go 327 lines 8.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package chrony
4
5 import (
6 "context"
7 "errors"
8 "net"
9 "os"
10 "testing"
11 "time"
12
13 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
14
15 "github.com/facebook/time/ntp/chrony"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18 )
19
20 var (
21 dataConfigJSON, _ = os.ReadFile("testdata/config.json")
22 dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
23 )
24
25 func Test_testDataIsValid(t *testing.T) {
26 for name, data := range map[string][]byte{
27 "dataConfigJSON": dataConfigJSON,
28 "dataConfigYAML": dataConfigYAML,
29 } {
30 assert.NotNil(t, data, name)
31 }
32 }
33
34 func TestCollector_ConfigurationSerialize(t *testing.T) {
35 collecttest.TestConfigurationSerialize(t, &Collector{}, dataConfigJSON, dataConfigYAML)
36 }
37
38 func TestCollector_Init(t *testing.T) {
39 tests := map[string]struct {
40 config Config
41 wantFail bool
42 }{
43 "default config": {
44 config: New().Config,
45 },
46 "unset 'address'": {
47 wantFail: true,
48 config: Config{
49 Address: "",
50 },
51 },
52 }
53
54 for name, test := range tests {
55 t.Run(name, func(t *testing.T) {
56 collr := New()
57 collr.Config = test.config
58
59 if test.wantFail {
60 assert.Error(t, collr.Init(context.Background()))
61 } else {
62 assert.NoError(t, collr.Init(context.Background()))
63 }
64 })
65 }
66 }
67
68 func TestCollector_Check(t *testing.T) {
69 tests := map[string]struct {
70 prepare func() *Collector
71 wantFail bool
72 }{
73 "tracking: success, activity: success": {
74 wantFail: false,
75 prepare: func() *Collector { return prepareChronyWithMock(&mockClient{}) },
76 },
77 "tracking: success, activity: fail": {
78 wantFail: true,
79 prepare: func() *Collector { return prepareChronyWithMock(&mockClient{errOnActivity: true}) },
80 },
81 "tracking: fail, activity: success": {
82 wantFail: true,
83 prepare: func() *Collector { return prepareChronyWithMock(&mockClient{errOnTracking: true}) },
84 },
85 "tracking: fail, activity: fail": {
86 wantFail: true,
87 prepare: func() *Collector { return prepareChronyWithMock(&mockClient{errOnTracking: true}) },
88 },
89 "fail on creating client": {
90 wantFail: true,
91 prepare: func() *Collector { return prepareChronyWithMock(nil) },
92 },
93 }
94
95 for name, test := range tests {
96 t.Run(name, func(t *testing.T) {
97 collr := test.prepare()
98
99 require.NoError(t, collr.Init(context.Background()))
100
101 if test.wantFail {
102 assert.Error(t, collr.Check(context.Background()))
103 } else {
104 assert.NoError(t, collr.Check(context.Background()))
105 }
106 })
107 }
108 }
109
110 func TestCollector_Charts(t *testing.T) {
111 assert.Equal(t, len(charts), len(*New().Charts()))
112 }
113
114 func TestCollector_Cleanup(t *testing.T) {
115 tests := map[string]struct {
116 prepare func(c *Collector)
117 wantClose bool
118 }{
119 "after New": {
120 wantClose: false,
121 prepare: func(c *Collector) {},
122 },
123 "after Init": {
124 wantClose: false,
125 prepare: func(c *Collector) { _ = c.Init(context.Background()) },
126 },
127 "after Check": {
128 wantClose: true,
129 prepare: func(c *Collector) { _ = c.Init(context.Background()); _ = c.Check(context.Background()) },
130 },
131 "after Collect": {
132 wantClose: true,
133 prepare: func(c *Collector) { _ = c.Init(context.Background()); _ = c.Collect(context.Background()) },
134 },
135 }
136
137 for name, test := range tests {
138 t.Run(name, func(t *testing.T) {
139 m := &mockClient{}
140 collr := prepareChronyWithMock(m)
141 test.prepare(collr)
142
143 require.NotPanics(t, func() { collr.Cleanup(context.Background()) })
144
145 if test.wantClose {
146 assert.True(t, m.closeCalled)
147 } else {
148 assert.False(t, m.closeCalled)
149 }
150 })
151 }
152 }
153
154 func TestCollector_Collect(t *testing.T) {
155 tests := map[string]struct {
156 prepare func() *Collector
157 expected map[string]int64
158 }{
159 "tracking: success, activity: success, serverstats: success": {
160 prepare: func() *Collector { return prepareChronyWithMock(&mockClient{}) },
161 expected: map[string]int64{
162 "burst_offline_sources": 3,
163 "burst_online_sources": 4,
164 "command_packets_dropped": 1,
165 "command_packets_received": 652,
166 "current_correction": 154872,
167 "frequency": 51051185607,
168 "last_offset": 3095,
169 "leap_status_delete_second": 0,
170 "leap_status_insert_second": 1,
171 "leap_status_normal": 0,
172 "leap_status_unsynchronised": 0,
173 "ntp_packets_dropped": 1,
174 "ntp_packets_received": 1,
175 "offline_sources": 2,
176 "online_sources": 8,
177 "ref_measurement_time": 63793323616,
178 "residual_frequency": -571789,
179 "rms_offset": 130089,
180 "root_delay": 59576179,
181 "root_dispersion": 1089275,
182 "skew": 41821926,
183 "stratum": 4,
184 "unresolved_sources": 1,
185 "update_interval": 1044219238281,
186 },
187 },
188 "tracking: success, activity: fail": {
189 prepare: func() *Collector { return prepareChronyWithMock(&mockClient{errOnActivity: true}) },
190 expected: map[string]int64{
191 "current_correction": 154872,
192 "frequency": 51051185607,
193 "last_offset": 3095,
194 "leap_status_delete_second": 0,
195 "leap_status_insert_second": 1,
196 "leap_status_normal": 0,
197 "leap_status_unsynchronised": 0,
198 "ref_measurement_time": 63793323586,
199 "residual_frequency": -571789,
200 "rms_offset": 130089,
201 "root_delay": 59576179,
202 "root_dispersion": 1089275,
203 "skew": 41821926,
204 "stratum": 4,
205 "update_interval": 1044219238281,
206 },
207 },
208 "tracking: fail, activity: success": {
209 prepare: func() *Collector { return prepareChronyWithMock(&mockClient{errOnTracking: true}) },
210 expected: nil,
211 },
212 "tracking: fail, activity: fail": {
213 prepare: func() *Collector { return prepareChronyWithMock(&mockClient{errOnTracking: true}) },
214 expected: nil,
215 },
216 "fail on creating client": {
217 prepare: func() *Collector { return prepareChronyWithMock(nil) },
218 expected: nil,
219 },
220 }
221
222 for name, test := range tests {
223 t.Run(name, func(t *testing.T) {
224 collr := test.prepare()
225
226 require.NoError(t, collr.Init(context.Background()))
227 collr.exec = &mockChronyc{}
228 _ = collr.Check(context.Background())
229
230 mx := collr.Collect(context.Background())
231 copyRefMeasurementTime(mx, test.expected)
232
233 assert.Equal(t, test.expected, mx)
234 })
235 }
236 }
237
238 func prepareChronyWithMock(m *mockClient) *Collector {
239 c := New()
240 if m == nil {
241 c.newConn = func(_ Config) (chronyConn, error) { return nil, errors.New("mock.newClient error") }
242 } else {
243 c.newConn = func(_ Config) (chronyConn, error) { return m, nil }
244 }
245 return c
246 }
247
248 type mockChronyc struct{}
249
250 func (m *mockChronyc) serverStats() ([]byte, error) {
251 data := `
252 NTP packets received : 1
253 NTP packets dropped : 1
254 Command packets received : 652
255 Command packets dropped : 1
256 Client log records dropped : 1
257 NTS-KE connections accepted: 1
258 NTS-KE connections dropped : 1
259 Authenticated NTP packets : 1
260 Interleaved NTP packets : 1
261 NTP timestamps held : 1
262 NTP timestamp span : 0
263 `
264 return []byte(data), nil
265 }
266
267 type mockClient struct {
268 errOnTracking bool
269 errOnActivity bool
270 errOnServerStats bool
271 closeCalled bool
272 }
273
274 func (m *mockClient) tracking() (*chrony.ReplyTracking, error) {
275 if m.errOnTracking {
276 return nil, errors.New("mockClient.Tracking call error")
277 }
278 reply := chrony.ReplyTracking{
279 Tracking: chrony.Tracking{
280 RefID: 2728380539,
281 IPAddr: net.IP("192.0.2.0"),
282 Stratum: 4,
283 LeapStatus: 1,
284 RefTime: time.Time{},
285 CurrentCorrection: 0.00015487267228309065,
286 LastOffset: 3.0953951863921247e-06,
287 RMSOffset: 0.00013008920359425247,
288 FreqPPM: -51.051185607910156,
289 ResidFreqPPM: -0.0005717896274290979,
290 SkewPPM: 0.0418219268321991,
291 RootDelay: 0.05957617983222008,
292 RootDispersion: 0.0010892755817621946,
293 LastUpdateInterval: 1044.21923828125,
294 },
295 }
296 return &reply, nil
297 }
298
299 func (m *mockClient) activity() (*chrony.ReplyActivity, error) {
300 if m.errOnActivity {
301 return nil, errors.New("mockClient.Activity call error")
302 }
303 reply := chrony.ReplyActivity{
304 Activity: chrony.Activity{
305 Online: 8,
306 Offline: 2,
307 BurstOnline: 4,
308 BurstOffline: 3,
309 Unresolved: 1,
310 },
311 }
312 return &reply, nil
313 }
314
315 func (m *mockClient) close() {
316 m.closeCalled = true
317 }
318
319 func copyRefMeasurementTime(dst, src map[string]int64) {
320 if _, ok := dst["ref_measurement_time"]; !ok {
321 return
322 }
323 if _, ok := src["ref_measurement_time"]; !ok {
324 return
325 }
326 dst["ref_measurement_time"] = src["ref_measurement_time"]
327 }