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