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  }