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