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  }