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  }