4
4
package dimensions
5
5
6
6
import (
7
- "context"
8
7
"encoding/json"
9
- "log"
10
8
"net/http"
11
9
"net/http/httptest"
12
10
"net/url"
@@ -30,97 +28,106 @@ type dim struct {
30
28
TagsToRemove []string `json:"tagsToRemove"`
31
29
}
32
30
33
- func waitForDims (dimCh <- chan dim , count , waitSeconds int ) []dim { // nolint: unparam
34
- var dims []dim
35
- timeout := time .After (time .Duration (waitSeconds ) * time .Second )
36
-
37
- loop:
38
- for {
39
- select {
40
- case d := <- dimCh :
41
- dims = append (dims , d )
42
- if len (dims ) >= count {
43
- break loop
44
- }
45
- case <- timeout :
46
- break loop
47
- }
48
- }
49
-
50
- return dims
31
+ type testServer struct {
32
+ startCh chan struct {}
33
+ finishCh chan struct {}
34
+ acceptedDims []dim
35
+ server * httptest.Server
36
+ respCode int
37
+ requestCount * atomic.Int32
51
38
}
52
39
53
- func makeHandler (dimCh chan <- dim , forcedResp * atomic.Int32 ) http.HandlerFunc {
54
- forcedResp .Store (200 )
40
+ func (ts * testServer ) ServeHTTP (rw http.ResponseWriter , r * http.Request ) {
41
+ ts .requestCount .Add (1 )
42
+ <- ts .startCh
55
43
56
- return func (rw http.ResponseWriter , r * http.Request ) {
57
- forcedRespInt := int (forcedResp .Load ())
58
- if forcedRespInt != 200 {
59
- rw .WriteHeader (forcedRespInt )
60
- return
61
- }
44
+ if ts .respCode != http .StatusOK {
45
+ rw .WriteHeader (ts .respCode )
46
+ ts .finishCh <- struct {}{}
47
+ return
48
+ }
62
49
63
- log .Printf ("Test server got request: %s" , r .URL .Path )
50
+ match := patchPathRegexp .FindStringSubmatch (r .URL .Path )
51
+ if match == nil {
52
+ rw .WriteHeader (http .StatusNotFound )
53
+ ts .finishCh <- struct {}{}
54
+ return
55
+ }
64
56
65
- if r .Method != "PATCH" {
66
- rw .WriteHeader (http .StatusNotFound )
67
- return
68
- }
57
+ var bodyDim dim
58
+ if err := json .NewDecoder (r .Body ).Decode (& bodyDim ); err != nil {
59
+ rw .WriteHeader (http .StatusBadRequest )
60
+ ts .finishCh <- struct {}{}
61
+ return
62
+ }
63
+ bodyDim .Key = match [1 ]
64
+ bodyDim .Value = match [2 ]
69
65
70
- match := patchPathRegexp .FindStringSubmatch (r .URL .Path )
71
- if match == nil {
72
- rw .WriteHeader (http .StatusNotFound )
73
- return
74
- }
66
+ ts .acceptedDims = append (ts .acceptedDims , bodyDim )
75
67
76
- var bodyDim dim
77
- if err := json .NewDecoder (r .Body ).Decode (& bodyDim ); err != nil {
78
- rw .WriteHeader (400 )
79
- return
80
- }
81
- bodyDim .Key = match [1 ]
82
- bodyDim .Value = match [2 ]
68
+ ts .finishCh <- struct {}{}
69
+ rw .WriteHeader (http .StatusOK )
70
+ }
83
71
84
- dimCh <- bodyDim
72
+ // startHandling unblocks the server to handle the request and waits until the request is processed.
73
+ func (ts * testServer ) handleRequest () {
74
+ ts .startCh <- struct {}{}
75
+ <- ts .finishCh
76
+ }
85
77
86
- rw .WriteHeader (http .StatusOK )
78
+ func (ts * testServer ) shutdown () {
79
+ ts .reset ()
80
+ if ts .server != nil {
81
+ ts .server .Close ()
87
82
}
88
83
}
89
84
90
- func setup (t * testing.T ) (* DimensionClient , chan dim , * atomic.Int32 , context.CancelFunc ) {
91
- dimCh := make (chan dim )
85
+ func (ts * testServer ) reset () {
86
+ if ts .startCh != nil {
87
+ close (ts .startCh )
88
+ ts .startCh = make (chan struct {})
89
+ }
90
+ if ts .finishCh != nil {
91
+ close (ts .finishCh )
92
+ ts .finishCh = make (chan struct {})
93
+ }
94
+ ts .acceptedDims = nil
95
+ ts .respCode = http .StatusOK
96
+ ts .requestCount .Store (0 )
97
+ }
92
98
93
- forcedResp := & atomic.Int32 {}
94
- server := httptest .NewServer (makeHandler (dimCh , forcedResp ))
99
+ func setupTestClientServer (t * testing.T ) (* DimensionClient , * testServer ) {
100
+ ts := & testServer {
101
+ startCh : make (chan struct {}),
102
+ finishCh : make (chan struct {}),
103
+ respCode : http .StatusOK ,
104
+ requestCount : new (atomic.Int32 ),
105
+ }
106
+ ts .server = httptest .NewServer (ts )
95
107
96
- serverURL , err := url .Parse (server .URL )
108
+ serverURL , err := url .Parse (ts . server .URL )
97
109
require .NoError (t , err , "failed to get server URL" , err )
98
110
99
- ctx , cancel := context .WithCancel (context .Background ())
100
- go func () {
101
- <- ctx .Done ()
102
- server .Close ()
103
- }()
104
-
105
111
client := NewDimensionClient (
106
112
DimensionClientOptions {
107
113
APIURL : serverURL ,
108
114
LogUpdates : true ,
109
115
Logger : zap .NewNop (),
110
- SendDelay : time .Second ,
116
+ SendDelay : 100 * time .Millisecond ,
111
117
MaxBuffered : 10 ,
112
118
})
113
119
client .Start ()
114
120
115
- return client , dimCh , forcedResp , cancel
121
+ return client , ts
116
122
}
117
123
118
124
func TestDimensionClient (t * testing.T ) {
119
- client , dimCh , forcedResp , cancel := setup (t )
120
- defer cancel ()
125
+ client , server := setupTestClientServer (t )
126
+ defer server . shutdown ()
121
127
defer client .Shutdown ()
122
128
123
129
t .Run ("send dimension update with properties and tags" , func (t * testing.T ) {
130
+ server .reset ()
124
131
require .NoError (t , client .acceptDimension (& DimensionUpdate {
125
132
Name : "host" ,
126
133
Value : "test-box" ,
@@ -135,7 +142,7 @@ func TestDimensionClient(t *testing.T) {
135
142
},
136
143
}))
137
144
138
- dims := waitForDims ( dimCh , 1 , 3 )
145
+ server . handleRequest ( )
139
146
require .Equal (t , []dim {
140
147
{
141
148
Key : "host" ,
@@ -148,10 +155,12 @@ func TestDimensionClient(t *testing.T) {
148
155
Tags : []string {"active" },
149
156
TagsToRemove : []string {"terminated" },
150
157
},
151
- }, dims )
158
+ }, server .acceptedDims )
159
+ require .EqualValues (t , 1 , server .requestCount .Load ())
152
160
})
153
161
154
162
t .Run ("same dimension with different values" , func (t * testing.T ) {
163
+ server .reset ()
155
164
require .NoError (t , client .acceptDimension (& DimensionUpdate {
156
165
Name : "host" ,
157
166
Value : "test-box" ,
@@ -163,7 +172,7 @@ func TestDimensionClient(t *testing.T) {
163
172
},
164
173
}))
165
174
166
- dims := waitForDims ( dimCh , 1 , 3 )
175
+ server . handleRequest ( )
167
176
require .Equal (t , []dim {
168
177
{
169
178
Key : "host" ,
@@ -173,11 +182,13 @@ func TestDimensionClient(t *testing.T) {
173
182
},
174
183
TagsToRemove : []string {"active" },
175
184
},
176
- }, dims )
185
+ }, server .acceptedDims )
186
+ require .EqualValues (t , 1 , server .requestCount .Load ())
177
187
})
178
188
179
189
t .Run ("send a distinct prop/tag set for existing dim with server error" , func (t * testing.T ) {
180
- forcedResp .Store (500 )
190
+ server .reset ()
191
+ server .respCode = http .StatusInternalServerError
181
192
182
193
// send a distinct prop/tag set for same dim with an error
183
194
require .NoError (t , client .acceptDimension (& DimensionUpdate {
@@ -190,11 +201,11 @@ func TestDimensionClient(t *testing.T) {
190
201
"running" : true ,
191
202
},
192
203
}))
193
- dims := waitForDims ( dimCh , 1 , 3 )
194
- require .Empty (t , dims )
204
+ server . handleRequest ( )
205
+ require .Empty (t , server . acceptedDims )
195
206
196
- forcedResp . Store ( 200 )
197
- dims = waitForDims ( dimCh , 1 , 3 )
207
+ server . respCode = http . StatusOK
208
+ server . handleRequest ( )
198
209
199
210
// After the server recovers the dim should be resent.
200
211
require .Equal (t , []dim {
@@ -206,11 +217,13 @@ func TestDimensionClient(t *testing.T) {
206
217
},
207
218
Tags : []string {"running" },
208
219
},
209
- }, dims )
220
+ }, server .acceptedDims )
221
+ require .EqualValues (t , 2 , server .requestCount .Load ())
210
222
})
211
223
212
224
t .Run ("does not retry 4xx responses" , func (t * testing.T ) {
213
- forcedResp .Store (400 )
225
+ server .reset ()
226
+ server .respCode = http .StatusBadRequest
214
227
215
228
// send a distinct prop/tag set for same dim with an error
216
229
require .NoError (t , client .acceptDimension (& DimensionUpdate {
@@ -220,16 +233,19 @@ func TestDimensionClient(t *testing.T) {
220
233
"z" : newString ("y" ),
221
234
},
222
235
}))
223
- dims := waitForDims (dimCh , 1 , 3 )
224
- require .Empty (t , dims )
236
+ server .handleRequest ()
237
+
238
+ require .Empty (t , server .acceptedDims )
225
239
226
- forcedResp .Store (200 )
227
- dims = waitForDims (dimCh , 1 , 3 )
228
- require .Empty (t , dims )
240
+ server .respCode = http .StatusOK
241
+
242
+ // there should be no retries
243
+ require .EqualValues (t , 1 , server .requestCount .Load ())
229
244
})
230
245
231
246
t .Run ("does retry 404 responses" , func (t * testing.T ) {
232
- forcedResp .Store (404 )
247
+ server .reset ()
248
+ server .respCode = http .StatusNotFound
233
249
234
250
// send a distinct prop/tag set for same dim with an error
235
251
require .NoError (t , client .acceptDimension (& DimensionUpdate {
@@ -240,11 +256,11 @@ func TestDimensionClient(t *testing.T) {
240
256
},
241
257
}))
242
258
243
- dims := waitForDims ( dimCh , 1 , 3 )
244
- require .Empty (t , dims )
259
+ server . handleRequest ( )
260
+ require .Empty (t , server . acceptedDims )
245
261
246
- forcedResp . Store ( 200 )
247
- dims = waitForDims ( dimCh , 1 , 3 )
262
+ server . respCode = http . StatusOK
263
+ server . handleRequest ( )
248
264
require .Equal (t , []dim {
249
265
{
250
266
Key : "AWSUniqueID" ,
@@ -253,10 +269,13 @@ func TestDimensionClient(t *testing.T) {
253
269
"z" : newString ("x" ),
254
270
},
255
271
},
256
- }, dims )
272
+ }, server .acceptedDims )
273
+ require .EqualValues (t , 2 , server .requestCount .Load ())
257
274
})
258
275
259
276
t .Run ("send successive quick updates to same dim" , func (t * testing.T ) {
277
+ server .reset ()
278
+
260
279
require .NoError (t , client .acceptDimension (& DimensionUpdate {
261
280
Name : "AWSUniqueID" ,
262
281
Value : "abcd" ,
@@ -292,7 +311,7 @@ func TestDimensionClient(t *testing.T) {
292
311
},
293
312
}))
294
313
295
- dims := waitForDims ( dimCh , 1 , 3 )
314
+ server . handleRequest ( )
296
315
297
316
require .Equal (t , []dim {
298
317
{
@@ -305,13 +324,14 @@ func TestDimensionClient(t *testing.T) {
305
324
Tags : []string {"dev" },
306
325
TagsToRemove : []string {"running" },
307
326
},
308
- }, dims )
327
+ }, server .acceptedDims )
328
+ require .EqualValues (t , 1 , server .requestCount .Load ())
309
329
})
310
330
}
311
331
312
332
func TestFlappyUpdates (t * testing.T ) {
313
- client , dimCh , _ , cancel := setup (t )
314
- defer cancel ()
333
+ client , server := setupTestClientServer (t )
334
+ defer server . shutdown ()
315
335
defer client .Shutdown ()
316
336
317
337
// Do some flappy updates
@@ -333,7 +353,10 @@ func TestFlappyUpdates(t *testing.T) {
333
353
}))
334
354
}
335
355
336
- dims := waitForDims (dimCh , 2 , 3 )
356
+ // handle 2 requests
357
+ server .handleRequest ()
358
+ server .handleRequest ()
359
+
337
360
require .ElementsMatch (t , []dim {
338
361
{
339
362
Key : "pod_uid" ,
@@ -345,12 +368,15 @@ func TestFlappyUpdates(t *testing.T) {
345
368
Value : "efgh" ,
346
369
Properties : map [string ]* string {"index" : newString ("4" )},
347
370
},
348
- }, dims )
371
+ }, server .acceptedDims )
372
+ require .EqualValues (t , 2 , server .requestCount .Load ())
349
373
}
350
374
375
+ // TODO: Update the dimension update client to never send empty dimension key or value
351
376
func TestInvalidUpdatesNotSent (t * testing.T ) {
352
- client , dimCh , _ , cancel := setup (t )
353
- defer cancel ()
377
+ t .Skip ("This test causes data race because empty dimension key or value result in 404s which causes infinite retries" )
378
+ client , server := setupTestClientServer (t )
379
+ defer server .shutdown ()
354
380
defer client .Shutdown ()
355
381
require .NoError (t , client .acceptDimension (& DimensionUpdate {
356
382
Name : "host" ,
@@ -363,6 +389,8 @@ func TestInvalidUpdatesNotSent(t *testing.T) {
363
389
"active" : true ,
364
390
},
365
391
}))
392
+ server .handleRequest ()
393
+
366
394
require .NoError (t , client .acceptDimension (& DimensionUpdate {
367
395
Name : "" ,
368
396
Value : "asdf" ,
@@ -374,9 +402,10 @@ func TestInvalidUpdatesNotSent(t *testing.T) {
374
402
"active" : true ,
375
403
},
376
404
}))
405
+ server .handleRequest ()
377
406
378
- dims := waitForDims ( dimCh , 2 , 3 )
379
- require .Empty (t , dims )
407
+ require . EqualValues ( t , 2 , server . requestCount . Load () )
408
+ require .Empty (t , server . acceptedDims )
380
409
}
381
410
382
411
func newString (s string ) * string {
0 commit comments