gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/go-control-plane/pkg/server/v3/delta_test.go (about) 1 package server_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "testing" 8 "time" 9 10 "gitee.com/ks-custle/core-gm/grpc" 11 12 "github.com/stretchr/testify/assert" 13 14 discovery "gitee.com/ks-custle/core-gm/go-control-plane/envoy/service/discovery/v3" 15 "gitee.com/ks-custle/core-gm/go-control-plane/pkg/cache/types" 16 "gitee.com/ks-custle/core-gm/go-control-plane/pkg/cache/v3" 17 rsrc "gitee.com/ks-custle/core-gm/go-control-plane/pkg/resource/v3" 18 "gitee.com/ks-custle/core-gm/go-control-plane/pkg/server/stream/v3" 19 "gitee.com/ks-custle/core-gm/go-control-plane/pkg/server/v3" 20 ) 21 22 func (config *mockConfigWatcher) CreateDeltaWatch(req *discovery.DeltaDiscoveryRequest, state stream.StreamState, out chan cache.DeltaResponse) func() { 23 config.deltaCounts[req.TypeUrl] = config.deltaCounts[req.TypeUrl] + 1 24 25 if len(config.deltaResponses[req.TypeUrl]) > 0 { 26 res := config.deltaResponses[req.TypeUrl][0] 27 // In subscribed, we only want to send back what's changed if we detect changes 28 var subscribed []types.Resource 29 r, _ := res.GetDeltaDiscoveryResponse() 30 31 switch { 32 case state.IsWildcard(): 33 for _, resource := range r.Resources { 34 name := resource.GetName() 35 res, _ := cache.MarshalResource(resource) 36 37 nextVersion := cache.HashResource(res) 38 prevVersion, found := state.GetResourceVersions()[name] 39 if !found || (prevVersion != nextVersion) { 40 state.GetResourceVersions()[name] = nextVersion 41 subscribed = append(subscribed, resource) 42 } 43 } 44 default: 45 for _, resource := range r.Resources { 46 res, _ := cache.MarshalResource(resource) 47 nextVersion := cache.HashResource(res) 48 for _, prevVersion := range state.GetResourceVersions() { 49 if prevVersion != nextVersion { 50 subscribed = append(subscribed, resource) 51 } 52 state.GetResourceVersions()[resource.GetName()] = nextVersion 53 } 54 } 55 } 56 57 out <- &cache.RawDeltaResponse{ 58 DeltaRequest: req, 59 Resources: subscribed, 60 SystemVersionInfo: "", 61 NextVersionMap: state.GetResourceVersions(), 62 } 63 } else { 64 config.deltaWatches++ 65 return func() { 66 config.deltaWatches-- 67 } 68 } 69 70 return nil 71 } 72 73 type mockDeltaStream struct { 74 t *testing.T 75 ctx context.Context 76 recv chan *discovery.DeltaDiscoveryRequest 77 sent chan *discovery.DeltaDiscoveryResponse 78 nonce int 79 sendError bool 80 grpc.ServerStream 81 } 82 83 func (stream *mockDeltaStream) Context() context.Context { 84 return stream.ctx 85 } 86 87 func (stream *mockDeltaStream) Send(resp *discovery.DeltaDiscoveryResponse) error { 88 // Check that nonce is incremented by one 89 stream.nonce = stream.nonce + 1 90 if resp.Nonce != fmt.Sprintf("%d", stream.nonce) { 91 stream.t.Errorf("Nonce => got %q, want %d", resp.Nonce, stream.nonce) 92 } 93 // Check that resources are non-empty 94 if len(resp.Resources) == 0 { 95 stream.t.Error("Resources => got none, want non-empty") 96 } 97 if resp.TypeUrl == "" { 98 stream.t.Error("TypeUrl => got none, want non-empty") 99 } 100 101 // Check that the per resource TypeURL is correctly set. 102 for _, res := range resp.Resources { 103 if res.Resource.TypeUrl != resp.TypeUrl { 104 stream.t.Errorf("TypeUrl => got %q, want %q", res.Resource.TypeUrl, resp.TypeUrl) 105 } 106 } 107 108 stream.sent <- resp 109 if stream.sendError { 110 return errors.New("send error") 111 } 112 return nil 113 } 114 115 func (stream *mockDeltaStream) Recv() (*discovery.DeltaDiscoveryRequest, error) { 116 req, more := <-stream.recv 117 if !more { 118 return nil, errors.New("empty") 119 } 120 return req, nil 121 } 122 123 func makeMockDeltaStream(t *testing.T) *mockDeltaStream { 124 return &mockDeltaStream{ 125 t: t, 126 ctx: context.Background(), 127 sent: make(chan *discovery.DeltaDiscoveryResponse, 10), 128 recv: make(chan *discovery.DeltaDiscoveryRequest, 10), 129 } 130 } 131 132 func makeDeltaResponses() map[string][]cache.DeltaResponse { 133 return map[string][]cache.DeltaResponse{ 134 rsrc.EndpointType: { 135 &cache.RawDeltaResponse{ 136 Resources: []types.Resource{endpoint}, 137 DeltaRequest: &discovery.DeltaDiscoveryRequest{TypeUrl: rsrc.EndpointType}, 138 SystemVersionInfo: "1", 139 }, 140 }, 141 rsrc.ClusterType: { 142 &cache.RawDeltaResponse{ 143 Resources: []types.Resource{cluster}, 144 DeltaRequest: &discovery.DeltaDiscoveryRequest{TypeUrl: rsrc.ClusterType}, 145 SystemVersionInfo: "2", 146 }, 147 }, 148 rsrc.RouteType: { 149 &cache.RawDeltaResponse{ 150 Resources: []types.Resource{route}, 151 DeltaRequest: &discovery.DeltaDiscoveryRequest{TypeUrl: rsrc.RouteType}, 152 SystemVersionInfo: "3", 153 }, 154 }, 155 rsrc.ListenerType: { 156 &cache.RawDeltaResponse{ 157 Resources: []types.Resource{listener}, 158 DeltaRequest: &discovery.DeltaDiscoveryRequest{TypeUrl: rsrc.ListenerType}, 159 SystemVersionInfo: "4", 160 }, 161 }, 162 rsrc.SecretType: { 163 &cache.RawDeltaResponse{ 164 SystemVersionInfo: "5", 165 Resources: []types.Resource{secret}, 166 DeltaRequest: &discovery.DeltaDiscoveryRequest{TypeUrl: rsrc.SecretType}, 167 }, 168 }, 169 rsrc.RuntimeType: { 170 &cache.RawDeltaResponse{ 171 SystemVersionInfo: "6", 172 Resources: []types.Resource{runtime}, 173 DeltaRequest: &discovery.DeltaDiscoveryRequest{TypeUrl: rsrc.RuntimeType}, 174 }, 175 }, 176 rsrc.ExtensionConfigType: { 177 &cache.RawDeltaResponse{ 178 SystemVersionInfo: "7", 179 Resources: []types.Resource{extensionConfig}, 180 DeltaRequest: &discovery.DeltaDiscoveryRequest{TypeUrl: rsrc.ExtensionConfigType}, 181 }, 182 }, 183 // Pass-through type (types without explicit handling) 184 opaqueType: { 185 &cache.RawDeltaResponse{ 186 SystemVersionInfo: "8", 187 Resources: []types.Resource{opaque}, 188 DeltaRequest: &discovery.DeltaDiscoveryRequest{TypeUrl: opaqueType}, 189 }, 190 }, 191 } 192 } 193 194 func process(typ string, resp *mockDeltaStream, s server.Server) error { 195 var err error 196 switch typ { 197 case rsrc.EndpointType: 198 err = s.DeltaEndpoints(resp) 199 case rsrc.ClusterType: 200 err = s.DeltaClusters(resp) 201 case rsrc.RouteType: 202 err = s.DeltaRoutes(resp) 203 case rsrc.ListenerType: 204 err = s.DeltaListeners(resp) 205 case rsrc.SecretType: 206 err = s.DeltaSecrets(resp) 207 case rsrc.RuntimeType: 208 err = s.DeltaRuntime(resp) 209 case rsrc.ExtensionConfigType: 210 err = s.DeltaExtensionConfigs(resp) 211 case opaqueType: 212 err = s.DeltaAggregatedResources(resp) 213 } 214 215 return err 216 } 217 218 func TestDeltaResponseHandlersWildcard(t *testing.T) { 219 for _, typ := range testTypes { 220 t.Run(typ, func(t *testing.T) { 221 config := makeMockConfigWatcher() 222 config.deltaResponses = makeDeltaResponses() 223 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 224 225 resp := makeMockDeltaStream(t) 226 // This is a wildcard request since we don't specify a list of resource subscriptions 227 resp.recv <- &discovery.DeltaDiscoveryRequest{Node: node, TypeUrl: typ} 228 229 go func() { 230 err := process(typ, resp, s) 231 assert.NoError(t, err) 232 }() 233 234 select { 235 case res := <-resp.sent: 236 close(resp.recv) 237 238 assert.Equal(t, 1, config.deltaCounts[typ]) 239 assert.Empty(t, res.GetSystemVersionInfo()) 240 case <-time.After(1 * time.Second): 241 t.Fatalf("got no response") 242 } 243 }) 244 } 245 } 246 247 func TestDeltaResponseHandlers(t *testing.T) { 248 for _, typ := range testTypes { 249 t.Run(typ, func(t *testing.T) { 250 config := makeMockConfigWatcher() 251 config.deltaResponses = makeDeltaResponses() 252 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 253 254 resp := makeMockDeltaStream(t) 255 // This is a wildcard request since we don't specify a list of resource subscriptions 256 res, err := config.deltaResponses[typ][0].GetDeltaDiscoveryResponse() 257 if err != nil { 258 t.Error(err) 259 } 260 // We only subscribe to one resource to see if we get the appropriate number of resources back 261 resp.recv <- &discovery.DeltaDiscoveryRequest{Node: node, TypeUrl: typ, ResourceNamesSubscribe: []string{res.Resources[0].Name}} 262 263 go func() { 264 err := process(typ, resp, s) 265 assert.NoError(t, err) 266 }() 267 268 select { 269 case res := <-resp.sent: 270 close(resp.recv) 271 272 assert.Equal(t, 1, config.deltaCounts[typ]) 273 assert.Empty(t, res.GetSystemVersionInfo()) 274 case <-time.After(1 * time.Second): 275 t.Fatalf("got no response") 276 } 277 }) 278 } 279 } 280 281 func TestSendDeltaError(t *testing.T) { 282 for _, typ := range testTypes { 283 t.Run(typ, func(t *testing.T) { 284 config := makeMockConfigWatcher() 285 config.deltaResponses = makeDeltaResponses() 286 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 287 288 // make a request with an error 289 resp := makeMockDeltaStream(t) 290 resp.sendError = true 291 resp.recv <- &discovery.DeltaDiscoveryRequest{ 292 Node: node, 293 TypeUrl: typ, 294 } 295 296 // check that response fails since we expect an error to come through 297 err := s.DeltaAggregatedResources(resp) 298 assert.Error(t, err) 299 300 close(resp.recv) 301 }) 302 } 303 } 304 305 func TestDeltaAggregatedHandlers(t *testing.T) { 306 config := makeMockConfigWatcher() 307 config.deltaResponses = makeDeltaResponses() 308 resp := makeMockDeltaStream(t) 309 310 reqs := []*discovery.DeltaDiscoveryRequest{ 311 { 312 Node: node, 313 TypeUrl: rsrc.ListenerType, 314 }, 315 { 316 Node: node, 317 TypeUrl: rsrc.ClusterType, 318 }, 319 { 320 Node: node, 321 TypeUrl: rsrc.EndpointType, 322 ResourceNamesSubscribe: []string{clusterName}, 323 }, 324 { 325 TypeUrl: rsrc.RouteType, 326 ResourceNamesSubscribe: []string{routeName}, 327 }, 328 { 329 TypeUrl: rsrc.SecretType, 330 ResourceNamesSubscribe: []string{secretName}, 331 }, 332 } 333 334 for _, r := range reqs { 335 resp.recv <- r 336 } 337 338 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 339 go func() { 340 err := s.DeltaAggregatedResources(resp) 341 assert.NoError(t, err) 342 }() 343 344 count := 0 345 for { 346 select { 347 case <-resp.sent: 348 count++ 349 if count >= len(reqs) { 350 close(resp.recv) 351 assert.Equal( 352 t, 353 map[string]int{rsrc.EndpointType: 1, rsrc.ClusterType: 1, rsrc.RouteType: 1, rsrc.ListenerType: 1, rsrc.SecretType: 1}, 354 config.deltaCounts, 355 ) 356 return 357 } 358 case <-time.After(1 * time.Second): 359 t.Fatalf("got %d messages on the stream, not 5", count) 360 } 361 } 362 } 363 364 func TestDeltaAggregateRequestType(t *testing.T) { 365 config := makeMockConfigWatcher() 366 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 367 resp := makeMockDeltaStream(t) 368 resp.recv <- &discovery.DeltaDiscoveryRequest{Node: node} 369 if err := s.DeltaAggregatedResources(resp); err == nil { 370 t.Error("DeltaAggregatedResources() => got nil, want an error") 371 } 372 } 373 374 func TestDeltaCancellations(t *testing.T) { 375 config := makeMockConfigWatcher() 376 resp := makeMockDeltaStream(t) 377 for _, typ := range testTypes { 378 resp.recv <- &discovery.DeltaDiscoveryRequest{ 379 Node: node, 380 TypeUrl: typ, 381 } 382 } 383 close(resp.recv) 384 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 385 if err := s.DeltaAggregatedResources(resp); err != nil { 386 t.Errorf("DeltaAggregatedResources() => got %v, want no error", err) 387 } 388 if config.watches != 0 { 389 t.Errorf("Expect all watches canceled, got %q", config.watches) 390 } 391 } 392 393 func TestDeltaOpaqueRequestsChannelMuxing(t *testing.T) { 394 config := makeMockConfigWatcher() 395 resp := makeMockDeltaStream(t) 396 for i := 0; i < 10; i++ { 397 resp.recv <- &discovery.DeltaDiscoveryRequest{ 398 Node: node, 399 TypeUrl: fmt.Sprintf("%s%d", opaqueType, i%2), 400 ResourceNamesSubscribe: []string{fmt.Sprintf("%d", i)}, 401 } 402 } 403 close(resp.recv) 404 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 405 if err := s.DeltaAggregatedResources(resp); err != nil { 406 t.Errorf("DeltaAggregatedResources() => got %v, want no error", err) 407 } 408 if config.watches != 0 { 409 t.Errorf("Expect all watches canceled, got %q", config.watches) 410 } 411 } 412 413 func TestDeltaCallbackError(t *testing.T) { 414 for _, typ := range testTypes { 415 t.Run(typ, func(t *testing.T) { 416 config := makeMockConfigWatcher() 417 config.deltaResponses = makeDeltaResponses() 418 419 s := server.NewServer(context.Background(), config, server.CallbackFuncs{ 420 DeltaStreamOpenFunc: func(ctx context.Context, i int64, s string) error { 421 return errors.New("stream open error") 422 }, 423 }) 424 425 // make a request 426 resp := makeMockDeltaStream(t) 427 resp.recv <- &discovery.DeltaDiscoveryRequest{ 428 Node: node, 429 TypeUrl: typ, 430 } 431 432 // check that response fails since stream open returns error 433 if err := s.DeltaAggregatedResources(resp); err == nil { 434 t.Error("Stream() => got no error, want error") 435 } 436 437 close(resp.recv) 438 }) 439 } 440 }