github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/tools/querytee/proxy_endpoint_test.go (about)

     1  package querytee
     2  
     3  import (
     4  	"io"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"net/url"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/go-kit/log"
    14  	"github.com/pkg/errors"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  	"go.uber.org/atomic"
    18  )
    19  
    20  func Test_ProxyEndpoint_waitBackendResponseForDownstream(t *testing.T) {
    21  	backendURL1, err := url.Parse("http://backend-1/")
    22  	require.NoError(t, err)
    23  	backendURL2, err := url.Parse("http://backend-2/")
    24  	require.NoError(t, err)
    25  	backendURL3, err := url.Parse("http://backend-3/")
    26  	require.NoError(t, err)
    27  
    28  	backendPref := NewProxyBackend("backend-1", backendURL1, time.Second, true)
    29  	backendOther1 := NewProxyBackend("backend-2", backendURL2, time.Second, false)
    30  	backendOther2 := NewProxyBackend("backend-3", backendURL3, time.Second, false)
    31  
    32  	tests := map[string]struct {
    33  		backends  []*ProxyBackend
    34  		responses []*backendResponse
    35  		expected  *ProxyBackend
    36  	}{
    37  		"the preferred backend is the 1st response received": {
    38  			backends: []*ProxyBackend{backendPref, backendOther1},
    39  			responses: []*backendResponse{
    40  				{backend: backendPref, status: 200},
    41  			},
    42  			expected: backendPref,
    43  		},
    44  		"the preferred backend is the last response received": {
    45  			backends: []*ProxyBackend{backendPref, backendOther1},
    46  			responses: []*backendResponse{
    47  				{backend: backendOther1, status: 200},
    48  				{backend: backendPref, status: 200},
    49  			},
    50  			expected: backendPref,
    51  		},
    52  		"the preferred backend is the last response received but it's not successful": {
    53  			backends: []*ProxyBackend{backendPref, backendOther1},
    54  			responses: []*backendResponse{
    55  				{backend: backendOther1, status: 200},
    56  				{backend: backendPref, status: 500},
    57  			},
    58  			expected: backendOther1,
    59  		},
    60  		"the preferred backend is the 2nd response received but only the last one is successful": {
    61  			backends: []*ProxyBackend{backendPref, backendOther1, backendOther2},
    62  			responses: []*backendResponse{
    63  				{backend: backendOther1, status: 500},
    64  				{backend: backendPref, status: 500},
    65  				{backend: backendOther2, status: 200},
    66  			},
    67  			expected: backendOther2,
    68  		},
    69  		"there's no preferred backend configured and the 1st response is successful": {
    70  			backends: []*ProxyBackend{backendOther1, backendOther2},
    71  			responses: []*backendResponse{
    72  				{backend: backendOther1, status: 200},
    73  			},
    74  			expected: backendOther1,
    75  		},
    76  		"there's no preferred backend configured and the last response is successful": {
    77  			backends: []*ProxyBackend{backendOther1, backendOther2},
    78  			responses: []*backendResponse{
    79  				{backend: backendOther1, status: 500},
    80  				{backend: backendOther2, status: 200},
    81  			},
    82  			expected: backendOther2,
    83  		},
    84  		"no received response is successful": {
    85  			backends: []*ProxyBackend{backendPref, backendOther1},
    86  			responses: []*backendResponse{
    87  				{backend: backendOther1, status: 500},
    88  				{backend: backendPref, status: 500},
    89  			},
    90  			expected: backendOther1,
    91  		},
    92  	}
    93  
    94  	for testName, testData := range tests {
    95  		testData := testData
    96  
    97  		t.Run(testName, func(t *testing.T) {
    98  			endpoint := NewProxyEndpoint(testData.backends, "test", NewProxyMetrics(nil), log.NewNopLogger(), nil)
    99  
   100  			// Send the responses from a dedicated goroutine.
   101  			resCh := make(chan *backendResponse)
   102  			go func() {
   103  				for _, res := range testData.responses {
   104  					resCh <- res
   105  				}
   106  				close(resCh)
   107  			}()
   108  
   109  			// Wait for the selected backend response.
   110  			actual := endpoint.waitBackendResponseForDownstream(resCh)
   111  			assert.Equal(t, testData.expected, actual.backend)
   112  		})
   113  	}
   114  }
   115  
   116  func Test_ProxyEndpoint_Requests(t *testing.T) {
   117  	var (
   118  		requestCount atomic.Uint64
   119  		wg           sync.WaitGroup
   120  		testHandler  http.HandlerFunc
   121  	)
   122  
   123  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   124  		defer wg.Done()
   125  		defer requestCount.Add(1)
   126  		testHandler(w, r)
   127  	})
   128  	backend1 := httptest.NewServer(handler)
   129  	defer backend1.Close()
   130  	backendURL1, err := url.Parse(backend1.URL)
   131  	require.NoError(t, err)
   132  
   133  	backend2 := httptest.NewServer(handler)
   134  	defer backend2.Close()
   135  	backendURL2, err := url.Parse(backend2.URL)
   136  	require.NoError(t, err)
   137  
   138  	backends := []*ProxyBackend{
   139  		NewProxyBackend("backend-1", backendURL1, time.Second, true),
   140  		NewProxyBackend("backend-2", backendURL2, time.Second, false),
   141  	}
   142  	endpoint := NewProxyEndpoint(backends, "test", NewProxyMetrics(nil), log.NewNopLogger(), nil)
   143  
   144  	for _, tc := range []struct {
   145  		name    string
   146  		request func(*testing.T) *http.Request
   147  		handler func(*testing.T) http.HandlerFunc
   148  	}{
   149  		{
   150  			name: "GET-request",
   151  			request: func(t *testing.T) *http.Request {
   152  				r, err := http.NewRequest("GET", "http://test/api/v1/test", nil)
   153  				r.Header["test-X"] = []string{"test-X-value"}
   154  				require.NoError(t, err)
   155  				return r
   156  			},
   157  			handler: func(t *testing.T) http.HandlerFunc {
   158  				return func(w http.ResponseWriter, r *http.Request) {
   159  					require.Equal(t, "test-X-value", r.Header.Get("test-X"))
   160  					_, _ = w.Write([]byte("ok"))
   161  				}
   162  			},
   163  		},
   164  		{
   165  			name: "GET-filter-accept-encoding",
   166  			request: func(t *testing.T) *http.Request {
   167  				r, err := http.NewRequest("GET", "http://test/api/v1/test", nil)
   168  				r.Header.Set("Accept-Encoding", "gzip")
   169  				require.NoError(t, err)
   170  				return r
   171  			},
   172  			handler: func(t *testing.T) http.HandlerFunc {
   173  				return func(w http.ResponseWriter, r *http.Request) {
   174  					require.Equal(t, 0, len(r.Header.Values("Accept-Encoding")))
   175  					_, _ = w.Write([]byte("ok"))
   176  				}
   177  			},
   178  		},
   179  		{
   180  			name: "POST-request-with-body",
   181  			request: func(t *testing.T) *http.Request {
   182  				strings := strings.NewReader("this-is-some-payload")
   183  				r, err := http.NewRequest("POST", "http://test/api/v1/test", strings)
   184  				require.NoError(t, err)
   185  				r.Header["test-X"] = []string{"test-X-value"}
   186  				return r
   187  			},
   188  			handler: func(t *testing.T) http.HandlerFunc {
   189  				return func(w http.ResponseWriter, r *http.Request) {
   190  					body, err := io.ReadAll(r.Body)
   191  					require.Equal(t, "this-is-some-payload", string(body))
   192  					require.NoError(t, err)
   193  					require.Equal(t, "test-X-value", r.Header.Get("test-X"))
   194  
   195  					_, _ = w.Write([]byte("ok"))
   196  				}
   197  			},
   198  		},
   199  	} {
   200  		t.Run(tc.name, func(t *testing.T) {
   201  			// reset request count
   202  			requestCount.Store(0)
   203  			wg.Add(2)
   204  
   205  			if tc.handler == nil {
   206  				testHandler = func(w http.ResponseWriter, r *http.Request) {
   207  					_, _ = w.Write([]byte("ok"))
   208  				}
   209  
   210  			} else {
   211  				testHandler = tc.handler(t)
   212  			}
   213  
   214  			w := httptest.NewRecorder()
   215  			endpoint.ServeHTTP(w, tc.request(t))
   216  			require.Equal(t, "ok", w.Body.String())
   217  			require.Equal(t, 200, w.Code)
   218  
   219  			wg.Wait()
   220  			require.Equal(t, uint64(2), requestCount.Load())
   221  		})
   222  	}
   223  }
   224  
   225  func Test_backendResponse_succeeded(t *testing.T) {
   226  	tests := map[string]struct {
   227  		resStatus int
   228  		resError  error
   229  		expected  bool
   230  	}{
   231  		"Error while executing request": {
   232  			resStatus: 0,
   233  			resError:  errors.New("network error"),
   234  			expected:  false,
   235  		},
   236  		"2xx response status code": {
   237  			resStatus: 200,
   238  			resError:  nil,
   239  			expected:  true,
   240  		},
   241  		"3xx response status code": {
   242  			resStatus: 300,
   243  			resError:  nil,
   244  			expected:  false,
   245  		},
   246  		"4xx response status code": {
   247  			resStatus: 400,
   248  			resError:  nil,
   249  			expected:  true,
   250  		},
   251  		"5xx response status code": {
   252  			resStatus: 500,
   253  			resError:  nil,
   254  			expected:  false,
   255  		},
   256  	}
   257  
   258  	for testName, testData := range tests {
   259  		t.Run(testName, func(t *testing.T) {
   260  			res := &backendResponse{
   261  				status: testData.resStatus,
   262  				err:    testData.resError,
   263  			}
   264  
   265  			assert.Equal(t, testData.expected, res.succeeded())
   266  		})
   267  	}
   268  }
   269  
   270  func Test_backendResponse_statusCode(t *testing.T) {
   271  	tests := map[string]struct {
   272  		resStatus int
   273  		resError  error
   274  		expected  int
   275  	}{
   276  		"Error while executing request": {
   277  			resStatus: 0,
   278  			resError:  errors.New("network error"),
   279  			expected:  500,
   280  		},
   281  		"200 response status code": {
   282  			resStatus: 200,
   283  			resError:  nil,
   284  			expected:  200,
   285  		},
   286  		"503 response status code": {
   287  			resStatus: 503,
   288  			resError:  nil,
   289  			expected:  503,
   290  		},
   291  	}
   292  
   293  	for testName, testData := range tests {
   294  		t.Run(testName, func(t *testing.T) {
   295  			res := &backendResponse{
   296  				status: testData.resStatus,
   297  				err:    testData.resError,
   298  			}
   299  
   300  			assert.Equal(t, testData.expected, res.statusCode())
   301  		})
   302  	}
   303  }