github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/go-control-plane/pkg/cache/v3/simple_test.go (about) 1 // Copyright 2018 Envoyproxy 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 cache_test 16 17 import ( 18 "context" 19 "fmt" 20 "reflect" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/stretchr/testify/assert" 26 27 core "github.com/hxx258456/ccgo/go-control-plane/envoy/config/core/v3" 28 discovery "github.com/hxx258456/ccgo/go-control-plane/envoy/service/discovery/v3" 29 "github.com/hxx258456/ccgo/go-control-plane/pkg/cache/types" 30 "github.com/hxx258456/ccgo/go-control-plane/pkg/cache/v3" 31 rsrc "github.com/hxx258456/ccgo/go-control-plane/pkg/resource/v3" 32 "github.com/hxx258456/ccgo/go-control-plane/pkg/test/resource/v3" 33 ) 34 35 type group struct{} 36 37 const ( 38 key = "node" 39 ) 40 41 func (group) ID(node *core.Node) string { 42 if node != nil { 43 return node.Id 44 } 45 return key 46 } 47 48 var ( 49 version = "x" 50 version2 = "y" 51 52 snapshot, _ = cache.NewSnapshot(version, map[rsrc.Type][]types.Resource{ 53 rsrc.EndpointType: {testEndpoint}, 54 rsrc.ClusterType: {testCluster}, 55 rsrc.RouteType: {testRoute}, 56 rsrc.ListenerType: {testListener}, 57 rsrc.RuntimeType: {testRuntime}, 58 rsrc.SecretType: {testSecret[0]}, 59 rsrc.ExtensionConfigType: {testExtensionConfig}, 60 }) 61 62 ttl = 2 * time.Second 63 snapshotWithTTL, _ = cache.NewSnapshotWithTTLs(version, map[rsrc.Type][]types.ResourceWithTTL{ 64 rsrc.EndpointType: {{Resource: testEndpoint, TTL: &ttl}}, 65 rsrc.ClusterType: {{Resource: testCluster}}, 66 rsrc.RouteType: {{Resource: testRoute}}, 67 rsrc.ListenerType: {{Resource: testListener}}, 68 rsrc.RuntimeType: {{Resource: testRuntime}}, 69 rsrc.SecretType: {{Resource: testSecret[0]}}, 70 rsrc.ExtensionConfigType: {{Resource: testExtensionConfig}}, 71 }) 72 73 names = map[string][]string{ 74 rsrc.EndpointType: {clusterName}, 75 rsrc.ClusterType: nil, 76 rsrc.RouteType: {routeName}, 77 rsrc.ListenerType: nil, 78 rsrc.RuntimeType: nil, 79 } 80 81 testTypes = []string{ 82 rsrc.EndpointType, 83 rsrc.ClusterType, 84 rsrc.RouteType, 85 rsrc.ListenerType, 86 rsrc.RuntimeType, 87 } 88 ) 89 90 type logger struct { 91 t *testing.T 92 } 93 94 func (log logger) Debugf(format string, args ...interface{}) { log.t.Logf(format, args...) } 95 func (log logger) Infof(format string, args ...interface{}) { log.t.Logf(format, args...) } 96 func (log logger) Warnf(format string, args ...interface{}) { log.t.Logf(format, args...) } 97 func (log logger) Errorf(format string, args ...interface{}) { log.t.Logf(format, args...) } 98 99 func TestSnapshotCacheWithTTL(t *testing.T) { 100 ctx, cancel := context.WithCancel(context.Background()) 101 defer cancel() 102 c := cache.NewSnapshotCacheWithHeartbeating(ctx, true, group{}, logger{t: t}, time.Second) 103 104 if _, err := c.GetSnapshot(key); err == nil { 105 t.Errorf("unexpected snapshot found for key %q", key) 106 } 107 108 if err := c.SetSnapshot(context.Background(), key, snapshotWithTTL); err != nil { 109 t.Fatal(err) 110 } 111 112 snap, err := c.GetSnapshot(key) 113 if err != nil { 114 t.Fatal(err) 115 } 116 if !reflect.DeepEqual(snap, snapshotWithTTL) { 117 t.Errorf("expect snapshot: %v, got: %v", snapshotWithTTL, snap) 118 } 119 120 wg := sync.WaitGroup{} 121 // All the resources should respond immediately when version is not up to date. 122 for _, typ := range testTypes { 123 wg.Add(1) 124 t.Run(typ, func(t *testing.T) { 125 defer wg.Done() 126 value := make(chan cache.Response, 1) 127 c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]}, value) 128 select { 129 case out := <-value: 130 if gotVersion, _ := out.GetVersion(); gotVersion != version { 131 t.Errorf("got version %q, want %q", gotVersion, version) 132 } 133 if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshotWithTTL.GetResourcesAndTTL(typ)) { 134 t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshotWithTTL.GetResourcesAndTTL(typ)) 135 } 136 case <-time.After(2 * time.Second): 137 t.Errorf("failed to receive snapshot response") 138 } 139 }) 140 } 141 wg.Wait() 142 143 // Once everything is up to date, only the TTL'd resource should send out updates. 144 wg = sync.WaitGroup{} 145 updatesByType := map[string]int{} 146 for _, typ := range testTypes { 147 wg.Add(1) 148 go func(typ string) { 149 defer wg.Done() 150 151 end := time.After(5 * time.Second) 152 for { 153 value := make(chan cache.Response, 1) 154 cancel := c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ], VersionInfo: version}, value) 155 156 select { 157 case out := <-value: 158 if gotVersion, _ := out.GetVersion(); gotVersion != version { 159 t.Errorf("got version %q, want %q", gotVersion, version) 160 } 161 if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshotWithTTL.GetResourcesAndTTL(typ)) { 162 t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshotWithTTL.GetResources(typ)) 163 } 164 165 if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshotWithTTL.GetResourcesAndTTL(typ)) { 166 t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshotWithTTL.GetResources(typ)) 167 } 168 169 updatesByType[typ]++ 170 case <-end: 171 cancel() 172 return 173 } 174 } 175 }(typ) 176 } 177 178 wg.Wait() 179 180 if len(updatesByType) != 1 { 181 t.Errorf("expected to only receive updates for TTL'd type, got %v", updatesByType) 182 } 183 // Avoid an exact match on number of triggers to avoid this being flaky. 184 if updatesByType[rsrc.EndpointType] < 2 { 185 t.Errorf("expected at least two TTL updates for endpoints, got %d", updatesByType[rsrc.EndpointType]) 186 } 187 } 188 189 func TestSnapshotCache(t *testing.T) { 190 c := cache.NewSnapshotCache(true, group{}, logger{t: t}) 191 192 if _, err := c.GetSnapshot(key); err == nil { 193 t.Errorf("unexpected snapshot found for key %q", key) 194 } 195 196 if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil { 197 t.Fatal(err) 198 } 199 200 snap, err := c.GetSnapshot(key) 201 if err != nil { 202 t.Fatal(err) 203 } 204 if !reflect.DeepEqual(snap, snapshot) { 205 t.Errorf("expect snapshot: %v, got: %v", snapshot, snap) 206 } 207 208 // try to get endpoints with incorrect list of names 209 // should not receive response 210 value := make(chan cache.Response, 1) 211 c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: rsrc.EndpointType, ResourceNames: []string{"none"}}, value) 212 select { 213 case out := <-value: 214 t.Errorf("watch for endpoints and mismatched names => got %v, want none", out) 215 case <-time.After(time.Second / 4): 216 } 217 218 for _, typ := range testTypes { 219 t.Run(typ, func(t *testing.T) { 220 value := make(chan cache.Response, 1) 221 c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]}, value) 222 select { 223 case out := <-value: 224 if gotVersion, _ := out.GetVersion(); gotVersion != version { 225 t.Errorf("got version %q, want %q", gotVersion, version) 226 } 227 if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshot.GetResourcesAndTTL(typ)) { 228 t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshot.GetResourcesAndTTL(typ)) 229 } 230 case <-time.After(time.Second): 231 t.Fatal("failed to receive snapshot response") 232 } 233 }) 234 } 235 } 236 237 func TestSnapshotCacheFetch(t *testing.T) { 238 c := cache.NewSnapshotCache(true, group{}, logger{t: t}) 239 if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil { 240 t.Fatal(err) 241 } 242 243 for _, typ := range testTypes { 244 t.Run(typ, func(t *testing.T) { 245 resp, err := c.Fetch(context.Background(), &discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]}) 246 if err != nil || resp == nil { 247 t.Fatal("unexpected error or null response") 248 } 249 if gotVersion, _ := resp.GetVersion(); gotVersion != version { 250 t.Errorf("got version %q, want %q", gotVersion, version) 251 } 252 }) 253 } 254 255 // no response for missing snapshot 256 if resp, err := c.Fetch(context.Background(), 257 &discovery.DiscoveryRequest{TypeUrl: rsrc.ClusterType, Node: &core.Node{Id: "oof"}}); resp != nil || err == nil { 258 t.Errorf("missing snapshot: response is not nil %v", resp) 259 } 260 261 // no response for latest version 262 if resp, err := c.Fetch(context.Background(), 263 &discovery.DiscoveryRequest{TypeUrl: rsrc.ClusterType, VersionInfo: version}); resp != nil || err == nil { 264 t.Errorf("latest version: response is not nil %v", resp) 265 } 266 } 267 268 func TestSnapshotCacheWatch(t *testing.T) { 269 c := cache.NewSnapshotCache(true, group{}, logger{t: t}) 270 watches := make(map[string]chan cache.Response) 271 for _, typ := range testTypes { 272 watches[typ] = make(chan cache.Response, 1) 273 c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]}, watches[typ]) 274 } 275 if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil { 276 t.Fatal(err) 277 } 278 for _, typ := range testTypes { 279 t.Run(typ, func(t *testing.T) { 280 select { 281 case out := <-watches[typ]: 282 if gotVersion, _ := out.GetVersion(); gotVersion != version { 283 t.Errorf("got version %q, want %q", gotVersion, version) 284 } 285 if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshot.GetResourcesAndTTL(typ)) { 286 t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshot.GetResourcesAndTTL(typ)) 287 } 288 case <-time.After(time.Second): 289 t.Fatal("failed to receive snapshot response") 290 } 291 }) 292 } 293 294 // open new watches with the latest version 295 for _, typ := range testTypes { 296 watches[typ] = make(chan cache.Response, 1) 297 c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ], VersionInfo: version}, watches[typ]) 298 } 299 if count := c.GetStatusInfo(key).GetNumWatches(); count != len(testTypes) { 300 t.Errorf("watches should be created for the latest version: %d", count) 301 } 302 303 // set partially-versioned snapshot 304 snapshot2 := snapshot 305 snapshot2.Resources[types.Endpoint] = cache.NewResources(version2, []types.Resource{resource.MakeEndpoint(clusterName, 9090)}) 306 if err := c.SetSnapshot(context.Background(), key, snapshot2); err != nil { 307 t.Fatal(err) 308 } 309 if count := c.GetStatusInfo(key).GetNumWatches(); count != len(testTypes)-1 { 310 t.Errorf("watches should be preserved for all but one: %d", count) 311 } 312 313 // validate response for endpoints 314 select { 315 case out := <-watches[rsrc.EndpointType]: 316 if gotVersion, _ := out.GetVersion(); gotVersion != version2 { 317 t.Errorf("got version %q, want %q", gotVersion, version2) 318 } 319 if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshot2.Resources[types.Endpoint].Items) { 320 t.Errorf("got resources %v, want %v", out.(*cache.RawResponse).Resources, snapshot2.Resources[types.Endpoint].Items) 321 } 322 case <-time.After(time.Second): 323 t.Fatal("failed to receive snapshot response") 324 } 325 } 326 327 func TestConcurrentSetWatch(t *testing.T) { 328 c := cache.NewSnapshotCache(false, group{}, logger{t: t}) 329 for i := 0; i < 50; i++ { 330 t.Run(fmt.Sprintf("worker%d", i), func(t *testing.T) { 331 t.Parallel() 332 id := fmt.Sprintf("%d", i%2) 333 value := make(chan cache.Response, 1) 334 if i < 25 { 335 snap := cache.Snapshot{} 336 snap.Resources[types.Endpoint] = cache.NewResources(fmt.Sprintf("v%d", i), []types.Resource{resource.MakeEndpoint(clusterName, uint32(i))}) 337 if err := c.SetSnapshot(context.Background(), id, snap); err != nil { 338 t.Fatalf("failed to set snapshot %q: %s", id, err) 339 } 340 } else { 341 cancel := c.CreateWatch(&discovery.DiscoveryRequest{ 342 Node: &core.Node{Id: id}, 343 TypeUrl: rsrc.EndpointType, 344 }, value) 345 346 defer cancel() 347 } 348 }) 349 } 350 } 351 352 func TestSnapshotCacheWatchCancel(t *testing.T) { 353 c := cache.NewSnapshotCache(true, group{}, logger{t: t}) 354 for _, typ := range testTypes { 355 value := make(chan cache.Response, 1) 356 cancel := c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]}, value) 357 cancel() 358 } 359 // should be status info for the node 360 if keys := c.GetStatusKeys(); len(keys) == 0 { 361 t.Error("got 0, want status info for the node") 362 } 363 364 for _, typ := range testTypes { 365 if count := c.GetStatusInfo(key).GetNumWatches(); count > 0 { 366 t.Errorf("watches should be released for %s", typ) 367 } 368 } 369 370 if empty := c.GetStatusInfo("missing"); empty != nil { 371 t.Errorf("should not return a status for unknown key: got %#v", empty) 372 } 373 } 374 375 func TestSnapshotCacheWatchTimeout(t *testing.T) { 376 c := cache.NewSnapshotCache(true, group{}, logger{t: t}) 377 378 // Create a non-buffered channel that will block sends. 379 watchCh := make(chan cache.Response) 380 c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: rsrc.EndpointType, ResourceNames: names[rsrc.EndpointType]}, watchCh) 381 382 // The first time we set the snapshot without consuming from the blocking channel, so this should time out. 383 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 384 defer cancel() 385 386 err := c.SetSnapshot(ctx, key, snapshot) 387 assert.EqualError(t, err, context.Canceled.Error()) 388 389 // Now reset the snapshot with a consuming channel. This verifies that if setting the snapshot fails, 390 // we can retry by setting the same snapshot. In other words, we keep the watch open even if we failed 391 // to respond to it within the deadline. 392 watchTriggeredCh := make(chan cache.Response) 393 go func() { 394 response := <-watchCh 395 watchTriggeredCh <- response 396 close(watchTriggeredCh) 397 }() 398 399 err = c.SetSnapshot(context.WithValue(context.Background(), testKey{}, "bar"), key, snapshot) 400 assert.NoError(t, err) 401 402 // The channel should get closed due to the watch trigger. 403 select { 404 case response := <-watchTriggeredCh: 405 // Verify that we pass the context through. 406 assert.Equal(t, response.GetContext().Value(testKey{}), "bar") 407 case <-time.After(time.Second): 408 t.Fatalf("timed out") 409 } 410 } 411 412 func TestSnapshotClear(t *testing.T) { 413 c := cache.NewSnapshotCache(true, group{}, logger{t: t}) 414 if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil { 415 t.Fatal(err) 416 } 417 c.ClearSnapshot(key) 418 if empty := c.GetStatusInfo(key); empty != nil { 419 t.Errorf("cache should be cleared") 420 } 421 if keys := c.GetStatusKeys(); len(keys) != 0 { 422 t.Errorf("keys should be empty") 423 } 424 }