master
go 449 lines 13.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package couchdb
4
5 import (
6 "context"
7 "net/http"
8 "net/http/httptest"
9 "os"
10 "testing"
11
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14
15 "github.com/netdata/netdata/go/plugins/pkg/tlscfg"
16 "github.com/netdata/netdata/go/plugins/pkg/web"
17 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
18 )
19
20 var (
21 dataConfigJSON, _ = os.ReadFile("testdata/config.json")
22 dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
23
24 dataVer311Root, _ = os.ReadFile("testdata/v3.1.1/root.json")
25 dataVer311ActiveTasks, _ = os.ReadFile("testdata/v3.1.1/active_tasks.json")
26 dataVer311NodeStats, _ = os.ReadFile("testdata/v3.1.1/node_stats.json")
27 dataVer311NodeSystem, _ = os.ReadFile("testdata/v3.1.1/node_system.json")
28 dataVer311DbsInfo, _ = os.ReadFile("testdata/v3.1.1/dbs_info.json")
29 )
30
31 func Test_testDataIsValid(t *testing.T) {
32 for name, data := range map[string][]byte{
33 "dataConfigJSON": dataConfigJSON,
34 "dataConfigYAML": dataConfigYAML,
35 "dataVer311Root": dataVer311Root,
36 "dataVer311ActiveTasks": dataVer311ActiveTasks,
37 "dataVer311NodeStats": dataVer311NodeStats,
38 "dataVer311NodeSystem": dataVer311NodeSystem,
39 "dataVer311DbsInfo": dataVer311DbsInfo,
40 } {
41 require.NotNil(t, data, name)
42 }
43 }
44
45 func TestCollector_ConfigurationSerialize(t *testing.T) {
46 collecttest.TestConfigurationSerialize(t, &Collector{}, dataConfigJSON, dataConfigYAML)
47 }
48
49 func TestCollector_Init(t *testing.T) {
50 tests := map[string]struct {
51 config Config
52 wantNumOfCharts int
53 wantFail bool
54 }{
55 "default": {
56 wantNumOfCharts: numOfCharts(
57 dbActivityCharts,
58 httpTrafficBreakdownCharts,
59 serverOperationsCharts,
60 erlangStatisticsCharts,
61 ),
62 config: New().Config,
63 },
64 "URL not set": {
65 wantFail: true,
66 config: Config{
67 HTTPConfig: web.HTTPConfig{
68 RequestConfig: web.RequestConfig{URL: ""},
69 }},
70 },
71 "invalid TLSCA": {
72 wantFail: true,
73 config: Config{
74 HTTPConfig: web.HTTPConfig{
75 ClientConfig: web.ClientConfig{
76 TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"},
77 },
78 }},
79 },
80 }
81
82 for name, test := range tests {
83 t.Run(name, func(t *testing.T) {
84 collr := New()
85 collr.Config = test.config
86
87 if test.wantFail {
88 assert.Error(t, collr.Init(context.Background()))
89 } else {
90 assert.NoError(t, collr.Init(context.Background()))
91 assert.Equal(t, test.wantNumOfCharts, len(*collr.Charts()))
92 }
93 })
94 }
95 }
96
97 func TestCollector_Check(t *testing.T) {
98 tests := map[string]struct {
99 prepare func(*testing.T) (collr *Collector, cleanup func())
100 wantFail bool
101 }{
102 "valid data": {prepare: prepareCouchDBValidData},
103 "invalid data": {prepare: prepareCouchDBInvalidData, wantFail: true},
104 "404": {prepare: prepareCouchDB404, wantFail: true},
105 "connection refused": {prepare: prepareCouchDBConnectionRefused, wantFail: true},
106 }
107
108 for name, test := range tests {
109 t.Run(name, func(t *testing.T) {
110 collr, cleanup := test.prepare(t)
111 defer cleanup()
112
113 if test.wantFail {
114 assert.Error(t, collr.Check(context.Background()))
115 } else {
116 assert.NoError(t, collr.Check(context.Background()))
117 }
118 })
119 }
120 }
121
122 func TestCollector_Charts(t *testing.T) {
123 assert.Nil(t, New().Charts())
124 }
125
126 func TestCollector_Cleanup(t *testing.T) {
127 assert.NotPanics(t, func() { New().Cleanup(context.Background()) })
128 }
129
130 func TestCollector_Collect(t *testing.T) {
131 tests := map[string]struct {
132 prepare func() *Collector
133 wantCollected map[string]int64
134 checkCharts bool
135 }{
136 "all stats": {
137 prepare: func() *Collector {
138 collr := New()
139 collr.Config.Databases = "db1 db2"
140 return collr
141 },
142 wantCollected: map[string]int64{
143
144 // node stats
145 "couch_replicator_jobs_crashed": 1,
146 "couch_replicator_jobs_pending": 1,
147 "couch_replicator_jobs_running": 1,
148 "couchdb_database_reads": 1,
149 "couchdb_database_writes": 14,
150 "couchdb_httpd_request_methods_COPY": 1,
151 "couchdb_httpd_request_methods_DELETE": 1,
152 "couchdb_httpd_request_methods_GET": 75544,
153 "couchdb_httpd_request_methods_HEAD": 1,
154 "couchdb_httpd_request_methods_OPTIONS": 1,
155 "couchdb_httpd_request_methods_POST": 15,
156 "couchdb_httpd_request_methods_PUT": 3,
157 "couchdb_httpd_status_codes_200": 75294,
158 "couchdb_httpd_status_codes_201": 15,
159 "couchdb_httpd_status_codes_202": 1,
160 "couchdb_httpd_status_codes_204": 1,
161 "couchdb_httpd_status_codes_206": 1,
162 "couchdb_httpd_status_codes_301": 1,
163 "couchdb_httpd_status_codes_302": 1,
164 "couchdb_httpd_status_codes_304": 1,
165 "couchdb_httpd_status_codes_400": 1,
166 "couchdb_httpd_status_codes_401": 20,
167 "couchdb_httpd_status_codes_403": 1,
168 "couchdb_httpd_status_codes_404": 225,
169 "couchdb_httpd_status_codes_405": 1,
170 "couchdb_httpd_status_codes_406": 1,
171 "couchdb_httpd_status_codes_409": 1,
172 "couchdb_httpd_status_codes_412": 3,
173 "couchdb_httpd_status_codes_413": 1,
174 "couchdb_httpd_status_codes_414": 1,
175 "couchdb_httpd_status_codes_415": 1,
176 "couchdb_httpd_status_codes_416": 1,
177 "couchdb_httpd_status_codes_417": 1,
178 "couchdb_httpd_status_codes_500": 1,
179 "couchdb_httpd_status_codes_501": 1,
180 "couchdb_httpd_status_codes_503": 1,
181 "couchdb_httpd_status_codes_2xx": 75312,
182 "couchdb_httpd_status_codes_3xx": 3,
183 "couchdb_httpd_status_codes_4xx": 258,
184 "couchdb_httpd_status_codes_5xx": 3,
185 "couchdb_httpd_view_reads": 1,
186 "couchdb_open_os_files": 1,
187
188 // node system
189 "context_switches": 22614499,
190 "ets_table_count": 116,
191 "internal_replication_jobs": 1,
192 "io_input": 49674812,
193 "io_output": 686400800,
194 "memory_atom_used": 488328,
195 "memory_atom": 504433,
196 "memory_binary": 297696,
197 "memory_code": 11252688,
198 "memory_ets": 1579120,
199 "memory_other": 20427855,
200 "memory_processes": 9161448,
201 "os_proc_count": 1,
202 "peak_msg_queue": 2,
203 "process_count": 296,
204 "reductions": 43211228312,
205 "run_queue": 1,
206
207 // active tasks
208 "active_tasks_database_compaction": 1,
209 "active_tasks_indexer": 2,
210 "active_tasks_replication": 1,
211 "active_tasks_view_compaction": 1,
212
213 // databases
214 "db_db1_db_doc_counts": 14,
215 "db_db1_db_doc_del_counts": 1,
216 "db_db1_db_sizes_active": 2818,
217 "db_db1_db_sizes_external": 588,
218 "db_db1_db_sizes_file": 74115,
219
220 "db_db2_db_doc_counts": 15,
221 "db_db2_db_doc_del_counts": 1,
222 "db_db2_db_sizes_active": 1818,
223 "db_db2_db_sizes_external": 288,
224 "db_db2_db_sizes_file": 7415,
225 },
226 checkCharts: true,
227 },
228 "wrong node": {
229 prepare: func() *Collector {
230 collr := New()
231 collr.Config.Node = "bad_node@bad_host"
232 collr.Config.Databases = "db1 db2"
233 return collr
234 },
235 wantCollected: map[string]int64{
236
237 // node stats
238
239 // node system
240
241 // active tasks
242 "active_tasks_database_compaction": 1,
243 "active_tasks_indexer": 2,
244 "active_tasks_replication": 1,
245 "active_tasks_view_compaction": 1,
246
247 // databases
248 "db_db1_db_doc_counts": 14,
249 "db_db1_db_doc_del_counts": 1,
250 "db_db1_db_sizes_active": 2818,
251 "db_db1_db_sizes_external": 588,
252 "db_db1_db_sizes_file": 74115,
253
254 "db_db2_db_doc_counts": 15,
255 "db_db2_db_doc_del_counts": 1,
256 "db_db2_db_sizes_active": 1818,
257 "db_db2_db_sizes_external": 288,
258 "db_db2_db_sizes_file": 7415,
259 },
260 checkCharts: false,
261 },
262 "wrong database": {
263 prepare: func() *Collector {
264 collr := New()
265 collr.Config.Databases = "bad_db db1 db2"
266 return collr
267 },
268 wantCollected: map[string]int64{
269
270 // node stats
271 "couch_replicator_jobs_crashed": 1,
272 "couch_replicator_jobs_pending": 1,
273 "couch_replicator_jobs_running": 1,
274 "couchdb_database_reads": 1,
275 "couchdb_database_writes": 14,
276 "couchdb_httpd_request_methods_COPY": 1,
277 "couchdb_httpd_request_methods_DELETE": 1,
278 "couchdb_httpd_request_methods_GET": 75544,
279 "couchdb_httpd_request_methods_HEAD": 1,
280 "couchdb_httpd_request_methods_OPTIONS": 1,
281 "couchdb_httpd_request_methods_POST": 15,
282 "couchdb_httpd_request_methods_PUT": 3,
283 "couchdb_httpd_status_codes_200": 75294,
284 "couchdb_httpd_status_codes_201": 15,
285 "couchdb_httpd_status_codes_202": 1,
286 "couchdb_httpd_status_codes_204": 1,
287 "couchdb_httpd_status_codes_206": 1,
288 "couchdb_httpd_status_codes_301": 1,
289 "couchdb_httpd_status_codes_302": 1,
290 "couchdb_httpd_status_codes_304": 1,
291 "couchdb_httpd_status_codes_400": 1,
292 "couchdb_httpd_status_codes_401": 20,
293 "couchdb_httpd_status_codes_403": 1,
294 "couchdb_httpd_status_codes_404": 225,
295 "couchdb_httpd_status_codes_405": 1,
296 "couchdb_httpd_status_codes_406": 1,
297 "couchdb_httpd_status_codes_409": 1,
298 "couchdb_httpd_status_codes_412": 3,
299 "couchdb_httpd_status_codes_413": 1,
300 "couchdb_httpd_status_codes_414": 1,
301 "couchdb_httpd_status_codes_415": 1,
302 "couchdb_httpd_status_codes_416": 1,
303 "couchdb_httpd_status_codes_417": 1,
304 "couchdb_httpd_status_codes_500": 1,
305 "couchdb_httpd_status_codes_501": 1,
306 "couchdb_httpd_status_codes_503": 1,
307 "couchdb_httpd_status_codes_2xx": 75312,
308 "couchdb_httpd_status_codes_3xx": 3,
309 "couchdb_httpd_status_codes_4xx": 258,
310 "couchdb_httpd_status_codes_5xx": 3,
311 "couchdb_httpd_view_reads": 1,
312 "couchdb_open_os_files": 1,
313
314 // node system
315 "context_switches": 22614499,
316 "ets_table_count": 116,
317 "internal_replication_jobs": 1,
318 "io_input": 49674812,
319 "io_output": 686400800,
320 "memory_atom_used": 488328,
321 "memory_atom": 504433,
322 "memory_binary": 297696,
323 "memory_code": 11252688,
324 "memory_ets": 1579120,
325 "memory_other": 20427855,
326 "memory_processes": 9161448,
327 "os_proc_count": 1,
328 "peak_msg_queue": 2,
329 "process_count": 296,
330 "reductions": 43211228312,
331 "run_queue": 1,
332
333 // active tasks
334 "active_tasks_database_compaction": 1,
335 "active_tasks_indexer": 2,
336 "active_tasks_replication": 1,
337 "active_tasks_view_compaction": 1,
338
339 // databases
340 "db_db1_db_doc_counts": 14,
341 "db_db1_db_doc_del_counts": 1,
342 "db_db1_db_sizes_active": 2818,
343 "db_db1_db_sizes_external": 588,
344 "db_db1_db_sizes_file": 74115,
345
346 "db_db2_db_doc_counts": 15,
347 "db_db2_db_doc_del_counts": 1,
348 "db_db2_db_sizes_active": 1818,
349 "db_db2_db_sizes_external": 288,
350 "db_db2_db_sizes_file": 7415,
351 },
352 checkCharts: false,
353 },
354 }
355
356 for name, test := range tests {
357 t.Run(name, func(t *testing.T) {
358 collr, cleanup := prepareCouchDB(t, test.prepare)
359 defer cleanup()
360
361 var mx map[string]int64
362 for range 10 {
363 mx = collr.Collect(context.Background())
364 }
365
366 assert.Equal(t, test.wantCollected, mx)
367 if test.checkCharts {
368 collecttest.TestMetricsHasAllChartsDims(t, collr.Charts(), mx)
369 }
370 })
371 }
372 }
373
374 func prepareCouchDB(t *testing.T, createCDB func() *Collector) (collr *Collector, cleanup func()) {
375 t.Helper()
376 collr = createCDB()
377 srv := prepareCouchDBEndpoint()
378 collr.URL = srv.URL
379
380 require.NoError(t, collr.Init(context.Background()))
381
382 return collr, srv.Close
383 }
384
385 func prepareCouchDBValidData(t *testing.T) (collr *Collector, cleanup func()) {
386 return prepareCouchDB(t, New)
387 }
388
389 func prepareCouchDBInvalidData(t *testing.T) (*Collector, func()) {
390 t.Helper()
391 srv := httptest.NewServer(http.HandlerFunc(
392 func(w http.ResponseWriter, r *http.Request) {
393 _, _ = w.Write([]byte("hello and\n goodbye"))
394 }))
395 collr := New()
396 collr.URL = srv.URL
397 require.NoError(t, collr.Init(context.Background()))
398
399 return collr, srv.Close
400 }
401
402 func prepareCouchDB404(t *testing.T) (*Collector, func()) {
403 t.Helper()
404 srv := httptest.NewServer(http.HandlerFunc(
405 func(w http.ResponseWriter, r *http.Request) {
406 w.WriteHeader(http.StatusNotFound)
407 }))
408 collr := New()
409 collr.URL = srv.URL
410 require.NoError(t, collr.Init(context.Background()))
411
412 return collr, srv.Close
413 }
414
415 func prepareCouchDBConnectionRefused(t *testing.T) (*Collector, func()) {
416 t.Helper()
417 collr := New()
418 collr.URL = "http://127.0.0.1:38001"
419 require.NoError(t, collr.Init(context.Background()))
420
421 return collr, func() {}
422 }
423
424 func prepareCouchDBEndpoint() *httptest.Server {
425 return httptest.NewServer(http.HandlerFunc(
426 func(w http.ResponseWriter, r *http.Request) {
427 switch r.URL.Path {
428 case "/_node/_local/_stats":
429 _, _ = w.Write(dataVer311NodeStats)
430 case "/_node/_local/_system":
431 _, _ = w.Write(dataVer311NodeSystem)
432 case urlPathActiveTasks:
433 _, _ = w.Write(dataVer311ActiveTasks)
434 case "/_dbs_info":
435 _, _ = w.Write(dataVer311DbsInfo)
436 case "/":
437 _, _ = w.Write(dataVer311Root)
438 default:
439 w.WriteHeader(http.StatusNotFound)
440 }
441 }))
442 }
443
444 func numOfCharts(charts ...Charts) (num int) {
445 for _, v := range charts {
446 num += len(v)
447 }
448 return num
449 }