github.com/netdata/go.d.plugin@v0.58.1/modules/powerdns_recursor/recursor_test.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package powerdns_recursor
     4  
     5  import (
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"os"
     9  	"testing"
    10  
    11  	"github.com/netdata/go.d.plugin/pkg/tlscfg"
    12  	"github.com/netdata/go.d.plugin/pkg/web"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  var (
    19  	v431statistics, _          = os.ReadFile("testdata/v4.3.1/statistics.json")
    20  	authoritativeStatistics, _ = os.ReadFile("testdata/authoritative/statistics.json")
    21  )
    22  
    23  func Test_testDataIsCorrectlyReadAndValid(t *testing.T) {
    24  	for name, data := range map[string][]byte{
    25  		"v431statistics":          v431statistics,
    26  		"authoritativeStatistics": authoritativeStatistics,
    27  	} {
    28  		require.NotNilf(t, data, name)
    29  	}
    30  }
    31  
    32  func TestNew(t *testing.T) {
    33  	assert.IsType(t, (*Recursor)(nil), New())
    34  }
    35  
    36  func TestRecursor_Init(t *testing.T) {
    37  	tests := map[string]struct {
    38  		config   Config
    39  		wantFail bool
    40  	}{
    41  		"success on default config": {
    42  			config: New().Config,
    43  		},
    44  		"fails on unset URL": {
    45  			wantFail: true,
    46  			config: Config{
    47  				HTTP: web.HTTP{
    48  					Request: web.Request{URL: ""},
    49  				},
    50  			},
    51  		},
    52  		"fails on invalid TLSCA": {
    53  			wantFail: true,
    54  			config: Config{
    55  				HTTP: web.HTTP{
    56  					Request: web.Request{
    57  						URL: "http://127.0.0.1:38001",
    58  					},
    59  					Client: web.Client{
    60  						TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"},
    61  					},
    62  				},
    63  			},
    64  		},
    65  	}
    66  
    67  	for name, test := range tests {
    68  		t.Run(name, func(t *testing.T) {
    69  			recursor := New()
    70  			recursor.Config = test.config
    71  
    72  			if test.wantFail {
    73  				assert.False(t, recursor.Init())
    74  			} else {
    75  				assert.True(t, recursor.Init())
    76  			}
    77  		})
    78  	}
    79  }
    80  
    81  func TestRecursor_Check(t *testing.T) {
    82  	tests := map[string]struct {
    83  		prepare  func() (r *Recursor, cleanup func())
    84  		wantFail bool
    85  	}{
    86  		"success on valid response v4.3.1": {
    87  			prepare: preparePowerDNSRecursorV431,
    88  		},
    89  		"fails on response from PowerDNS Authoritative Server": {
    90  			wantFail: true,
    91  			prepare:  preparePowerDNSRecursorAuthoritativeData,
    92  		},
    93  		"fails on 404 response": {
    94  			wantFail: true,
    95  			prepare:  preparePowerDNSRecursor404,
    96  		},
    97  		"fails on connection refused": {
    98  			wantFail: true,
    99  			prepare:  preparePowerDNSRecursorConnectionRefused,
   100  		},
   101  		"fails on response with invalid data": {
   102  			wantFail: true,
   103  			prepare:  preparePowerDNSRecursorInvalidData,
   104  		},
   105  	}
   106  
   107  	for name, test := range tests {
   108  		t.Run(name, func(t *testing.T) {
   109  			recursor, cleanup := test.prepare()
   110  			defer cleanup()
   111  			require.True(t, recursor.Init())
   112  
   113  			if test.wantFail {
   114  				assert.False(t, recursor.Check())
   115  			} else {
   116  				assert.True(t, recursor.Check())
   117  			}
   118  		})
   119  	}
   120  }
   121  
   122  func TestRecursor_Charts(t *testing.T) {
   123  	recursor := New()
   124  	require.True(t, recursor.Init())
   125  	assert.NotNil(t, recursor.Charts())
   126  }
   127  
   128  func TestRecursor_Cleanup(t *testing.T) {
   129  	assert.NotPanics(t, New().Cleanup)
   130  }
   131  
   132  func TestRecursor_Collect(t *testing.T) {
   133  	tests := map[string]struct {
   134  		prepare       func() (r *Recursor, cleanup func())
   135  		wantCollected map[string]int64
   136  	}{
   137  		"success on valid response v4.3.1": {
   138  			prepare: preparePowerDNSRecursorV431,
   139  			wantCollected: map[string]int64{
   140  				"all-outqueries":                41,
   141  				"answers-slow":                  1,
   142  				"answers0-1":                    1,
   143  				"answers1-10":                   1,
   144  				"answers10-100":                 1,
   145  				"answers100-1000":               1,
   146  				"auth-zone-queries":             1,
   147  				"auth4-answers-slow":            1,
   148  				"auth4-answers0-1":              1,
   149  				"auth4-answers1-10":             5,
   150  				"auth4-answers10-100":           35,
   151  				"auth4-answers100-1000":         1,
   152  				"auth6-answers-slow":            1,
   153  				"auth6-answers0-1":              1,
   154  				"auth6-answers1-10":             1,
   155  				"auth6-answers10-100":           1,
   156  				"auth6-answers100-1000":         1,
   157  				"cache-entries":                 171,
   158  				"cache-hits":                    1,
   159  				"cache-misses":                  1,
   160  				"case-mismatches":               1,
   161  				"chain-resends":                 1,
   162  				"client-parse-errors":           1,
   163  				"concurrent-queries":            1,
   164  				"cpu-msec-thread-0":             439,
   165  				"cpu-msec-thread-1":             445,
   166  				"cpu-msec-thread-2":             466,
   167  				"dlg-only-drops":                1,
   168  				"dnssec-authentic-data-queries": 1,
   169  				"dnssec-check-disabled-queries": 1,
   170  				"dnssec-queries":                1,
   171  				"dnssec-result-bogus":           1,
   172  				"dnssec-result-indeterminate":   1,
   173  				"dnssec-result-insecure":        1,
   174  				"dnssec-result-nta":             1,
   175  				"dnssec-result-secure":          5,
   176  				"dnssec-validations":            5,
   177  				"dont-outqueries":               1,
   178  				"ecs-queries":                   1,
   179  				"ecs-responses":                 1,
   180  				"edns-ping-matches":             1,
   181  				"edns-ping-mismatches":          1,
   182  				"empty-queries":                 1,
   183  				"failed-host-entries":           1,
   184  				"fd-usage":                      32,
   185  				"ignored-packets":               1,
   186  				"ipv6-outqueries":               1,
   187  				"ipv6-questions":                1,
   188  				"malloc-bytes":                  1,
   189  				"max-cache-entries":             1000000,
   190  				"max-mthread-stack":             1,
   191  				"max-packetcache-entries":       500000,
   192  				"negcache-entries":              1,
   193  				"no-packet-error":               1,
   194  				"noedns-outqueries":             1,
   195  				"noerror-answers":               1,
   196  				"noping-outqueries":             1,
   197  				"nsset-invalidations":           1,
   198  				"nsspeeds-entries":              78,
   199  				"nxdomain-answers":              1,
   200  				"outgoing-timeouts":             1,
   201  				"outgoing4-timeouts":            1,
   202  				"outgoing6-timeouts":            1,
   203  				"over-capacity-drops":           1,
   204  				"packetcache-entries":           1,
   205  				"packetcache-hits":              1,
   206  				"packetcache-misses":            1,
   207  				"policy-drops":                  1,
   208  				"policy-result-custom":          1,
   209  				"policy-result-drop":            1,
   210  				"policy-result-noaction":        1,
   211  				"policy-result-nodata":          1,
   212  				"policy-result-nxdomain":        1,
   213  				"policy-result-truncate":        1,
   214  				"qa-latency":                    1,
   215  				"qname-min-fallback-success":    1,
   216  				"query-pipe-full-drops":         1,
   217  				"questions":                     1,
   218  				"real-memory-usage":             44773376,
   219  				"rebalanced-queries":            1,
   220  				"resource-limits":               1,
   221  				"security-status":               3,
   222  				"server-parse-errors":           1,
   223  				"servfail-answers":              1,
   224  				"spoof-prevents":                1,
   225  				"sys-msec":                      1520,
   226  				"tcp-client-overflow":           1,
   227  				"tcp-clients":                   1,
   228  				"tcp-outqueries":                1,
   229  				"tcp-questions":                 1,
   230  				"throttle-entries":              1,
   231  				"throttled-out":                 1,
   232  				"throttled-outqueries":          1,
   233  				"too-old-drops":                 1,
   234  				"truncated-drops":               1,
   235  				"udp-in-errors":                 1,
   236  				"udp-noport-errors":             1,
   237  				"udp-recvbuf-errors":            1,
   238  				"udp-sndbuf-errors":             1,
   239  				"unauthorized-tcp":              1,
   240  				"unauthorized-udp":              1,
   241  				"unexpected-packets":            1,
   242  				"unreachables":                  1,
   243  				"uptime":                        1624,
   244  				"user-msec":                     465,
   245  				"variable-responses":            1,
   246  				"x-our-latency":                 1,
   247  				"x-ourtime-slow":                1,
   248  				"x-ourtime0-1":                  1,
   249  				"x-ourtime1-2":                  1,
   250  				"x-ourtime16-32":                1,
   251  				"x-ourtime2-4":                  1,
   252  				"x-ourtime4-8":                  1,
   253  				"x-ourtime8-16":                 1,
   254  			},
   255  		},
   256  		"fails on response from PowerDNS Authoritative Server": {
   257  			prepare: preparePowerDNSRecursorAuthoritativeData,
   258  		},
   259  		"fails on 404 response": {
   260  			prepare: preparePowerDNSRecursor404,
   261  		},
   262  		"fails on connection refused": {
   263  			prepare: preparePowerDNSRecursorConnectionRefused,
   264  		},
   265  		"fails on response with invalid data": {
   266  			prepare: preparePowerDNSRecursorInvalidData,
   267  		},
   268  	}
   269  
   270  	for name, test := range tests {
   271  		t.Run(name, func(t *testing.T) {
   272  			recursor, cleanup := test.prepare()
   273  			defer cleanup()
   274  			require.True(t, recursor.Init())
   275  
   276  			collected := recursor.Collect()
   277  
   278  			assert.Equal(t, test.wantCollected, collected)
   279  			if len(test.wantCollected) > 0 {
   280  				ensureCollectedHasAllChartsDimsVarsIDs(t, recursor, collected)
   281  			}
   282  		})
   283  	}
   284  }
   285  
   286  func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, rec *Recursor, collected map[string]int64) {
   287  	for _, chart := range *rec.Charts() {
   288  		if chart.Obsolete {
   289  			continue
   290  		}
   291  		for _, dim := range chart.Dims {
   292  			_, ok := collected[dim.ID]
   293  			assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", dim.ID, chart.ID)
   294  		}
   295  		for _, v := range chart.Vars {
   296  			_, ok := collected[v.ID]
   297  			assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", v.ID, chart.ID)
   298  		}
   299  	}
   300  }
   301  
   302  func preparePowerDNSRecursorV431() (*Recursor, func()) {
   303  	srv := preparePowerDNSRecursorEndpoint()
   304  	recursor := New()
   305  	recursor.URL = srv.URL
   306  
   307  	return recursor, srv.Close
   308  }
   309  
   310  func preparePowerDNSRecursorAuthoritativeData() (*Recursor, func()) {
   311  	srv := preparePowerDNSAuthoritativeEndpoint()
   312  	recursor := New()
   313  	recursor.URL = srv.URL
   314  
   315  	return recursor, srv.Close
   316  }
   317  
   318  func preparePowerDNSRecursorInvalidData() (*Recursor, func()) {
   319  	srv := httptest.NewServer(http.HandlerFunc(
   320  		func(w http.ResponseWriter, r *http.Request) {
   321  			_, _ = w.Write([]byte("hello and\n goodbye"))
   322  		}))
   323  	recursor := New()
   324  	recursor.URL = srv.URL
   325  
   326  	return recursor, srv.Close
   327  }
   328  
   329  func preparePowerDNSRecursor404() (*Recursor, func()) {
   330  	srv := httptest.NewServer(http.HandlerFunc(
   331  		func(w http.ResponseWriter, r *http.Request) {
   332  			w.WriteHeader(http.StatusNotFound)
   333  		}))
   334  	recursor := New()
   335  	recursor.URL = srv.URL
   336  
   337  	return recursor, srv.Close
   338  }
   339  
   340  func preparePowerDNSRecursorConnectionRefused() (*Recursor, func()) {
   341  	recursor := New()
   342  	recursor.URL = "http://127.0.0.1:38001"
   343  
   344  	return recursor, func() {}
   345  }
   346  
   347  func preparePowerDNSRecursorEndpoint() *httptest.Server {
   348  	return httptest.NewServer(http.HandlerFunc(
   349  		func(w http.ResponseWriter, r *http.Request) {
   350  			switch r.URL.Path {
   351  			case urlPathLocalStatistics:
   352  				_, _ = w.Write(v431statistics)
   353  			default:
   354  				w.WriteHeader(http.StatusNotFound)
   355  			}
   356  		}))
   357  }
   358  
   359  func preparePowerDNSAuthoritativeEndpoint() *httptest.Server {
   360  	return httptest.NewServer(http.HandlerFunc(
   361  		func(w http.ResponseWriter, r *http.Request) {
   362  			switch r.URL.Path {
   363  			case urlPathLocalStatistics:
   364  				_, _ = w.Write(authoritativeStatistics)
   365  			default:
   366  				w.WriteHeader(http.StatusNotFound)
   367  			}
   368  		}))
   369  }