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