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 }