master
go 355 lines 13.2 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package traefik
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 dataVer221Metrics, _ = os.ReadFile("testdata/v2.2.1/metrics.txt")
25 )
26
27 func Test_testDataIsValid(t *testing.T) {
28 for name, data := range map[string][]byte{
29 "dataConfigJSON": dataConfigJSON,
30 "dataConfigYAML": dataConfigYAML,
31 "dataVer221Metrics": dataVer221Metrics,
32 } {
33 require.NotNil(t, data, name)
34 }
35 }
36
37 func TestCollector_ConfigurationSerialize(t *testing.T) {
38 collecttest.TestConfigurationSerialize(t, &Collector{}, dataConfigJSON, dataConfigYAML)
39 }
40
41 func TestCollector_Init(t *testing.T) {
42 tests := map[string]struct {
43 config Config
44 wantFail bool
45 }{
46 "success on default config": {
47 config: New().Config,
48 },
49 "fails on unset 'url'": {
50 wantFail: true,
51 config: Config{HTTPConfig: web.HTTPConfig{
52 RequestConfig: web.RequestConfig{},
53 }},
54 },
55 "fails on invalid TLSCA": {
56 wantFail: true,
57 config: Config{
58 HTTPConfig: web.HTTPConfig{
59 ClientConfig: web.ClientConfig{
60 TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"},
61 },
62 }},
63 },
64 }
65
66 for name, test := range tests {
67 t.Run(name, func(t *testing.T) {
68 collr := New()
69 collr.Config = test.config
70
71 if test.wantFail {
72 assert.Error(t, collr.Init(context.Background()))
73 } else {
74 assert.NoError(t, collr.Init(context.Background()))
75 }
76 })
77 }
78 }
79
80 func TestCollector_Charts(t *testing.T) {
81 assert.NotNil(t, New().Charts())
82 }
83
84 func TestCollector_Cleanup(t *testing.T) {
85 assert.NotPanics(t, func() { New().Cleanup(context.Background()) })
86 }
87
88 func TestCollector_Check(t *testing.T) {
89 tests := map[string]struct {
90 wantFail bool
91 prepare func(t *testing.T) (collr *Collector, cleanup func())
92 }{
93 "success on valid response v2.3.1": {
94 wantFail: false,
95 prepare: prepareCaseTraefikV221Metrics,
96 },
97 "fails on response with unexpected metrics (not HAProxy)": {
98 wantFail: true,
99 prepare: prepareCaseNotTraefikMetrics,
100 },
101 "fails on 404 response": {
102 wantFail: true,
103 prepare: prepareCase404Response,
104 },
105 "fails on connection refused": {
106 wantFail: true,
107 prepare: prepareCaseConnectionRefused,
108 },
109 }
110
111 for name, test := range tests {
112 t.Run(name, func(t *testing.T) {
113 collr, cleanup := test.prepare(t)
114 defer cleanup()
115
116 if test.wantFail {
117 assert.Error(t, collr.Check(context.Background()))
118 } else {
119 assert.NoError(t, collr.Check(context.Background()))
120 }
121 })
122 }
123 }
124
125 func TestCollector_Collect(t *testing.T) {
126 tests := map[string]struct {
127 prepare func(t *testing.T) (collr *Collector, cleanup func())
128 wantCollected []map[string]int64
129 }{
130 "success on valid response v2.2.1": {
131 prepare: prepareCaseTraefikV221Metrics,
132 wantCollected: []map[string]int64{
133 {
134 "entrypoint_open_connections_traefik_http_GET": 1,
135 "entrypoint_open_connections_web_http_DELETE": 0,
136 "entrypoint_open_connections_web_http_GET": 0,
137 "entrypoint_open_connections_web_http_HEAD": 0,
138 "entrypoint_open_connections_web_http_OPTIONS": 0,
139 "entrypoint_open_connections_web_http_PATCH": 0,
140 "entrypoint_open_connections_web_http_POST": 4,
141 "entrypoint_open_connections_web_http_PUT": 0,
142 "entrypoint_open_connections_web_websocket_GET": 0,
143 "entrypoint_request_duration_average_traefik_http_1xx": 0,
144 "entrypoint_request_duration_average_traefik_http_2xx": 0,
145 "entrypoint_request_duration_average_traefik_http_3xx": 0,
146 "entrypoint_request_duration_average_traefik_http_4xx": 0,
147 "entrypoint_request_duration_average_traefik_http_5xx": 0,
148 "entrypoint_request_duration_average_web_http_1xx": 0,
149 "entrypoint_request_duration_average_web_http_2xx": 0,
150 "entrypoint_request_duration_average_web_http_3xx": 0,
151 "entrypoint_request_duration_average_web_http_4xx": 0,
152 "entrypoint_request_duration_average_web_http_5xx": 0,
153 "entrypoint_request_duration_average_web_websocket_1xx": 0,
154 "entrypoint_request_duration_average_web_websocket_2xx": 0,
155 "entrypoint_request_duration_average_web_websocket_3xx": 0,
156 "entrypoint_request_duration_average_web_websocket_4xx": 0,
157 "entrypoint_request_duration_average_web_websocket_5xx": 0,
158 "entrypoint_requests_traefik_http_1xx": 0,
159 "entrypoint_requests_traefik_http_2xx": 2840814,
160 "entrypoint_requests_traefik_http_3xx": 0,
161 "entrypoint_requests_traefik_http_4xx": 8,
162 "entrypoint_requests_traefik_http_5xx": 0,
163 "entrypoint_requests_web_http_1xx": 0,
164 "entrypoint_requests_web_http_2xx": 1036208982,
165 "entrypoint_requests_web_http_3xx": 416262,
166 "entrypoint_requests_web_http_4xx": 267591379,
167 "entrypoint_requests_web_http_5xx": 223136,
168 "entrypoint_requests_web_websocket_1xx": 0,
169 "entrypoint_requests_web_websocket_2xx": 0,
170 "entrypoint_requests_web_websocket_3xx": 0,
171 "entrypoint_requests_web_websocket_4xx": 79137,
172 "entrypoint_requests_web_websocket_5xx": 0,
173 },
174 },
175 },
176 "properly calculating entrypoint request duration delta": {
177 prepare: prepareCaseTraefikEntrypointRequestDuration,
178 wantCollected: []map[string]int64{
179 {
180 "entrypoint_request_duration_average_traefik_http_1xx": 0,
181 "entrypoint_request_duration_average_traefik_http_2xx": 0,
182 "entrypoint_request_duration_average_traefik_http_3xx": 0,
183 "entrypoint_request_duration_average_traefik_http_4xx": 0,
184 "entrypoint_request_duration_average_traefik_http_5xx": 0,
185 "entrypoint_request_duration_average_web_websocket_1xx": 0,
186 "entrypoint_request_duration_average_web_websocket_2xx": 0,
187 "entrypoint_request_duration_average_web_websocket_3xx": 0,
188 "entrypoint_request_duration_average_web_websocket_4xx": 0,
189 "entrypoint_request_duration_average_web_websocket_5xx": 0,
190 },
191 {
192 "entrypoint_request_duration_average_traefik_http_1xx": 0,
193 "entrypoint_request_duration_average_traefik_http_2xx": 500,
194 "entrypoint_request_duration_average_traefik_http_3xx": 0,
195 "entrypoint_request_duration_average_traefik_http_4xx": 0,
196 "entrypoint_request_duration_average_traefik_http_5xx": 0,
197 "entrypoint_request_duration_average_web_websocket_1xx": 0,
198 "entrypoint_request_duration_average_web_websocket_2xx": 0,
199 "entrypoint_request_duration_average_web_websocket_3xx": 250,
200 "entrypoint_request_duration_average_web_websocket_4xx": 0,
201 "entrypoint_request_duration_average_web_websocket_5xx": 0,
202 },
203 {
204 "entrypoint_request_duration_average_traefik_http_1xx": 0,
205 "entrypoint_request_duration_average_traefik_http_2xx": 1000,
206 "entrypoint_request_duration_average_traefik_http_3xx": 0,
207 "entrypoint_request_duration_average_traefik_http_4xx": 0,
208 "entrypoint_request_duration_average_traefik_http_5xx": 0,
209 "entrypoint_request_duration_average_web_websocket_1xx": 0,
210 "entrypoint_request_duration_average_web_websocket_2xx": 0,
211 "entrypoint_request_duration_average_web_websocket_3xx": 500,
212 "entrypoint_request_duration_average_web_websocket_4xx": 0,
213 "entrypoint_request_duration_average_web_websocket_5xx": 0,
214 },
215 {
216 "entrypoint_request_duration_average_traefik_http_1xx": 0,
217 "entrypoint_request_duration_average_traefik_http_2xx": 0,
218 "entrypoint_request_duration_average_traefik_http_3xx": 0,
219 "entrypoint_request_duration_average_traefik_http_4xx": 0,
220 "entrypoint_request_duration_average_traefik_http_5xx": 0,
221 "entrypoint_request_duration_average_web_websocket_1xx": 0,
222 "entrypoint_request_duration_average_web_websocket_2xx": 0,
223 "entrypoint_request_duration_average_web_websocket_3xx": 0,
224 "entrypoint_request_duration_average_web_websocket_4xx": 0,
225 "entrypoint_request_duration_average_web_websocket_5xx": 0,
226 },
227 },
228 },
229 "fails on response with unexpected metrics (not Traefik)": {
230 prepare: prepareCaseNotTraefikMetrics,
231 },
232 "fails on 404 response": {
233 prepare: prepareCase404Response,
234 },
235 "fails on connection refused": {
236 prepare: prepareCaseConnectionRefused,
237 },
238 }
239
240 for name, test := range tests {
241 t.Run(name, func(t *testing.T) {
242 collr, cleanup := test.prepare(t)
243 defer cleanup()
244
245 var mx map[string]int64
246 for _, want := range test.wantCollected {
247 mx = collr.Collect(context.Background())
248 assert.Equal(t, want, mx)
249 }
250 if len(test.wantCollected) > 0 {
251 collecttest.TestMetricsHasAllChartsDims(t, collr.Charts(), mx)
252 }
253 })
254 }
255 }
256
257 func prepareCaseTraefikV221Metrics(t *testing.T) (*Collector, func()) {
258 t.Helper()
259 srv := httptest.NewServer(http.HandlerFunc(
260 func(w http.ResponseWriter, r *http.Request) {
261 _, _ = w.Write(dataVer221Metrics)
262 }))
263 collr := New()
264 collr.URL = srv.URL
265 require.NoError(t, collr.Init(context.Background()))
266
267 return collr, srv.Close
268 }
269
270 func prepareCaseTraefikEntrypointRequestDuration(t *testing.T) (*Collector, func()) {
271 t.Helper()
272 var num int
273 srv := httptest.NewServer(http.HandlerFunc(
274 func(w http.ResponseWriter, r *http.Request) {
275 num++
276 switch num {
277 case 1:
278 _, _ = w.Write([]byte(`
279 traefik_entrypoint_request_duration_seconds_sum{code="200",entrypoint="traefik",method="GET",protocol="http"} 10.1
280 traefik_entrypoint_request_duration_seconds_sum{code="300",entrypoint="web",method="GET",protocol="websocket"} 20.1
281 traefik_entrypoint_request_duration_seconds_count{code="200",entrypoint="traefik",method="PUT",protocol="http"} 30
282 traefik_entrypoint_request_duration_seconds_count{code="300",entrypoint="web",method="PUT",protocol="websocket"} 40
283 `))
284 case 2:
285 _, _ = w.Write([]byte(`
286 traefik_entrypoint_request_duration_seconds_sum{code="200",entrypoint="traefik",method="GET",protocol="http"} 15.1
287 traefik_entrypoint_request_duration_seconds_sum{code="300",entrypoint="web",method="GET",protocol="websocket"} 25.1
288 traefik_entrypoint_request_duration_seconds_count{code="200",entrypoint="traefik",method="PUT",protocol="http"} 40
289 traefik_entrypoint_request_duration_seconds_count{code="300",entrypoint="web",method="PUT",protocol="websocket"} 60
290 `))
291 default:
292 _, _ = w.Write([]byte(`
293 traefik_entrypoint_request_duration_seconds_sum{code="200",entrypoint="traefik",method="GET",protocol="http"} 25.1
294 traefik_entrypoint_request_duration_seconds_sum{code="300",entrypoint="web",method="GET",protocol="websocket"} 35.1
295 traefik_entrypoint_request_duration_seconds_count{code="200",entrypoint="traefik",method="PUT",protocol="http"} 50
296 traefik_entrypoint_request_duration_seconds_count{code="300",entrypoint="web",method="PUT",protocol="websocket"} 80
297 `))
298 }
299 }))
300 collr := New()
301 collr.URL = srv.URL
302 require.NoError(t, collr.Init(context.Background()))
303
304 return collr, srv.Close
305 }
306
307 func prepareCaseNotTraefikMetrics(t *testing.T) (*Collector, func()) {
308 t.Helper()
309 srv := httptest.NewServer(http.HandlerFunc(
310 func(w http.ResponseWriter, r *http.Request) {
311 _, _ = w.Write([]byte(`
312 # HELP application_backend_http_responses_total Total number of HTTP responses.
313 # TYPE application_backend_http_responses_total counter
314 application_backend_http_responses_total{proxy="infra-traefik-web",code="1xx"} 0
315 application_backend_http_responses_total{proxy="infra-vernemq-ws",code="1xx"} 4130401
316 application_backend_http_responses_total{proxy="infra-traefik-web",code="2xx"} 21338013
317 application_backend_http_responses_total{proxy="infra-vernemq-ws",code="2xx"} 0
318 application_backend_http_responses_total{proxy="infra-traefik-web",code="3xx"} 10004
319 application_backend_http_responses_total{proxy="infra-vernemq-ws",code="3xx"} 0
320 application_backend_http_responses_total{proxy="infra-traefik-web",code="4xx"} 10170758
321 application_backend_http_responses_total{proxy="infra-vernemq-ws",code="4xx"} 0
322 application_backend_http_responses_total{proxy="infra-traefik-web",code="5xx"} 3075
323 application_backend_http_responses_total{proxy="infra-vernemq-ws",code="5xx"} 0
324 application_backend_http_responses_total{proxy="infra-traefik-web",code="other"} 5657
325 application_backend_http_responses_total{proxy="infra-vernemq-ws",code="other"} 0
326 `))
327 }))
328 collr := New()
329 collr.URL = srv.URL
330 require.NoError(t, collr.Init(context.Background()))
331
332 return collr, srv.Close
333 }
334
335 func prepareCase404Response(t *testing.T) (*Collector, func()) {
336 t.Helper()
337 srv := httptest.NewServer(http.HandlerFunc(
338 func(w http.ResponseWriter, r *http.Request) {
339 w.WriteHeader(http.StatusNotFound)
340 }))
341 collr := New()
342 collr.URL = srv.URL
343 require.NoError(t, collr.Init(context.Background()))
344
345 return collr, srv.Close
346 }
347
348 func prepareCaseConnectionRefused(t *testing.T) (*Collector, func()) {
349 t.Helper()
350 collr := New()
351 collr.URL = "http://127.0.0.1:38001"
352 require.NoError(t, collr.Init(context.Background()))
353
354 return collr, func() {}
355 }