istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/xds_cache_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  	"math/rand"
    20  	"reflect"
    21  	"testing"
    22  	"time"
    23  
    24  	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    25  	"go.uber.org/atomic"
    26  	anypb "google.golang.org/protobuf/types/known/anypb"
    27  
    28  	"istio.io/istio/pilot/pkg/model"
    29  	"istio.io/istio/pilot/pkg/xds/endpoints"
    30  	"istio.io/istio/pkg/config"
    31  	"istio.io/istio/pkg/config/schema/kind"
    32  	"istio.io/istio/pkg/test/util/retry"
    33  	"istio.io/istio/pkg/util/sets"
    34  )
    35  
    36  var (
    37  	proxy = &model.Proxy{Metadata: &model.NodeMetadata{}}
    38  	any1  = &discovery.Resource{Resource: &anypb.Any{TypeUrl: "foo"}}
    39  	any2  = &discovery.Resource{Resource: &anypb.Any{TypeUrl: "bar"}}
    40  )
    41  
    42  // TestXdsCacheToken is a regression test to ensure that we do not write
    43  // nolint: gosec
    44  // Test only code
    45  func TestXdsCacheToken(t *testing.T) {
    46  	c := model.NewXdsCache()
    47  	n := atomic.NewInt32(0)
    48  	mkv := func(n int32) *discovery.Resource {
    49  		return &discovery.Resource{Resource: &anypb.Any{TypeUrl: fmt.Sprint(n)}}
    50  	}
    51  	k := endpoints.NewCDSEndpointBuilder(
    52  		proxy, nil,
    53  		"outbound||foo.com",
    54  		model.TrafficDirectionOutbound, "", "foo.com", 80,
    55  		&model.Service{Hostname: "foo.com"}, nil)
    56  	work := func(start time.Time, n int32) {
    57  		v := mkv(n)
    58  		time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
    59  		req := &model.PushRequest{Start: start}
    60  		c.Add(k, req, v)
    61  	}
    62  	// 5 round of xds push
    63  	for vals := 0; vals < 5; vals++ {
    64  		c.ClearAll()
    65  		n.Inc()
    66  		start := time.Now()
    67  		for i := 0; i < 5; i++ {
    68  			go work(start, n.Load())
    69  		}
    70  		retry.UntilOrFail(t, func() bool {
    71  			val := c.Get(k)
    72  			return val != nil && val.Resource.TypeUrl == fmt.Sprint(n.Load())
    73  		})
    74  		for i := 0; i < 5; i++ {
    75  			val := c.Get(k)
    76  			if val == nil {
    77  				t.Fatalf("no cache found")
    78  			}
    79  			if val != nil && val.Resource.TypeUrl != fmt.Sprint(n.Load()) {
    80  				t.Fatalf("got bad write: %v", val.Resource.TypeUrl)
    81  			}
    82  			time.Sleep(time.Millisecond * time.Duration(rand.Intn(20)))
    83  		}
    84  	}
    85  }
    86  
    87  func TestXdsCache(t *testing.T) {
    88  	makeEp := func(subset string, dr *model.ConsolidatedDestRule) *endpoints.EndpointBuilder {
    89  		svc := &model.Service{Hostname: "foo.com"}
    90  		b := endpoints.NewCDSEndpointBuilder(
    91  			proxy, nil,
    92  			fmt.Sprintf("outbound|%s|foo.com", subset),
    93  			model.TrafficDirectionOutbound, subset, "foo.com", 80,
    94  			svc, dr)
    95  		return b
    96  	}
    97  	ep1 := makeEp("1", nil)
    98  	ep2 := makeEp("2", nil)
    99  
   100  	t.Run("simple", func(t *testing.T) {
   101  		c := model.NewXdsCache()
   102  		c.Add(ep1, &model.PushRequest{Start: time.Now()}, any1)
   103  		if !reflect.DeepEqual(c.Keys(model.EDSType), []any{ep1.Key()}) {
   104  			t.Fatalf("unexpected keys: %v, want %v", c.Keys(model.EDSType), ep1.Key())
   105  		}
   106  		if got := c.Get(ep1); got != any1 {
   107  			t.Fatalf("unexpected result: %v, want %v", got, any1)
   108  		}
   109  		c.Add(ep1, &model.PushRequest{Start: time.Now()}, any2)
   110  		if got := c.Get(ep1); got != any2 {
   111  			t.Fatalf("unexpected result: %v, want %v", got, any2)
   112  		}
   113  
   114  		c.Clear(sets.New(model.ConfigKey{Kind: kind.ServiceEntry, Name: "foo.com"}))
   115  		if got := c.Get(ep1); got != nil {
   116  			t.Fatalf("unexpected result, found key when not expected: %v", c.Keys(model.EDSType))
   117  		}
   118  	})
   119  
   120  	t.Run("multiple hostnames", func(t *testing.T) {
   121  		c := model.NewXdsCache()
   122  		start := time.Now()
   123  		c.Add(ep1, &model.PushRequest{Start: start}, any1)
   124  		c.Add(ep2, &model.PushRequest{Start: start}, any2)
   125  
   126  		if got := c.Get(ep1); got != any1 {
   127  			t.Fatalf("unexpected result: %v, want %v", got, any1)
   128  		}
   129  		if got := c.Get(ep2); got != any2 {
   130  			t.Fatalf("unexpected result: %v, want %v", got, any2)
   131  		}
   132  		c.Clear(sets.New(model.ConfigKey{Kind: kind.ServiceEntry, Name: "foo.com"}))
   133  		if got := c.Get(ep1); got != nil {
   134  			t.Fatalf("unexpected result, found key when not expected: %v", c.Keys(model.EDSType))
   135  		}
   136  		if got := c.Get(ep2); got != nil {
   137  			t.Fatalf("unexpected result, found key when not expected: %v", c.Keys(model.EDSType))
   138  		}
   139  	})
   140  
   141  	t.Run("multiple destinationRules", func(t *testing.T) {
   142  		c := model.NewXdsCache()
   143  
   144  		ep1 := makeEp("1", model.ConvertConsolidatedDestRule(&config.Config{Meta: config.Meta{Name: "a", Namespace: "b"}}))
   145  		ep2 := makeEp("2", model.ConvertConsolidatedDestRule(&config.Config{Meta: config.Meta{Name: "b", Namespace: "b"}}))
   146  
   147  		start := time.Now()
   148  		c.Add(ep1, &model.PushRequest{Start: start}, any1)
   149  		c.Add(ep2, &model.PushRequest{Start: start}, any2)
   150  		if got := c.Get(ep1); got != any1 {
   151  			t.Fatalf("unexpected result: %v, want %v", got, any1)
   152  		}
   153  		if got := c.Get(ep2); got != any2 {
   154  			t.Fatalf("unexpected result: %v, want %v", got, any2)
   155  		}
   156  		c.Clear(sets.New(model.ConfigKey{Kind: kind.DestinationRule, Name: "a", Namespace: "b"}))
   157  		if got := c.Get(ep1); got != nil {
   158  			t.Fatalf("unexpected result, found key when not expected: %v", c.Keys(model.EDSType))
   159  		}
   160  		if got := c.Get(ep2); got != any2 {
   161  			t.Fatalf("unexpected result: %v, want %v", got, any2)
   162  		}
   163  		c.Clear(sets.New(model.ConfigKey{Kind: kind.DestinationRule, Name: "b", Namespace: "b"}))
   164  		if got := c.Get(ep1); got != nil {
   165  			t.Fatalf("unexpected result, found key when not expected: %v", c.Keys(model.EDSType))
   166  		}
   167  		if got := c.Get(ep2); got != nil {
   168  			t.Fatalf("unexpected result, found key when not expected: %v", c.Keys(model.EDSType))
   169  		}
   170  	})
   171  
   172  	t.Run("clear all", func(t *testing.T) {
   173  		c := model.NewXdsCache()
   174  		start := time.Now()
   175  		c.Add(ep1, &model.PushRequest{Start: start}, any1)
   176  		c.Add(ep2, &model.PushRequest{Start: start}, any2)
   177  
   178  		c.ClearAll()
   179  		if len(c.Keys(model.EDSType)) != 0 {
   180  			t.Fatalf("expected no keys, got: %v", c.Keys(model.EDSType))
   181  		}
   182  		if got := c.Get(ep1); got != nil {
   183  			t.Fatalf("unexpected result, found key when not expected: %v", c.Keys(model.EDSType))
   184  		}
   185  		if got := c.Get(ep2); got != nil {
   186  			t.Fatalf("unexpected result, found key when not expected: %v", c.Keys(model.EDSType))
   187  		}
   188  	})
   189  
   190  	t.Run("write without token does nothing", func(t *testing.T) {
   191  		c := model.NewXdsCache()
   192  		c.Add(ep1, &model.PushRequest{}, any1)
   193  		if got := c.Get(ep1); got != nil {
   194  			t.Fatalf("unexpected result: %v, want none", got)
   195  		}
   196  	})
   197  
   198  	t.Run("write with evicted token", func(t *testing.T) {
   199  		c := model.NewXdsCache()
   200  		t1 := time.Now()
   201  		t2 := t1.Add(1 * time.Nanosecond)
   202  		c.Add(ep1, &model.PushRequest{Start: t2}, any1)
   203  		c.Add(ep1, &model.PushRequest{Start: t1}, any2)
   204  		if len(c.Keys(model.EDSType)) != 1 {
   205  			t.Fatalf("expected 1 keys, got: %v", c.Keys(model.EDSType))
   206  		}
   207  		if got := c.Get(ep1); got != any1 {
   208  			t.Fatalf("unexpected result: %v, want %v", got, any1)
   209  		}
   210  	})
   211  
   212  	t.Run("write with expired token", func(t *testing.T) {
   213  		c := model.NewXdsCache()
   214  		t1 := time.Now()
   215  		t2 := t1.Add(-1 * time.Nanosecond)
   216  
   217  		c.Add(ep1, &model.PushRequest{Start: t1}, any1)
   218  		c.ClearAll()
   219  		// prevented, this is stale token
   220  		c.Add(ep1, &model.PushRequest{Start: t2}, any2)
   221  		if got := c.Get(ep1); got != nil {
   222  			t.Fatalf("expected no cache, but got %v", got)
   223  		}
   224  	})
   225  
   226  	t.Run("disallow write with stale token after clear", func(t *testing.T) {
   227  		c := model.NewXdsCache()
   228  		t1 := time.Now()
   229  
   230  		c.Add(ep1, &model.PushRequest{Start: t1}, any1)
   231  		c.ClearAll()
   232  		// prevented, this can be stale data after `disallowCacheSameToken`
   233  		c.Add(ep1, &model.PushRequest{Start: t1}, any2)
   234  		if got := c.Get(ep1); got != nil {
   235  			t.Fatalf("expected no cache, but got %v", got)
   236  		}
   237  
   238  		// cache with newer token
   239  		c.Add(ep1, &model.PushRequest{Start: time.Now()}, any1)
   240  		if got := c.Get(ep1); got != any1 {
   241  			t.Fatalf("unexpected result: %v, want %v", got, any1)
   242  		}
   243  	})
   244  }