github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/go-control-plane/pkg/cache/v3/delta_test.go (about) 1 package cache_test 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 12 core "github.com/hxx258456/ccgo/go-control-plane/envoy/config/core/v3" 13 discovery "github.com/hxx258456/ccgo/go-control-plane/envoy/service/discovery/v3" 14 "github.com/hxx258456/ccgo/go-control-plane/pkg/cache/types" 15 "github.com/hxx258456/ccgo/go-control-plane/pkg/cache/v3" 16 rsrc "github.com/hxx258456/ccgo/go-control-plane/pkg/resource/v3" 17 "github.com/hxx258456/ccgo/go-control-plane/pkg/server/stream/v3" 18 "github.com/hxx258456/ccgo/go-control-plane/pkg/test/resource/v3" 19 ) 20 21 func TestSnapshotCacheDeltaWatch(t *testing.T) { 22 c := cache.NewSnapshotCache(false, group{}, logger{t: t}) 23 watches := make(map[string]chan cache.DeltaResponse) 24 25 // Make our initial request as a wildcard to get all resources and make sure the wildcard requesting works as intended 26 for _, typ := range testTypes { 27 watches[typ] = make(chan cache.DeltaResponse, 1) 28 c.CreateDeltaWatch(&discovery.DeltaDiscoveryRequest{ 29 Node: &core.Node{ 30 Id: "node", 31 }, 32 TypeUrl: typ, 33 ResourceNamesSubscribe: names[typ], 34 }, stream.NewStreamState(true, nil), watches[typ]) 35 } 36 37 if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil { 38 t.Fatal(err) 39 } 40 41 versionMap := make(map[string]map[string]string) 42 for _, typ := range testTypes { 43 t.Run(typ, func(t *testing.T) { 44 select { 45 case out := <-watches[typ]: 46 if !reflect.DeepEqual(cache.IndexRawResourcesByName(out.(*cache.RawDeltaResponse).Resources), snapshot.GetResources(typ)) { 47 t.Errorf("got resources %v, want %v", out.(*cache.RawDeltaResponse).Resources, snapshot.GetResources(typ)) 48 } 49 vMap := out.GetNextVersionMap() 50 versionMap[typ] = vMap 51 case <-time.After(time.Second): 52 t.Fatal("failed to receive snapshot response") 53 } 54 }) 55 } 56 57 // On re-request we want to use non-wildcard so we can verify the logic path of not requesting 58 // all resources as well as individual resource removals 59 for _, typ := range testTypes { 60 watches[typ] = make(chan cache.DeltaResponse, 1) 61 c.CreateDeltaWatch(&discovery.DeltaDiscoveryRequest{ 62 Node: &core.Node{ 63 Id: "node", 64 }, 65 TypeUrl: typ, 66 ResourceNamesSubscribe: names[typ], 67 }, stream.NewStreamState(false, versionMap[typ]), watches[typ]) 68 } 69 70 if count := c.GetStatusInfo(key).GetNumDeltaWatches(); count != len(testTypes) { 71 t.Errorf("watches should be created for the latest version, saw %d watches expected %d", count, len(testTypes)) 72 } 73 74 // set partially-versioned snapshot 75 snapshot2 := snapshot 76 snapshot2.Resources[types.Endpoint] = cache.NewResources(version2, []types.Resource{resource.MakeEndpoint(clusterName, 9090)}) 77 if err := c.SetSnapshot(context.Background(), key, snapshot2); err != nil { 78 t.Fatal(err) 79 } 80 if count := c.GetStatusInfo(key).GetNumDeltaWatches(); count != len(testTypes)-1 { 81 t.Errorf("watches should be preserved for all but one, got: %d open watches instead of the expected %d open watches", count, len(testTypes)-1) 82 } 83 84 // validate response for endpoints 85 select { 86 case out := <-watches[testTypes[0]]: 87 if !reflect.DeepEqual(cache.IndexRawResourcesByName(out.(*cache.RawDeltaResponse).Resources), snapshot2.GetResources(rsrc.EndpointType)) { 88 t.Fatalf("got resources %v, want %v", out.(*cache.RawDeltaResponse).Resources, snapshot2.GetResources(rsrc.EndpointType)) 89 } 90 vMap := out.GetNextVersionMap() 91 versionMap[testTypes[0]] = vMap 92 case <-time.After(time.Second): 93 t.Fatal("failed to receive snapshot response") 94 } 95 } 96 97 func TestDeltaRemoveResources(t *testing.T) { 98 c := cache.NewSnapshotCache(false, group{}, logger{t: t}) 99 watches := make(map[string]chan cache.DeltaResponse) 100 streams := make(map[string]*stream.StreamState) 101 102 for _, typ := range testTypes { 103 watches[typ] = make(chan cache.DeltaResponse, 1) 104 state := stream.NewStreamState(true, make(map[string]string)) 105 streams[typ] = &state 106 // We don't specify any resource name subscriptions here because we want to make sure we test wildcard 107 // functionality. This means we should receive all resources back without requesting a subscription by name. 108 c.CreateDeltaWatch(&discovery.DeltaDiscoveryRequest{ 109 Node: &core.Node{ 110 Id: "node", 111 }, 112 TypeUrl: typ, 113 }, *streams[typ], watches[typ]) 114 } 115 116 if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil { 117 t.Fatal(err) 118 } 119 120 for _, typ := range testTypes { 121 t.Run(typ, func(t *testing.T) { 122 select { 123 case out := <-watches[typ]: 124 if !reflect.DeepEqual(cache.IndexRawResourcesByName(out.(*cache.RawDeltaResponse).Resources), snapshot.GetResources(typ)) { 125 t.Errorf("got resources %v, want %v", out.(*cache.RawDeltaResponse).Resources, snapshot.GetResources(typ)) 126 } 127 nextVersionMap := out.GetNextVersionMap() 128 streams[typ].SetResourceVersions(nextVersionMap) 129 case <-time.After(time.Second): 130 t.Fatal("failed to receive a snapshot response") 131 } 132 }) 133 } 134 135 // We want to continue to do wildcard requests here so we can later 136 // test the removal of certain resources from a partial snapshot 137 for _, typ := range testTypes { 138 watches[typ] = make(chan cache.DeltaResponse, 1) 139 c.CreateDeltaWatch(&discovery.DeltaDiscoveryRequest{ 140 Node: &core.Node{ 141 Id: "node", 142 }, 143 TypeUrl: typ, 144 }, *streams[typ], watches[typ]) 145 } 146 147 if count := c.GetStatusInfo(key).GetNumDeltaWatches(); count != len(testTypes) { 148 t.Errorf("watches should be created for the latest version, saw %d watches expected %d", count, len(testTypes)) 149 } 150 151 // set a partially versioned snapshot with no endpoints 152 snapshot2 := snapshot 153 snapshot2.Resources[types.Endpoint] = cache.NewResources(version2, []types.Resource{}) 154 if err := c.SetSnapshot(context.Background(), key, snapshot2); err != nil { 155 t.Fatal(err) 156 } 157 158 // validate response for endpoints 159 select { 160 case out := <-watches[testTypes[0]]: 161 if !reflect.DeepEqual(cache.IndexRawResourcesByName(out.(*cache.RawDeltaResponse).Resources), snapshot2.GetResources(rsrc.EndpointType)) { 162 t.Fatalf("got resources %v, want %v", out.(*cache.RawDeltaResponse).Resources, snapshot2.GetResources(rsrc.EndpointType)) 163 } 164 nextVersionMap := out.GetNextVersionMap() 165 166 // make sure the version maps are different since we no longer are tracking any endpoint resources 167 if reflect.DeepEqual(streams[testTypes[0]].GetResourceVersions(), nextVersionMap) { 168 t.Fatalf("versionMap for the endpoint resource type did not change, received: %v, instead of an empty map", nextVersionMap) 169 } 170 case <-time.After(time.Second): 171 t.Fatal("failed to receive snapshot response") 172 } 173 } 174 175 func TestConcurrentSetDeltaWatch(t *testing.T) { 176 c := cache.NewSnapshotCache(false, group{}, logger{t: t}) 177 for i := 0; i < 50; i++ { 178 version := fmt.Sprintf("v%d", i) 179 func(i int) { 180 t.Run(fmt.Sprintf("worker%d", i), func(t *testing.T) { 181 t.Parallel() 182 id := fmt.Sprintf("%d", i%2) 183 responses := make(chan cache.DeltaResponse, 1) 184 if i < 25 { 185 snap, err := cache.NewSnapshot("", map[rsrc.Type][]types.Resource{}) 186 if err != nil { 187 t.Fatal(err) 188 } 189 snap.Resources[types.Endpoint] = cache.NewResources(version, []types.Resource{resource.MakeEndpoint(clusterName, uint32(i))}) 190 if err := c.SetSnapshot(context.Background(), key, snap); err != nil { 191 t.Fatalf("snapshot failed: %s", err) 192 } 193 } else { 194 cancel := c.CreateDeltaWatch(&discovery.DeltaDiscoveryRequest{ 195 Node: &core.Node{ 196 Id: id, 197 }, 198 TypeUrl: rsrc.EndpointType, 199 ResourceNamesSubscribe: []string{clusterName}, 200 }, stream.NewStreamState(false, make(map[string]string)), responses) 201 202 defer cancel() 203 } 204 }) 205 }(i) 206 } 207 } 208 209 type testKey struct{} 210 211 func TestSnapshotDeltaCacheWatchTimeout(t *testing.T) { 212 c := cache.NewSnapshotCache(true, group{}, logger{t: t}) 213 214 // Create a non-buffered channel that will block sends. 215 watchCh := make(chan cache.DeltaResponse) 216 c.CreateDeltaWatch(&discovery.DeltaDiscoveryRequest{ 217 Node: &core.Node{ 218 Id: key, 219 }, 220 TypeUrl: rsrc.EndpointType, 221 ResourceNamesSubscribe: names[rsrc.EndpointType], 222 }, stream.NewStreamState(false, map[string]string{names[rsrc.EndpointType][0]: ""}), watchCh) 223 224 // The first time we set the snapshot without consuming from the blocking channel, so this should time out. 225 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 226 defer cancel() 227 228 err := c.SetSnapshot(ctx, key, snapshot) 229 assert.EqualError(t, err, context.Canceled.Error()) 230 231 // Now reset the snapshot with a consuming channel. This verifies that if setting the snapshot fails, 232 // we can retry by setting the same snapshot. In other words, we keep the watch open even if we failed 233 // to respond to it within the deadline. 234 watchTriggeredCh := make(chan cache.DeltaResponse) 235 go func() { 236 response := <-watchCh 237 watchTriggeredCh <- response 238 close(watchTriggeredCh) 239 }() 240 241 err = c.SetSnapshot(context.WithValue(context.Background(), testKey{}, "bar"), key, snapshot) 242 assert.NoError(t, err) 243 244 // The channel should get closed due to the watch trigger. 245 select { 246 case response := <-watchTriggeredCh: 247 // Verify that we pass the context through. 248 assert.Equal(t, response.GetContext().Value(testKey{}), "bar") 249 case <-time.After(time.Second): 250 t.Fatalf("timed out") 251 } 252 } 253 254 func TestSnapshotCacheDeltaWatchCancel(t *testing.T) { 255 c := cache.NewSnapshotCache(true, group{}, logger{t: t}) 256 for _, typ := range testTypes { 257 responses := make(chan cache.DeltaResponse, 1) 258 cancel := c.CreateDeltaWatch(&discovery.DeltaDiscoveryRequest{ 259 Node: &core.Node{ 260 Id: key, 261 }, 262 TypeUrl: typ, 263 ResourceNamesSubscribe: names[typ], 264 }, stream.NewStreamState(false, make(map[string]string)), responses) 265 266 // Cancel the watch 267 cancel() 268 } 269 // c.GetStatusKeys() should return at least 1 because we register a node ID with the above watch creations 270 if keys := c.GetStatusKeys(); len(keys) == 0 { 271 t.Errorf("expected to see a status info registered for watch, saw %d entries", len(keys)) 272 } 273 274 for _, typ := range testTypes { 275 if count := c.GetStatusInfo(key).GetNumDeltaWatches(); count > 0 { 276 t.Errorf("watches should be released for %s", typ) 277 } 278 } 279 280 if s := c.GetStatusInfo("missing"); s != nil { 281 t.Errorf("should not return a status for unknown key: got %#v", s) 282 } 283 }