master
go 263 lines 5.86 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package dnsmasq
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "os"
10 "testing"
11 "time"
12
13 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
14
15 "github.com/miekg/dns"
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 require.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 "success on default config": {
44 config: New().Config,
45 },
46 "fails on unset 'address'": {
47 wantFail: true,
48 config: Config{
49 Protocol: "udp",
50 Address: "",
51 },
52 },
53 "fails on unset 'protocol'": {
54 wantFail: true,
55 config: Config{
56 Protocol: "",
57 Address: "127.0.0.1:53",
58 },
59 },
60 "fails on invalid 'protocol'": {
61 wantFail: true,
62 config: Config{
63 Protocol: "http",
64 Address: "127.0.0.1:53",
65 },
66 },
67 }
68
69 for name, test := range tests {
70 t.Run(name, func(t *testing.T) {
71 collr := New()
72 collr.Config = test.config
73
74 if test.wantFail {
75 assert.Error(t, collr.Init(context.Background()))
76 } else {
77 assert.NoError(t, collr.Init(context.Background()))
78 }
79 })
80 }
81 }
82
83 func TestCollector_Check(t *testing.T) {
84 tests := map[string]struct {
85 prepare func() *Collector
86 wantFail bool
87 }{
88 "success on valid response": {
89 prepare: prepareOKDnsmasq,
90 },
91 "fails on error on cache stats query": {
92 wantFail: true,
93 prepare: prepareErrorOnExchangeDnsmasq,
94 },
95 "fails on response rcode is not success": {
96 wantFail: true,
97 prepare: prepareRcodeServerFailureOnExchangeDnsmasq,
98 },
99 }
100
101 for name, test := range tests {
102 t.Run(name, func(t *testing.T) {
103 collr := test.prepare()
104 require.NoError(t, collr.Init(context.Background()))
105
106 if test.wantFail {
107 assert.Error(t, collr.Check(context.Background()))
108 } else {
109 assert.NoError(t, collr.Check(context.Background()))
110 }
111 })
112 }
113 }
114
115 func TestCollector_Charts(t *testing.T) {
116 collr := New()
117 require.NoError(t, collr.Init(context.Background()))
118 assert.NotNil(t, collr.Charts())
119 }
120
121 func TestCollector_Cleanup(t *testing.T) {
122 assert.NotPanics(t, func() { New().Cleanup(context.Background()) })
123 }
124
125 func TestCollector_Collect(t *testing.T) {
126 tests := map[string]struct {
127 prepare func() *Collector
128 wantCollected map[string]int64
129 }{
130 "success on valid response": {
131 prepare: prepareOKDnsmasq,
132 wantCollected: map[string]int64{
133 //"auth": 5,
134 "cachesize": 999,
135 "evictions": 5,
136 "failed_queries": 9,
137 "hits": 100,
138 "insertions": 10,
139 "misses": 50,
140 "queries": 17,
141 },
142 },
143 "fails on error on cache stats query": {
144 prepare: prepareErrorOnExchangeDnsmasq,
145 },
146 "fails on response rcode is not success": {
147 prepare: prepareRcodeServerFailureOnExchangeDnsmasq,
148 },
149 }
150
151 for name, test := range tests {
152 t.Run(name, func(t *testing.T) {
153 collr := test.prepare()
154 require.NoError(t, collr.Init(context.Background()))
155
156 mx := collr.Collect(context.Background())
157
158 assert.Equal(t, test.wantCollected, mx)
159 if len(test.wantCollected) > 0 {
160 collecttest.TestMetricsHasAllChartsDims(t, collr.Charts(), mx)
161 }
162 })
163 }
164 }
165
166 func prepareOKDnsmasq() *Collector {
167 collr := New()
168 collr.newDNSClient = func(network string, timeout time.Duration) dnsClient {
169 return &mockDNSClient{}
170 }
171 return collr
172 }
173
174 func prepareErrorOnExchangeDnsmasq() *Collector {
175 collr := New()
176 collr.newDNSClient = func(network string, timeout time.Duration) dnsClient {
177 return &mockDNSClient{
178 errOnExchange: true,
179 }
180 }
181 return collr
182 }
183
184 func prepareRcodeServerFailureOnExchangeDnsmasq() *Collector {
185 collr := New()
186 collr.newDNSClient = func(network string, timeout time.Duration) dnsClient {
187 return &mockDNSClient{
188 rcodeServerFailureOnExchange: true,
189 }
190 }
191 return collr
192 }
193
194 type mockDNSClient struct {
195 errOnExchange bool
196 rcodeServerFailureOnExchange bool
197 }
198
199 func (m mockDNSClient) Exchange(msg *dns.Msg, _ string) (*dns.Msg, time.Duration, error) {
200 if m.errOnExchange {
201 return nil, 0, errors.New("'Exchange' error")
202 }
203 if m.rcodeServerFailureOnExchange {
204 resp := &dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}}
205 return resp, 0, nil
206 }
207
208 var answers []dns.RR
209 for _, q := range msg.Question {
210 a, err := prepareDNSAnswer(q)
211 if err != nil {
212 return nil, 0, err
213 }
214 answers = append(answers, a)
215 }
216
217 resp := &dns.Msg{
218 MsgHdr: dns.MsgHdr{
219 Rcode: dns.RcodeSuccess,
220 },
221 Answer: answers,
222 }
223 return resp, 0, nil
224 }
225
226 func prepareDNSAnswer(q dns.Question) (dns.RR, error) {
227 if want, got := dns.TypeToString[dns.TypeTXT], dns.TypeToString[q.Qtype]; want != got {
228 return nil, fmt.Errorf("unexpected Qtype, want=%s, got=%s", want, got)
229 }
230 if want, got := dns.ClassToString[dns.ClassCHAOS], dns.ClassToString[q.Qclass]; want != got {
231 return nil, fmt.Errorf("unexpected Qclass, want=%s, got=%s", want, got)
232 }
233
234 var txt []string
235 switch q.Name {
236 case "cachesize.bind.":
237 txt = []string{"999"}
238 case "insertions.bind.":
239 txt = []string{"10"}
240 case "evictions.bind.":
241 txt = []string{"5"}
242 case "hits.bind.":
243 txt = []string{"100"}
244 case "misses.bind.":
245 txt = []string{"50"}
246 case "auth.bind.":
247 txt = []string{"5"}
248 case "servers.bind.":
249 txt = []string{"10.0.0.1#53 10 5", "1.1.1.1#53 4 3", "1.0.0.1#53 3 1"}
250 default:
251 return nil, fmt.Errorf("unexpected question Name: %s", q.Name)
252 }
253
254 rr := &dns.TXT{
255 Hdr: dns.RR_Header{
256 Name: q.Name,
257 Rrtype: dns.TypeTXT,
258 Class: dns.ClassCHAOS,
259 },
260 Txt: txt,
261 }
262 return rr, nil
263 }