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 }