istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/pushqueue_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package xds 16 17 import ( 18 "fmt" 19 "reflect" 20 "sort" 21 "strconv" 22 "sync" 23 "testing" 24 "time" 25 26 "istio.io/istio/pilot/pkg/model" 27 "istio.io/istio/pkg/config/schema/kind" 28 "istio.io/istio/pkg/util/sets" 29 ) 30 31 // Helper function to remove an item or timeout and return nil if there are no pending pushes 32 func getWithTimeout(p *PushQueue) *Connection { 33 done := make(chan *Connection, 1) 34 go func() { 35 con, _, _ := p.Dequeue() 36 done <- con 37 }() 38 select { 39 case ret := <-done: 40 return ret 41 case <-time.After(time.Millisecond * 500): 42 return nil 43 } 44 } 45 46 func ExpectTimeout(t *testing.T, p *PushQueue) { 47 t.Helper() 48 done := make(chan struct{}, 1) 49 go func() { 50 p.Dequeue() 51 done <- struct{}{} 52 }() 53 select { 54 case <-done: 55 t.Fatalf("Expected timeout") 56 case <-time.After(time.Millisecond * 500): 57 } 58 } 59 60 func ExpectDequeue(t *testing.T, p *PushQueue, expected *Connection) { 61 t.Helper() 62 result := make(chan *Connection, 1) 63 go func() { 64 con, _, _ := p.Dequeue() 65 result <- con 66 }() 67 select { 68 case got := <-result: 69 if got != expected { 70 t.Fatalf("Expected proxy %v, got %v", expected, got) 71 } 72 case <-time.After(time.Millisecond * 500): 73 t.Fatalf("Timed out") 74 } 75 } 76 77 func TestProxyQueue(t *testing.T) { 78 proxies := make([]*Connection, 0, 100) 79 for p := 0; p < 100; p++ { 80 conn := newConnection("", nil) 81 conn.SetID(fmt.Sprintf("proxy-%d", p)) 82 proxies = append(proxies, conn) 83 } 84 85 t.Run("simple add and remove", func(t *testing.T) { 86 t.Parallel() 87 p := NewPushQueue() 88 defer p.ShutDown() 89 p.Enqueue(proxies[0], &model.PushRequest{}) 90 p.Enqueue(proxies[1], &model.PushRequest{}) 91 92 ExpectDequeue(t, p, proxies[0]) 93 ExpectDequeue(t, p, proxies[1]) 94 }) 95 96 t.Run("remove too many", func(t *testing.T) { 97 t.Parallel() 98 p := NewPushQueue() 99 defer p.ShutDown() 100 101 p.Enqueue(proxies[0], &model.PushRequest{}) 102 103 ExpectDequeue(t, p, proxies[0]) 104 ExpectTimeout(t, p) 105 }) 106 107 t.Run("add multiple times", func(t *testing.T) { 108 t.Parallel() 109 p := NewPushQueue() 110 defer p.ShutDown() 111 112 p.Enqueue(proxies[0], &model.PushRequest{}) 113 p.Enqueue(proxies[1], &model.PushRequest{}) 114 p.Enqueue(proxies[0], &model.PushRequest{}) 115 116 ExpectDequeue(t, p, proxies[0]) 117 ExpectDequeue(t, p, proxies[1]) 118 ExpectTimeout(t, p) 119 }) 120 121 t.Run("add and remove and markdone", func(t *testing.T) { 122 t.Parallel() 123 p := NewPushQueue() 124 defer p.ShutDown() 125 126 p.Enqueue(proxies[0], &model.PushRequest{}) 127 ExpectDequeue(t, p, proxies[0]) 128 p.MarkDone(proxies[0]) 129 p.Enqueue(proxies[0], &model.PushRequest{}) 130 ExpectDequeue(t, p, proxies[0]) 131 ExpectTimeout(t, p) 132 }) 133 134 t.Run("add and remove and add and markdone", func(t *testing.T) { 135 t.Parallel() 136 p := NewPushQueue() 137 defer p.ShutDown() 138 139 p.Enqueue(proxies[0], &model.PushRequest{}) 140 ExpectDequeue(t, p, proxies[0]) 141 p.Enqueue(proxies[0], &model.PushRequest{}) 142 p.Enqueue(proxies[0], &model.PushRequest{}) 143 p.MarkDone(proxies[0]) 144 145 ExpectDequeue(t, p, proxies[0]) 146 ExpectTimeout(t, p) 147 }) 148 149 t.Run("remove should block", func(t *testing.T) { 150 t.Parallel() 151 p := NewPushQueue() 152 defer p.ShutDown() 153 154 wg := &sync.WaitGroup{} 155 wg.Add(1) 156 go func() { 157 ExpectDequeue(t, p, proxies[0]) 158 wg.Done() 159 }() 160 time.Sleep(time.Millisecond * 50) 161 p.Enqueue(proxies[0], &model.PushRequest{}) 162 wg.Wait() 163 }) 164 165 t.Run("should merge model.PushRequest", func(t *testing.T) { 166 t.Parallel() 167 p := NewPushQueue() 168 defer p.ShutDown() 169 170 firstTime := time.Now() 171 p.Enqueue(proxies[0], &model.PushRequest{ 172 Full: false, 173 ConfigsUpdated: sets.New(model.ConfigKey{ 174 Kind: kind.ServiceEntry, 175 Name: "foo", 176 }), 177 Start: firstTime, 178 }) 179 180 p.Enqueue(proxies[0], &model.PushRequest{ 181 Full: false, 182 ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.ServiceEntry, Name: "bar", Namespace: "ns1"}), 183 184 Start: firstTime.Add(time.Second), 185 }) 186 _, info, _ := p.Dequeue() 187 188 if info.Start != firstTime { 189 t.Errorf("Expected start time to be %v, got %v", firstTime, info.Start) 190 } 191 expectedEds := sets.New( 192 model.ConfigKey{ 193 Kind: kind.ServiceEntry, 194 Name: "foo", 195 Namespace: "", 196 }, 197 model.ConfigKey{ 198 Kind: kind.ServiceEntry, 199 Name: "bar", 200 Namespace: "ns1", 201 }, 202 ) 203 if !reflect.DeepEqual(model.ConfigsOfKind(info.ConfigsUpdated, kind.ServiceEntry), expectedEds) { 204 t.Errorf("Expected EdsUpdates to be %v, got %v", expectedEds, model.ConfigsOfKind(info.ConfigsUpdated, kind.ServiceEntry)) 205 } 206 if info.Full { 207 t.Errorf("Expected full to be false, got true") 208 } 209 }) 210 211 t.Run("two removes, one should block one should return", func(t *testing.T) { 212 t.Parallel() 213 p := NewPushQueue() 214 defer p.ShutDown() 215 216 wg := &sync.WaitGroup{} 217 wg.Add(2) 218 respChannel := make(chan *Connection, 2) 219 go func() { 220 respChannel <- getWithTimeout(p) 221 wg.Done() 222 }() 223 time.Sleep(time.Millisecond * 50) 224 p.Enqueue(proxies[0], &model.PushRequest{}) 225 go func() { 226 respChannel <- getWithTimeout(p) 227 wg.Done() 228 }() 229 230 wg.Wait() 231 timeouts := 0 232 close(respChannel) 233 for resp := range respChannel { 234 if resp == nil { 235 timeouts++ 236 } 237 } 238 if timeouts != 1 { 239 t.Fatalf("Expected 1 timeout, got %v", timeouts) 240 } 241 }) 242 243 t.Run("concurrent", func(t *testing.T) { 244 t.Parallel() 245 p := NewPushQueue() 246 defer p.ShutDown() 247 248 key := func(p *Connection, eds string) string { return fmt.Sprintf("%s~%s", p.ID(), eds) } 249 250 // We will trigger many pushes for eds services to each proxy. In the end we will expect 251 // all of these to be dequeue, but order is not deterministic. 252 expected := sets.String{} 253 for eds := 0; eds < 100; eds++ { 254 for _, pr := range proxies { 255 expected.Insert(key(pr, fmt.Sprintf("%d", eds))) 256 } 257 } 258 go func() { 259 for eds := 0; eds < 100; eds++ { 260 for _, pr := range proxies { 261 p.Enqueue(pr, &model.PushRequest{ 262 ConfigsUpdated: sets.New(model.ConfigKey{ 263 Kind: kind.ServiceEntry, 264 Name: fmt.Sprintf("%d", eds), 265 }), 266 }) 267 } 268 } 269 }() 270 271 done := make(chan struct{}) 272 mu := sync.RWMutex{} 273 go func() { 274 for { 275 con, info, shuttingdown := p.Dequeue() 276 if shuttingdown { 277 return 278 } 279 for eds := range model.ConfigNamesOfKind(info.ConfigsUpdated, kind.ServiceEntry) { 280 mu.Lock() 281 delete(expected, key(con, eds)) 282 mu.Unlock() 283 } 284 p.MarkDone(con) 285 if len(expected) == 0 { 286 done <- struct{}{} 287 } 288 } 289 }() 290 291 select { 292 case <-done: 293 case <-time.After(time.Second * 10): 294 mu.RLock() 295 defer mu.RUnlock() 296 t.Fatalf("failed to get all updates, still pending: %v", len(expected)) 297 } 298 }) 299 300 t.Run("concurrent with deterministic order", func(t *testing.T) { 301 t.Parallel() 302 p := NewPushQueue() 303 defer p.ShutDown() 304 305 con := newConnection("", nil) 306 con.SetID("proxy-test") 307 308 // We will trigger many pushes for eds services to the proxy. In the end we will expect 309 // all of these to be dequeue, but order is deterministic. 310 expected := make([]string, 100) 311 for eds := 0; eds < 100; eds++ { 312 expected[eds] = fmt.Sprintf("%d", eds) 313 } 314 go func() { 315 // send to pushQueue 316 for eds := 0; eds < 100; eds++ { 317 p.Enqueue(con, &model.PushRequest{ 318 ConfigsUpdated: sets.New(model.ConfigKey{ 319 Kind: kind.Kind(eds), 320 Name: fmt.Sprintf("%d", eds), 321 }), 322 }) 323 } 324 }() 325 326 processed := make([]string, 0, 100) 327 done := make(chan struct{}) 328 pushChannel := make(chan *model.PushRequest) 329 go func() { 330 // dequeue pushQueue and send to pushChannel 331 for { 332 _, request, shuttingdown := p.Dequeue() 333 if shuttingdown { 334 close(pushChannel) 335 return 336 } 337 pushChannel <- request 338 } 339 }() 340 341 go func() { 342 // recv from pushChannel and simulate push 343 for { 344 request := <-pushChannel 345 if request == nil { 346 return 347 } 348 updated := make([]string, 0, len(request.ConfigsUpdated)) 349 for configkey := range request.ConfigsUpdated { 350 updated = append(updated, fmt.Sprintf("%d", configkey.Kind)) 351 } 352 sort.Slice(updated, func(i, j int) bool { 353 l, _ := strconv.Atoi(updated[i]) 354 r, _ := strconv.Atoi(updated[j]) 355 return l < r 356 }) 357 processed = append(processed, updated...) 358 if len(processed) == 100 { 359 done <- struct{}{} 360 } 361 p.MarkDone(con) 362 } 363 }() 364 365 select { 366 case <-done: 367 case <-time.After(time.Second * 10): 368 t.Fatalf("failed to get all updates, still pending: got %v", len(processed)) 369 } 370 371 if !reflect.DeepEqual(expected, processed) { 372 t.Fatalf("expected order %v, but got %v", expected, processed) 373 } 374 }) 375 }