gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/go-control-plane/pkg/server/v3/server_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 server_test 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "reflect" 22 "sync" 23 "testing" 24 "time" 25 26 "gitee.com/ks-custle/core-gm/grpc" 27 28 core "gitee.com/ks-custle/core-gm/go-control-plane/envoy/config/core/v3" 29 discovery "gitee.com/ks-custle/core-gm/go-control-plane/envoy/service/discovery/v3" 30 "gitee.com/ks-custle/core-gm/go-control-plane/pkg/cache/types" 31 "gitee.com/ks-custle/core-gm/go-control-plane/pkg/cache/v3" 32 rsrc "gitee.com/ks-custle/core-gm/go-control-plane/pkg/resource/v3" 33 "gitee.com/ks-custle/core-gm/go-control-plane/pkg/server/v3" 34 "gitee.com/ks-custle/core-gm/go-control-plane/pkg/test/resource/v3" 35 ) 36 37 type mockConfigWatcher struct { 38 counts map[string]int 39 deltaCounts map[string]int 40 responses map[string][]cache.Response 41 deltaResponses map[string][]cache.DeltaResponse 42 watches int 43 deltaWatches int 44 45 mu *sync.RWMutex 46 } 47 48 func (config *mockConfigWatcher) CreateWatch(req *discovery.DiscoveryRequest, out chan cache.Response) func() { 49 config.counts[req.TypeUrl] = config.counts[req.TypeUrl] + 1 50 if len(config.responses[req.TypeUrl]) > 0 { 51 out <- config.responses[req.TypeUrl][0] 52 config.responses[req.TypeUrl] = config.responses[req.TypeUrl][1:] 53 } else { 54 config.watches++ 55 return func() { 56 config.watches-- 57 } 58 } 59 return nil 60 } 61 62 //goland:noinspection GoUnusedParameter 63 func (config *mockConfigWatcher) Fetch(ctx context.Context, req *discovery.DiscoveryRequest) (cache.Response, error) { 64 if len(config.responses[req.TypeUrl]) > 0 { 65 out := config.responses[req.TypeUrl][0] 66 config.responses[req.TypeUrl] = config.responses[req.TypeUrl][1:] 67 return out, nil 68 } 69 return nil, errors.New("missing") 70 } 71 72 func makeMockConfigWatcher() *mockConfigWatcher { 73 return &mockConfigWatcher{ 74 counts: make(map[string]int), 75 deltaCounts: make(map[string]int), 76 mu: &sync.RWMutex{}, 77 } 78 } 79 80 type mockStream struct { 81 t *testing.T 82 ctx context.Context 83 recv chan *discovery.DiscoveryRequest 84 sent chan *discovery.DiscoveryResponse 85 nonce int 86 sendError bool 87 grpc.ServerStream 88 } 89 90 func (stream *mockStream) Context() context.Context { 91 return stream.ctx 92 } 93 94 func (stream *mockStream) Send(resp *discovery.DiscoveryResponse) error { 95 // check that nonce is monotonically incrementing 96 stream.nonce = stream.nonce + 1 97 if resp.Nonce != fmt.Sprintf("%d", stream.nonce) { 98 stream.t.Errorf("Nonce => got %q, want %d", resp.Nonce, stream.nonce) 99 } 100 // check that version is set 101 if resp.VersionInfo == "" { 102 stream.t.Error("VersionInfo => got none, want non-empty") 103 } 104 // check resources are non-empty 105 if len(resp.Resources) == 0 { 106 stream.t.Error("Resources => got none, want non-empty") 107 } 108 // check that type URL matches in resources 109 if resp.TypeUrl == "" { 110 stream.t.Error("TypeUrl => got none, want non-empty") 111 } 112 for _, res := range resp.Resources { 113 if res.TypeUrl != resp.TypeUrl { 114 stream.t.Errorf("TypeUrl => got %q, want %q", res.TypeUrl, resp.TypeUrl) 115 } 116 } 117 stream.sent <- resp 118 if stream.sendError { 119 return errors.New("send error") 120 } 121 return nil 122 } 123 124 func (stream *mockStream) Recv() (*discovery.DiscoveryRequest, error) { 125 req, more := <-stream.recv 126 if !more { 127 return nil, errors.New("empty") 128 } 129 return req, nil 130 } 131 132 func makeMockStream(t *testing.T) *mockStream { 133 return &mockStream{ 134 t: t, 135 ctx: context.Background(), 136 sent: make(chan *discovery.DiscoveryResponse, 10), 137 recv: make(chan *discovery.DiscoveryRequest, 10), 138 } 139 } 140 141 const ( 142 clusterName = "cluster0" 143 routeName = "route0" 144 listenerName = "listener0" 145 secretName = "secret0" 146 runtimeName = "runtime0" 147 extensionConfigName = "extensionConfig0" 148 ) 149 150 var ( 151 node = &core.Node{ 152 Id: "test-id", 153 Cluster: "test-cluster", 154 } 155 endpoint = resource.MakeEndpoint(clusterName, 8080) 156 cluster = resource.MakeCluster(resource.Ads, clusterName) 157 route = resource.MakeRoute(routeName, clusterName) 158 listener = resource.MakeHTTPListener(resource.Ads, listenerName, 80, routeName) 159 secret = resource.MakeSecrets(secretName, "test")[0] 160 runtime = resource.MakeRuntime(runtimeName) 161 extensionConfig = resource.MakeExtensionConfig(resource.Ads, extensionConfigName, routeName) 162 opaque = &core.Address{} 163 opaqueType = "unknown-type" 164 testTypes = []string{ 165 rsrc.EndpointType, 166 rsrc.ClusterType, 167 rsrc.RouteType, 168 rsrc.ListenerType, 169 rsrc.SecretType, 170 rsrc.RuntimeType, 171 rsrc.ExtensionConfigType, 172 opaqueType, 173 } 174 ) 175 176 func makeResponses() map[string][]cache.Response { 177 return map[string][]cache.Response{ 178 rsrc.EndpointType: { 179 &cache.RawResponse{ 180 Version: "1", 181 Resources: []types.ResourceWithTTL{{Resource: endpoint}}, 182 Request: &discovery.DiscoveryRequest{TypeUrl: rsrc.EndpointType}, 183 }, 184 }, 185 rsrc.ClusterType: { 186 &cache.RawResponse{ 187 Version: "2", 188 Resources: []types.ResourceWithTTL{{Resource: cluster}}, 189 Request: &discovery.DiscoveryRequest{TypeUrl: rsrc.ClusterType}, 190 }, 191 }, 192 rsrc.RouteType: { 193 &cache.RawResponse{ 194 Version: "3", 195 Resources: []types.ResourceWithTTL{{Resource: route}}, 196 Request: &discovery.DiscoveryRequest{TypeUrl: rsrc.RouteType}, 197 }, 198 }, 199 rsrc.ListenerType: { 200 &cache.RawResponse{ 201 Version: "4", 202 Resources: []types.ResourceWithTTL{{Resource: listener}}, 203 Request: &discovery.DiscoveryRequest{TypeUrl: rsrc.ListenerType}, 204 }, 205 }, 206 rsrc.SecretType: { 207 &cache.RawResponse{ 208 Version: "5", 209 Resources: []types.ResourceWithTTL{{Resource: secret}}, 210 Request: &discovery.DiscoveryRequest{TypeUrl: rsrc.SecretType}, 211 }, 212 }, 213 rsrc.RuntimeType: { 214 &cache.RawResponse{ 215 Version: "6", 216 Resources: []types.ResourceWithTTL{{Resource: runtime}}, 217 Request: &discovery.DiscoveryRequest{TypeUrl: rsrc.RuntimeType}, 218 }, 219 }, 220 rsrc.ExtensionConfigType: { 221 &cache.RawResponse{ 222 Version: "7", 223 Resources: []types.ResourceWithTTL{{Resource: extensionConfig}}, 224 Request: &discovery.DiscoveryRequest{TypeUrl: rsrc.ExtensionConfigType}, 225 }, 226 }, 227 // Pass-through type (xDS does not exist for this type) 228 opaqueType: { 229 &cache.RawResponse{ 230 Version: "8", 231 Resources: []types.ResourceWithTTL{{Resource: opaque}}, 232 Request: &discovery.DiscoveryRequest{TypeUrl: opaqueType}, 233 }, 234 }, 235 } 236 } 237 238 func TestServerShutdown(t *testing.T) { 239 for _, typ := range testTypes { 240 t.Run(typ, func(t *testing.T) { 241 config := makeMockConfigWatcher() 242 config.responses = makeResponses() 243 shutdown := make(chan bool) 244 ctx, cancel := context.WithCancel(context.Background()) 245 s := server.NewServer(ctx, config, server.CallbackFuncs{}) 246 247 // make a request 248 resp := makeMockStream(t) 249 resp.recv <- &discovery.DiscoveryRequest{Node: node, TypeUrl: typ} 250 go func(rType string) { 251 var err error 252 switch rType { 253 case rsrc.EndpointType: 254 err = s.StreamEndpoints(resp) 255 case rsrc.ClusterType: 256 err = s.StreamClusters(resp) 257 case rsrc.RouteType: 258 err = s.StreamRoutes(resp) 259 case rsrc.ListenerType: 260 err = s.StreamListeners(resp) 261 case rsrc.SecretType: 262 err = s.StreamSecrets(resp) 263 case rsrc.RuntimeType: 264 err = s.StreamRuntime(resp) 265 case rsrc.ExtensionConfigType: 266 err = s.StreamExtensionConfigs(resp) 267 case opaqueType: 268 err = s.StreamAggregatedResources(resp) 269 } 270 if err != nil { 271 t.Errorf("Stream() => got %v, want no error", err) 272 } 273 shutdown <- true 274 }(typ) 275 276 go func() { 277 defer cancel() 278 }() 279 280 select { 281 case <-shutdown: 282 case <-time.After(1 * time.Second): 283 t.Fatalf("got no response") 284 } 285 }) 286 } 287 } 288 289 func TestResponseHandlers(t *testing.T) { 290 for _, typ := range testTypes { 291 t.Run(typ, func(t *testing.T) { 292 config := makeMockConfigWatcher() 293 config.responses = makeResponses() 294 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 295 296 // make a request 297 resp := makeMockStream(t) 298 resp.recv <- &discovery.DiscoveryRequest{Node: node, TypeUrl: typ} 299 go func(rType string) { 300 var err error 301 switch rType { 302 case rsrc.EndpointType: 303 err = s.StreamEndpoints(resp) 304 case rsrc.ClusterType: 305 err = s.StreamClusters(resp) 306 case rsrc.RouteType: 307 err = s.StreamRoutes(resp) 308 case rsrc.ListenerType: 309 err = s.StreamListeners(resp) 310 case rsrc.SecretType: 311 err = s.StreamSecrets(resp) 312 case rsrc.RuntimeType: 313 err = s.StreamRuntime(resp) 314 case rsrc.ExtensionConfigType: 315 err = s.StreamExtensionConfigs(resp) 316 case opaqueType: 317 err = s.StreamAggregatedResources(resp) 318 } 319 if err != nil { 320 t.Errorf("Stream() => got %v, want no error", err) 321 } 322 }(typ) 323 324 // check a response 325 select { 326 case <-resp.sent: 327 close(resp.recv) 328 if want := map[string]int{typ: 1}; !reflect.DeepEqual(want, config.counts) { 329 t.Errorf("watch counts => got %v, want %v", config.counts, want) 330 } 331 case <-time.After(1 * time.Second): 332 t.Fatalf("got no response") 333 } 334 }) 335 } 336 } 337 338 func TestFetch(t *testing.T) { 339 config := makeMockConfigWatcher() 340 config.responses = makeResponses() 341 342 requestCount := 0 343 responseCount := 0 344 callbackError := false 345 346 cb := server.CallbackFuncs{ 347 StreamOpenFunc: func(ctx context.Context, i int64, s string) error { 348 if callbackError { 349 return errors.New("stream open error") 350 } 351 return nil 352 }, 353 FetchRequestFunc: func(ctx context.Context, request *discovery.DiscoveryRequest) error { 354 if callbackError { 355 return errors.New("fetch request error") 356 } 357 requestCount++ 358 return nil 359 }, 360 FetchResponseFunc: func(request *discovery.DiscoveryRequest, response *discovery.DiscoveryResponse) { 361 responseCount++ 362 }, 363 } 364 365 s := server.NewServer(context.Background(), config, cb) 366 if out, err := s.FetchEndpoints(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil { 367 t.Errorf("unexpected empty or error for endpoints: %v", err) 368 } 369 if out, err := s.FetchClusters(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil { 370 t.Errorf("unexpected empty or error for clusters: %v", err) 371 } 372 if out, err := s.FetchRoutes(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil { 373 t.Errorf("unexpected empty or error for routes: %v", err) 374 } 375 if out, err := s.FetchListeners(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil { 376 t.Errorf("unexpected empty or error for listeners: %v", err) 377 } 378 if out, err := s.FetchSecrets(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil { 379 t.Errorf("unexpected empty or error for secrets: %v", err) 380 } 381 if out, err := s.FetchRuntime(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil { 382 t.Errorf("unexpected empty or error for runtime: %v", err) 383 } 384 if out, err := s.FetchExtensionConfigs(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil { 385 t.Errorf("unexpected empty or error for extensionConfigs: %v", err) 386 } 387 388 // try again and expect empty results 389 if out, err := s.FetchEndpoints(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil { 390 t.Errorf("expected empty or error for endpoints: %v", err) 391 } 392 if out, err := s.FetchClusters(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil { 393 t.Errorf("expected empty or error for clusters: %v", err) 394 } 395 if out, err := s.FetchRoutes(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil { 396 t.Errorf("expected empty or error for routes: %v", err) 397 } 398 if out, err := s.FetchListeners(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil { 399 t.Errorf("expected empty or error for listeners: %v", err) 400 } 401 402 // try empty requests: not valid in a real gRPC server 403 if out, err := s.FetchEndpoints(context.Background(), nil); out != nil { 404 t.Errorf("expected empty on empty request: %v", err) 405 } 406 if out, err := s.FetchClusters(context.Background(), nil); out != nil { 407 t.Errorf("expected empty on empty request: %v", err) 408 } 409 if out, err := s.FetchRoutes(context.Background(), nil); out != nil { 410 t.Errorf("expected empty on empty request: %v", err) 411 } 412 if out, err := s.FetchListeners(context.Background(), nil); out != nil { 413 t.Errorf("expected empty on empty request: %v", err) 414 } 415 if out, err := s.FetchSecrets(context.Background(), nil); out != nil { 416 t.Errorf("expected empty on empty request: %v", err) 417 } 418 if out, err := s.FetchRuntime(context.Background(), nil); out != nil { 419 t.Errorf("expected empty on empty request: %v", err) 420 } 421 if out, err := s.FetchExtensionConfigs(context.Background(), nil); out != nil { 422 t.Errorf("expected empty on empty request: %v", err) 423 } 424 425 // send error from callback 426 callbackError = true 427 if out, err := s.FetchEndpoints(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil || err == nil { 428 t.Errorf("expected empty or error due to callback error") 429 } 430 if out, err := s.FetchClusters(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil || err == nil { 431 t.Errorf("expected empty or error due to callback error") 432 } 433 if out, err := s.FetchRoutes(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil || err == nil { 434 t.Errorf("expected empty or error due to callback error") 435 } 436 if out, err := s.FetchListeners(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil || err == nil { 437 t.Errorf("expected empty or error due to callback error") 438 } 439 440 // verify fetch callbacks 441 if want := 11; requestCount != want { 442 t.Errorf("unexpected number of fetch requests: got %d, want %d", requestCount, want) 443 } 444 if want := 7; responseCount != want { 445 t.Errorf("unexpected number of fetch responses: got %d, want %d", responseCount, want) 446 } 447 } 448 449 func TestSendError(t *testing.T) { 450 for _, typ := range testTypes { 451 t.Run(typ, func(t *testing.T) { 452 config := makeMockConfigWatcher() 453 config.responses = makeResponses() 454 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 455 456 // make a request 457 resp := makeMockStream(t) 458 resp.sendError = true 459 resp.recv <- &discovery.DiscoveryRequest{ 460 Node: node, 461 TypeUrl: typ, 462 } 463 464 // check that response fails since send returns error 465 if err := s.StreamAggregatedResources(resp); err == nil { 466 t.Error("Stream() => got no error, want send error") 467 } 468 469 close(resp.recv) 470 }) 471 } 472 } 473 474 func TestStaleNonce(t *testing.T) { 475 for _, typ := range testTypes { 476 t.Run(typ, func(t *testing.T) { 477 config := makeMockConfigWatcher() 478 config.responses = makeResponses() 479 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 480 481 resp := makeMockStream(t) 482 resp.recv <- &discovery.DiscoveryRequest{ 483 Node: node, 484 TypeUrl: typ, 485 } 486 stop := make(chan struct{}) 487 go func() { 488 if err := s.StreamAggregatedResources(resp); err != nil { 489 t.Errorf("StreamAggregatedResources() => got %v, want no error", err) 490 } 491 // should be two watches called 492 if want := map[string]int{typ: 2}; !reflect.DeepEqual(want, config.counts) { 493 t.Errorf("watch counts => got %v, want %v", config.counts, want) 494 } 495 close(stop) 496 }() 497 select { 498 case <-resp.sent: 499 // stale request 500 resp.recv <- &discovery.DiscoveryRequest{ 501 Node: node, 502 TypeUrl: typ, 503 ResponseNonce: "xyz", 504 } 505 // fresh request 506 resp.recv <- &discovery.DiscoveryRequest{ 507 VersionInfo: "1", 508 Node: node, 509 TypeUrl: typ, 510 ResponseNonce: "1", 511 } 512 close(resp.recv) 513 case <-time.After(1 * time.Second): 514 t.Fatalf("got %d messages on the stream, not 4", resp.nonce) 515 } 516 <-stop 517 }) 518 } 519 } 520 521 func TestAggregatedHandlers(t *testing.T) { 522 config := makeMockConfigWatcher() 523 config.responses = makeResponses() 524 resp := makeMockStream(t) 525 526 resp.recv <- &discovery.DiscoveryRequest{ 527 Node: node, 528 TypeUrl: rsrc.ListenerType, 529 } 530 // Delta compress node 531 resp.recv <- &discovery.DiscoveryRequest{ 532 TypeUrl: rsrc.ClusterType, 533 } 534 resp.recv <- &discovery.DiscoveryRequest{ 535 TypeUrl: rsrc.EndpointType, 536 ResourceNames: []string{clusterName}, 537 } 538 resp.recv <- &discovery.DiscoveryRequest{ 539 TypeUrl: rsrc.RouteType, 540 ResourceNames: []string{routeName}, 541 } 542 543 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 544 go func() { 545 if err := s.StreamAggregatedResources(resp); err != nil { 546 t.Errorf("StreamAggregatedResources() => got %v, want no error", err) 547 } 548 }() 549 550 count := 0 551 for { 552 select { 553 case <-resp.sent: 554 count++ 555 if count >= 4 { 556 close(resp.recv) 557 if want := map[string]int{ 558 rsrc.EndpointType: 1, 559 rsrc.ClusterType: 1, 560 rsrc.RouteType: 1, 561 rsrc.ListenerType: 1, 562 }; !reflect.DeepEqual(want, config.counts) { 563 t.Errorf("watch counts => got %v, want %v", config.counts, want) 564 } 565 566 // got all messages 567 return 568 } 569 case <-time.After(1 * time.Second): 570 t.Fatalf("got %d messages on the stream, not 4", count) 571 } 572 } 573 } 574 575 func TestAggregateRequestType(t *testing.T) { 576 config := makeMockConfigWatcher() 577 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 578 resp := makeMockStream(t) 579 resp.recv <- &discovery.DiscoveryRequest{Node: node} 580 if err := s.StreamAggregatedResources(resp); err == nil { 581 t.Error("StreamAggregatedResources() => got nil, want an error") 582 } 583 } 584 585 func TestCancellations(t *testing.T) { 586 config := makeMockConfigWatcher() 587 resp := makeMockStream(t) 588 for _, typ := range testTypes { 589 resp.recv <- &discovery.DiscoveryRequest{ 590 Node: node, 591 TypeUrl: typ, 592 } 593 } 594 close(resp.recv) 595 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 596 if err := s.StreamAggregatedResources(resp); err != nil { 597 t.Errorf("StreamAggregatedResources() => got %v, want no error", err) 598 } 599 if config.watches != 0 { 600 t.Errorf("Expect all watches canceled, got %q", config.watches) 601 } 602 } 603 604 func TestOpaqueRequestsChannelMuxing(t *testing.T) { 605 config := makeMockConfigWatcher() 606 resp := makeMockStream(t) 607 for i := 0; i < 10; i++ { 608 resp.recv <- &discovery.DiscoveryRequest{ 609 Node: node, 610 TypeUrl: fmt.Sprintf("%s%d", opaqueType, i%2), 611 // each subsequent request is assumed to supercede the previous request 612 ResourceNames: []string{fmt.Sprintf("%d", i)}, 613 } 614 } 615 close(resp.recv) 616 s := server.NewServer(context.Background(), config, server.CallbackFuncs{}) 617 if err := s.StreamAggregatedResources(resp); err != nil { 618 t.Errorf("StreamAggregatedResources() => got %v, want no error", err) 619 } 620 if config.watches != 0 { 621 t.Errorf("Expect all watches canceled, got %q", config.watches) 622 } 623 } 624 625 func TestCallbackError(t *testing.T) { 626 for _, typ := range testTypes { 627 t.Run(typ, func(t *testing.T) { 628 config := makeMockConfigWatcher() 629 config.responses = makeResponses() 630 631 s := server.NewServer(context.Background(), config, server.CallbackFuncs{ 632 StreamOpenFunc: func(ctx context.Context, i int64, s string) error { 633 return errors.New("stream open error") 634 }, 635 }) 636 637 // make a request 638 resp := makeMockStream(t) 639 resp.recv <- &discovery.DiscoveryRequest{ 640 Node: node, 641 TypeUrl: typ, 642 } 643 644 // check that response fails since stream open returns error 645 if err := s.StreamAggregatedResources(resp); err == nil { 646 t.Error("Stream() => got no error, want error") 647 } 648 649 close(resp.recv) 650 }) 651 } 652 }