github.com/koko1123/flow-go-1@v0.29.6/cmd/scaffold_test.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "os" 11 "path/filepath" 12 "strings" 13 "sync" 14 "testing" 15 "time" 16 17 gcemd "cloud.google.com/go/compute/metadata" 18 "github.com/hashicorp/go-multierror" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 "google.golang.org/api/option" 22 "google.golang.org/grpc" 23 "google.golang.org/grpc/credentials/insecure" 24 25 "github.com/koko1123/flow-go-1/cmd/bootstrap/utils" 26 "github.com/koko1123/flow-go-1/model/bootstrap" 27 "github.com/koko1123/flow-go-1/module" 28 "github.com/koko1123/flow-go-1/module/component" 29 "github.com/koko1123/flow-go-1/module/irrecoverable" 30 "github.com/koko1123/flow-go-1/module/profiler" 31 "github.com/koko1123/flow-go-1/utils/unittest" 32 ) 33 34 // TestLoadSecretsEncryptionKey checks that the key file is read correctly if it exists 35 // and returns the expected sentinel error if it does not exist. 36 func TestLoadSecretsEncryptionKey(t *testing.T) { 37 myID := unittest.IdentifierFixture() 38 39 unittest.RunWithTempDir(t, func(dir string) { 40 path := filepath.Join(dir, fmt.Sprintf(bootstrap.PathSecretsEncryptionKey, myID)) 41 42 t.Run("should return ErrNotExist if file doesn't exist", func(t *testing.T) { 43 require.NoFileExists(t, path) 44 _, err := loadSecretsEncryptionKey(dir, myID) 45 assert.Error(t, err) 46 assert.True(t, errors.Is(err, os.ErrNotExist)) 47 }) 48 49 t.Run("should return key and no error if file exists", func(t *testing.T) { 50 err := os.MkdirAll(filepath.Join(dir, bootstrap.DirPrivateRoot, fmt.Sprintf("private-node-info_%v", myID)), 0700) 51 require.NoError(t, err) 52 key, err := utils.GenerateSecretsDBEncryptionKey() 53 require.NoError(t, err) 54 err = os.WriteFile(path, key, 0700) 55 require.NoError(t, err) 56 57 data, err := loadSecretsEncryptionKey(dir, myID) 58 assert.NoError(t, err) 59 assert.Equal(t, key, data) 60 }) 61 }) 62 } 63 64 // Test the components are started in the correct order, and are run serially 65 func TestComponentsRunSerially(t *testing.T) { 66 ctx, cancel := context.WithCancel(context.Background()) 67 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 68 69 nb := FlowNode("scaffold test") 70 nb.componentBuilder = component.NewComponentManagerBuilder() 71 72 logger := &testLog{} 73 74 name1 := "component 1" 75 nb.Component(name1, func(node *NodeConfig) (module.ReadyDoneAware, error) { 76 logger.Logf("%s initialized", name1) 77 return newMockReadyDone(logger, name1), nil 78 }) 79 80 name2 := "component 2" 81 nb.Component(name2, func(node *NodeConfig) (module.ReadyDoneAware, error) { 82 logger.Logf("%s initialized", name2) 83 c := newMockComponent(logger, name2) 84 c.startFn = func(ctx irrecoverable.SignalerContext, name string) { 85 // add delay to test components are run serially 86 time.Sleep(5 * time.Millisecond) 87 } 88 return c, nil 89 }) 90 91 name3 := "component 3" 92 nb.Component(name3, func(node *NodeConfig) (module.ReadyDoneAware, error) { 93 logger.Logf("%s initialized", name3) 94 return newMockReadyDone(logger, name3), nil 95 }) 96 97 err := nb.handleComponents() 98 assert.NoError(t, err) 99 100 cm := nb.componentBuilder.Build() 101 102 cm.Start(signalerCtx) 103 <-cm.Ready() 104 cancel() 105 <-cm.Done() 106 107 logs := logger.logs 108 109 assert.Len(t, logs, 10) 110 111 // components are initialized in a specific order, so check that the order is correct 112 startLogs := logs[:len(logs)-3] 113 assert.Equal(t, []string{ 114 "component 1 initialized", 115 "component 1 ready", 116 "component 2 initialized", 117 "component 2 started", 118 "component 2 ready", 119 "component 3 initialized", 120 "component 3 ready", 121 }, startLogs) 122 123 // components are stopped via context cancellation, so the specific order is random 124 doneLogs := logs[len(logs)-3:] 125 assert.ElementsMatch(t, []string{ 126 "component 1 done", 127 "component 2 done", 128 "component 3 done", 129 }, doneLogs) 130 } 131 132 func TestPostShutdown(t *testing.T) { 133 nb := FlowNode("scaffold test") 134 135 logger := testLog{} 136 137 err1 := errors.New("error 1") 138 err3 := errors.New("error 3") 139 errExpected := multierror.Append(&multierror.Error{}, err1, err3) 140 nb. 141 ShutdownFunc(func() error { 142 logger.Log("shutdown 1") 143 return err1 144 }). 145 ShutdownFunc(func() error { 146 logger.Log("shutdown 2") 147 return nil 148 }). 149 ShutdownFunc(func() error { 150 logger.Log("shutdown 3") 151 return err3 152 }) 153 154 err := nb.postShutdown() 155 assert.EqualError(t, err, errExpected.Error()) 156 157 logs := logger.logs 158 assert.Len(t, logs, 3) 159 assert.Equal(t, []string{ 160 "shutdown 1", 161 "shutdown 2", 162 "shutdown 3", 163 }, logs) 164 } 165 166 func TestOverrideComponent(t *testing.T) { 167 ctx, cancel := context.WithCancel(context.Background()) 168 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 169 170 nb := FlowNode("scaffold test") 171 nb.componentBuilder = component.NewComponentManagerBuilder() 172 173 logger := &testLog{} 174 175 name1 := "component 1" 176 nb.Component(name1, func(node *NodeConfig) (module.ReadyDoneAware, error) { 177 logger.Logf("%s initialized", name1) 178 return newMockReadyDone(logger, name1), nil 179 }) 180 181 name2 := "component 2" 182 nb.Component(name2, func(node *NodeConfig) (module.ReadyDoneAware, error) { 183 logger.Logf("%s initialized", name2) 184 return newMockReadyDone(logger, name2), nil 185 }) 186 187 name3 := "component 3" 188 nb.Component(name3, func(node *NodeConfig) (module.ReadyDoneAware, error) { 189 logger.Logf("%s initialized", name3) 190 return newMockReadyDone(logger, name3), nil 191 }) 192 193 // Overrides second component 194 nb.OverrideComponent(name2, func(node *NodeConfig) (module.ReadyDoneAware, error) { 195 logger.Logf("%s overridden", name2) 196 return newMockReadyDone(logger, name2), nil 197 }) 198 199 err := nb.handleComponents() 200 assert.NoError(t, err) 201 202 cm := nb.componentBuilder.Build() 203 204 cm.Start(signalerCtx) 205 206 <-cm.Ready() 207 208 logs := logger.logs 209 210 assert.Len(t, logs, 6) 211 212 // components are initialized in a specific order, so check that the order is correct 213 assert.Equal(t, []string{ 214 "component 1 initialized", 215 "component 1 ready", 216 "component 2 overridden", // overridden version of 2 should be initialized. 217 "component 2 ready", 218 "component 3 initialized", 219 "component 3 ready", 220 }, logs) 221 222 cancel() 223 <-cm.Done() 224 } 225 226 func TestOverrideModules(t *testing.T) { 227 ctx, cancel := context.WithCancel(context.Background()) 228 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 229 230 nb := FlowNode("scaffold test") 231 nb.componentBuilder = component.NewComponentManagerBuilder() 232 233 logger := &testLog{} 234 235 name1 := "module 1" 236 nb.Module(name1, func(nodeConfig *NodeConfig) error { 237 logger.Logf("%s initialized", name1) 238 return nil 239 }) 240 241 name2 := "module 2" 242 nb.Module(name2, func(nodeConfig *NodeConfig) error { 243 logger.Logf("%s initialized", name2) 244 return nil 245 }) 246 247 name3 := "module 3" 248 nb.Module(name3, func(nodeConfig *NodeConfig) error { 249 logger.Logf("%s initialized", name3) 250 return nil 251 }) 252 253 // Overrides second module 254 nb.OverrideModule(name2, func(nodeConfig *NodeConfig) error { 255 logger.Logf("%s overridden", name2) 256 return nil 257 }) 258 259 err := nb.handleModules() 260 assert.NoError(t, err) 261 262 cm := nb.componentBuilder.Build() 263 require.NoError(t, err) 264 265 cm.Start(signalerCtx) 266 267 <-cm.Ready() 268 269 logs := logger.logs 270 271 assert.Len(t, logs, 3) 272 273 // components are initialized in a specific order, so check that the order is correct 274 assert.Equal(t, []string{ 275 "module 1 initialized", 276 "module 2 overridden", // overridden version of 2 should be initialized. 277 "module 3 initialized", 278 }, logs) 279 280 cancel() 281 <-cm.Done() 282 } 283 284 type testComponentDefinition struct { 285 name string 286 factory ReadyDoneFactory 287 errorHandler component.OnError 288 } 289 290 func runRestartableTest(t *testing.T, components []testComponentDefinition, expectedErr error, shutdown <-chan struct{}) { 291 ctx, cancel := context.WithCancel(context.Background()) 292 signalerCtx, errChan := irrecoverable.WithSignaler(ctx) 293 294 go func() { 295 select { 296 case <-ctx.Done(): 297 return 298 case err := <-errChan: 299 if expectedErr == nil { 300 assert.NoError(t, err, "unexpected unhandled irrecoverable error") 301 } else { 302 assert.ErrorIs(t, err, expectedErr) 303 } 304 } 305 }() 306 307 nb := FlowNode("scaffold test") 308 nb.componentBuilder = component.NewComponentManagerBuilder() 309 310 for _, c := range components { 311 if c.errorHandler == nil { 312 nb.Component(c.name, c.factory) 313 } else { 314 nb.RestartableComponent(c.name, c.factory, c.errorHandler) 315 } 316 } 317 318 err := nb.handleComponents() 319 assert.NoError(t, err) 320 321 cm := nb.componentBuilder.Build() 322 323 cm.Start(signalerCtx) 324 325 <-shutdown 326 cancel() 327 328 <-cm.Done() 329 } 330 331 func TestRestartableRestartsSuccessfully(t *testing.T) { 332 333 logger := &testLog{} 334 shutdown := make(chan struct{}) 335 336 name := "component 1" 337 err := fmt.Errorf("%s error", name) 338 339 starts := 0 340 factory := func(node *NodeConfig) (module.ReadyDoneAware, error) { 341 logger.Logf("%s initialized", name) 342 c := newMockComponent(logger, name) 343 c.startFn = func(signalCtx irrecoverable.SignalerContext, name string) { 344 go func() { 345 <-c.Ready() 346 starts++ 347 if starts == 1 { 348 signalCtx.Throw(err) 349 } 350 close(shutdown) 351 }() 352 } 353 return c, nil 354 } 355 356 runRestartableTest(t, []testComponentDefinition{ 357 { 358 name: name, 359 factory: factory, 360 errorHandler: testErrorHandler(logger, err), 361 }, 362 }, nil, shutdown) 363 364 assert.Equal(t, []string{ 365 "component 1 initialized", 366 "component 1 started", 367 "component 1 ready", 368 "component 1 done", 369 "handled error: component 1 error", 370 "component 1 initialized", 371 "component 1 started", 372 "component 1 ready", 373 "component 1 done", 374 }, logger.logs) 375 } 376 377 func TestRestartableStopsSuccessfully(t *testing.T) { 378 logger := &testLog{} 379 shutdown := make(chan struct{}) 380 381 name := "component 1" 382 err := fmt.Errorf("%s error", name) 383 unexpectedErr := fmt.Errorf("%s unexpected error", name) 384 385 starts := 0 386 factory := func(node *NodeConfig) (module.ReadyDoneAware, error) { 387 logger.Logf("%s initialized", name) 388 c := newMockComponent(logger, name) 389 c.startFn = func(signalCtx irrecoverable.SignalerContext, name string) { 390 go func() { 391 <-c.Ready() 392 starts++ 393 if starts < 2 { 394 signalCtx.Throw(err) 395 } 396 if starts == 2 { 397 defer close(shutdown) 398 signalCtx.Throw(unexpectedErr) 399 } 400 }() 401 } 402 return c, nil 403 } 404 405 runRestartableTest(t, []testComponentDefinition{ 406 { 407 name: name, 408 factory: factory, 409 errorHandler: testErrorHandler(logger, err), 410 }, 411 }, unexpectedErr, shutdown) 412 413 assert.Equal(t, []string{ 414 "component 1 initialized", 415 "component 1 started", 416 "component 1 ready", 417 "component 1 done", 418 "handled error: component 1 error", 419 "component 1 initialized", 420 "component 1 started", 421 "component 1 ready", 422 "component 1 done", 423 "handled unexpected error: component 1 unexpected error", 424 }, logger.logs) 425 } 426 427 func TestRestartableWithMultipleComponents(t *testing.T) { 428 logger := &testLog{} 429 shutdown := make(chan struct{}) 430 431 // synchronization is needed since RestartableComponents are non-blocking 432 readyComponents := sync.WaitGroup{} 433 readyComponents.Add(3) 434 435 c1 := func() testComponentDefinition { 436 name := "component 1" 437 factory := func(node *NodeConfig) (module.ReadyDoneAware, error) { 438 logger.Logf("%s initialized", name) 439 c := newMockReadyDone(logger, name) 440 c.readyFn = func(name string) { 441 // delay to demonstrate that components are started serially 442 time.Sleep(5 * time.Millisecond) 443 readyComponents.Done() 444 } 445 return c, nil 446 } 447 448 return testComponentDefinition{ 449 name: name, 450 factory: factory, 451 } 452 } 453 454 c2Initialized := make(chan struct{}) 455 c2 := func() testComponentDefinition { 456 name := "component 2" 457 err := fmt.Errorf("%s error", name) 458 factory := func(node *NodeConfig) (module.ReadyDoneAware, error) { 459 defer close(c2Initialized) 460 logger.Logf("%s initialized", name) 461 c := newMockComponent(logger, name) 462 c.startFn = func(ctx irrecoverable.SignalerContext, name string) { 463 // delay to demonstrate the RestartableComponent startup is non-blocking 464 time.Sleep(5 * time.Millisecond) 465 } 466 c.readyFn = func(name string) { 467 readyComponents.Done() 468 } 469 return c, nil 470 } 471 472 return testComponentDefinition{ 473 name: name, 474 factory: factory, 475 errorHandler: testErrorHandler(logger, err), 476 } 477 } 478 479 c3 := func() testComponentDefinition { 480 name := "component 3" 481 err := fmt.Errorf("%s error", name) 482 starts := 0 483 factory := func(node *NodeConfig) (module.ReadyDoneAware, error) { 484 logger.Logf("%s initialized", name) 485 c := newMockComponent(logger, name) 486 c.startFn = func(signalCtx irrecoverable.SignalerContext, name string) { 487 go func() { 488 <-c.Ready() 489 starts++ 490 if starts == 1 { 491 signalCtx.Throw(err) 492 } 493 <-c2Initialized // can't use ready since it may not be initialized yet 494 readyComponents.Done() 495 }() 496 } 497 return c, nil 498 } 499 500 return testComponentDefinition{ 501 name: name, 502 factory: factory, 503 errorHandler: testErrorHandler(logger, err), 504 } 505 } 506 507 go func() { 508 readyComponents.Wait() 509 close(shutdown) 510 }() 511 512 runRestartableTest(t, []testComponentDefinition{c1(), c2(), c3()}, nil, shutdown) 513 514 logs := logger.logs 515 516 // make sure component 1 is started and ready before any other components start 517 assert.Equal(t, []string{ 518 "component 1 initialized", 519 "component 1 ready", 520 }, logs[:2]) 521 522 // now split logs by component, and verify we got the right messages/order 523 component1 := []string{} 524 component2 := []string{} 525 component3 := []string{} 526 unexpected := []string{} 527 for _, l := range logs { 528 switch { 529 case strings.Contains(l, "component 1"): 530 component1 = append(component1, l) 531 case strings.Contains(l, "component 2"): 532 component2 = append(component2, l) 533 case strings.Contains(l, "component 3"): 534 component3 = append(component3, l) 535 default: 536 unexpected = append(unexpected, l) 537 } 538 } 539 540 // no unexpected logs 541 assert.Len(t, unexpected, 0) 542 543 assert.Equal(t, []string{ 544 "component 1 initialized", 545 "component 1 ready", 546 "component 1 done", 547 }, component1) 548 549 assert.Equal(t, []string{ 550 "component 2 initialized", 551 "component 2 started", 552 "component 2 ready", 553 "component 2 done", 554 }, component2) 555 556 assert.Equal(t, []string{ 557 "component 3 initialized", 558 "component 3 started", 559 "component 3 ready", 560 "component 3 done", 561 "handled error: component 3 error", 562 "component 3 initialized", 563 "component 3 started", 564 "component 3 ready", 565 "component 3 done", 566 }, component3) 567 568 // components are stopped via context cancellation, so the specific order is random 569 doneLogs := logs[len(logs)-3:] 570 assert.ElementsMatch(t, []string{ 571 "component 1 done", 572 "component 2 done", 573 "component 3 done", 574 }, doneLogs) 575 } 576 577 func testErrorHandler(logger *testLog, expected error) component.OnError { 578 return func(err error) component.ErrorHandlingResult { 579 if errors.Is(err, expected) { 580 logger.Logf("handled error: %s", err) 581 return component.ErrorHandlingRestart 582 } 583 logger.Logf("handled unexpected error: %s", err) 584 return component.ErrorHandlingStop 585 } 586 } 587 588 // TestDependableComponentWaitForDependencies tests that dependable components are started after 589 // their dependencies are ready 590 // In this test: 591 // * Components 1 & 2 are DependableComponents 592 // * Component 3 is a normal Component 593 // * 1 depends on 3 594 // * 2 depends on 1 595 // * Start order should be 3, 1, 2 596 // run test 10 times to ensure order is consistent 597 func TestDependableComponentWaitForDependencies(t *testing.T) { 598 for i := 0; i < 10; i++ { 599 testDependableComponentWaitForDependencies(t) 600 } 601 } 602 603 func testDependableComponentWaitForDependencies(t *testing.T) { 604 ctx, cancel := context.WithCancel(context.Background()) 605 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 606 607 nb := FlowNode("scaffold test") 608 nb.componentBuilder = component.NewComponentManagerBuilder() 609 610 logger := &testLog{} 611 612 component1Dependable := module.NewProxiedReadyDoneAware() 613 component3Dependable := module.NewProxiedReadyDoneAware() 614 615 name1 := "component 1" 616 nb.DependableComponent(name1, func(node *NodeConfig) (module.ReadyDoneAware, error) { 617 logger.Logf("%s initialized", name1) 618 c := newMockComponent(logger, name1) 619 component1Dependable.Init(c) 620 return c, nil 621 }, &DependencyList{[]module.ReadyDoneAware{component3Dependable}}) 622 623 name2 := "component 2" 624 nb.DependableComponent(name2, func(node *NodeConfig) (module.ReadyDoneAware, error) { 625 logger.Logf("%s initialized", name2) 626 return newMockComponent(logger, name2), nil 627 }, &DependencyList{[]module.ReadyDoneAware{component1Dependable}}) 628 629 name3 := "component 3" 630 nb.Component(name3, func(node *NodeConfig) (module.ReadyDoneAware, error) { 631 logger.Logf("%s initialized", name3) 632 c := newMockComponent(logger, name3) 633 c.startFn = func(ctx irrecoverable.SignalerContext, name string) { 634 // add delay to test components are run serially 635 time.Sleep(5 * time.Millisecond) 636 } 637 component3Dependable.Init(c) 638 return c, nil 639 }) 640 641 err := nb.handleComponents() 642 require.NoError(t, err) 643 644 cm := nb.componentBuilder.Build() 645 646 cm.Start(signalerCtx) 647 <-cm.Ready() 648 649 cancel() 650 <-cm.Done() 651 652 logs := logger.logs 653 654 assert.Len(t, logs, 12) 655 656 // components are initialized in a specific order, so check that the order is correct 657 startLogs := logs[:len(logs)-3] 658 assert.Equal(t, []string{ 659 "component 3 initialized", 660 "component 3 started", 661 "component 3 ready", 662 "component 1 initialized", 663 "component 1 started", 664 "component 1 ready", 665 "component 2 initialized", 666 "component 2 started", 667 "component 2 ready", 668 }, startLogs) 669 670 // components are stopped via context cancellation, so the specific order is random 671 doneLogs := logs[len(logs)-3:] 672 assert.ElementsMatch(t, []string{ 673 "component 1 done", 674 "component 2 done", 675 "component 3 done", 676 }, doneLogs) 677 } 678 679 func TestCreateUploader(t *testing.T) { 680 t.Parallel() 681 t.Run("create uploader", func(t *testing.T) { 682 t.Parallel() 683 nb := FlowNode("scaffold_uploader") 684 mockHttp := &http.Client{ 685 Transport: &mockRoundTripper{ 686 DoFunc: func(req *http.Request) (*http.Response, error) { 687 switch req.URL.Path { 688 case "/computeMetadata/v1/project/project-id": 689 return &http.Response{ 690 StatusCode: 200, 691 Body: io.NopCloser(bytes.NewBufferString("test-project-id")), 692 }, nil 693 case "/computeMetadata/v1/instance/id": 694 return &http.Response{ 695 StatusCode: 200, 696 Body: io.NopCloser(bytes.NewBufferString("test-instance-id")), 697 }, nil 698 default: 699 return nil, fmt.Errorf("unexpected request: %s", req.URL.Path) 700 } 701 }, 702 }, 703 } 704 705 testClient := gcemd.NewClient(mockHttp) 706 uploader, err := nb.createGCEProfileUploader( 707 testClient, 708 709 option.WithoutAuthentication(), 710 option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())), 711 ) 712 require.NoError(t, err) 713 require.NotNil(t, uploader) 714 715 uploaderImpl, ok := uploader.(*profiler.UploaderImpl) 716 require.True(t, ok) 717 718 assert.Equal(t, "test-project-id", uploaderImpl.Deployment.ProjectId) 719 assert.Equal(t, "unknown-scaffold_uploader", uploaderImpl.Deployment.Target) 720 assert.Equal(t, "test-instance-id", uploaderImpl.Deployment.Labels["instance"]) 721 assert.Equal(t, "undefined-undefined", uploaderImpl.Deployment.Labels["version"]) 722 }) 723 } 724 725 type mockRoundTripper struct { 726 DoFunc func(req *http.Request) (*http.Response, error) 727 } 728 729 func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 730 return m.DoFunc(req) 731 }