go.temporal.io/server@v1.23.0/common/archiver/s3store/history_archiver_test.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package s3store 26 27 import ( 28 "bytes" 29 "context" 30 "errors" 31 "fmt" 32 "io" 33 "sort" 34 "strconv" 35 "strings" 36 "testing" 37 "time" 38 39 "github.com/aws/aws-sdk-go/aws" 40 "github.com/aws/aws-sdk-go/aws/awserr" 41 "github.com/aws/aws-sdk-go/aws/request" 42 "github.com/aws/aws-sdk-go/service/s3" 43 "github.com/golang/mock/gomock" 44 "github.com/stretchr/testify/require" 45 "github.com/stretchr/testify/suite" 46 enumspb "go.temporal.io/api/enums/v1" 47 historypb "go.temporal.io/api/history/v1" 48 "go.temporal.io/api/serviceerror" 49 "google.golang.org/protobuf/types/known/timestamppb" 50 51 archiverspb "go.temporal.io/server/api/archiver/v1" 52 "go.temporal.io/server/common" 53 "go.temporal.io/server/common/archiver" 54 "go.temporal.io/server/common/archiver/s3store/mocks" 55 "go.temporal.io/server/common/codec" 56 "go.temporal.io/server/common/convert" 57 "go.temporal.io/server/common/log" 58 "go.temporal.io/server/common/metrics" 59 ) 60 61 const ( 62 testNamespaceID = "test-namespace-id" 63 testNamespace = "test-namespace" 64 testWorkflowID = "test-workflow-id" 65 testRunID = "test-run-id" 66 testNextEventID = 1800 67 testCloseFailoverVersion = int64(100) 68 testPageSize = 100 69 testBucket = "test-bucket" 70 testBucketURI = "s3://test-bucket" 71 ) 72 73 var testBranchToken = []byte{1, 2, 3} 74 75 type historyArchiverSuite struct { 76 *require.Assertions 77 suite.Suite 78 s3cli *mocks.MockS3API 79 container *archiver.HistoryBootstrapContainer 80 testArchivalURI archiver.URI 81 historyBatchesV1 []*archiverspb.HistoryBlob 82 historyBatchesV100 []*archiverspb.HistoryBlob 83 controller *gomock.Controller 84 } 85 86 func TestHistoryArchiverSuite(t *testing.T) { 87 suite.Run(t, new(historyArchiverSuite)) 88 } 89 90 func (s *historyArchiverSuite) SetupSuite() { 91 var err error 92 s.testArchivalURI, err = archiver.NewURI(testBucketURI) 93 s.Require().NoError(err) 94 } 95 96 func (s *historyArchiverSuite) TearDownSuite() { 97 } 98 99 func (s *historyArchiverSuite) SetupTest() { 100 s.Assertions = require.New(s.T()) 101 s.container = &archiver.HistoryBootstrapContainer{ 102 Logger: log.NewNoopLogger(), 103 MetricsHandler: metrics.NoopMetricsHandler, 104 } 105 106 s.controller = gomock.NewController(s.T()) 107 s.s3cli = mocks.NewMockS3API(s.controller) 108 setupFsEmulation(s.s3cli) 109 s.setupHistoryDirectory() 110 } 111 112 func setupFsEmulation(s3cli *mocks.MockS3API) { 113 fs := make(map[string][]byte) 114 115 putObjectFn := func(_ aws.Context, input *s3.PutObjectInput, _ ...request.Option) (*s3.PutObjectOutput, error) { 116 buf := new(bytes.Buffer) 117 if _, err := buf.ReadFrom(input.Body); err != nil { 118 return nil, err 119 } 120 fs[*input.Bucket+*input.Key] = buf.Bytes() 121 return &s3.PutObjectOutput{}, nil 122 } 123 124 s3cli.EXPECT().ListObjectsV2WithContext(gomock.Any(), gomock.Any()).DoAndReturn( 125 func(_ context.Context, input *s3.ListObjectsV2Input, opts ...request.Option) (*s3.ListObjectsV2Output, error) { 126 objects := make([]*s3.Object, 0) 127 commonPrefixMap := map[string]bool{} 128 for k := range fs { 129 if strings.HasPrefix(k, *input.Bucket+*input.Prefix) { 130 key := k[len(*input.Bucket):] 131 keyWithoutPrefix := key[len(*input.Prefix):] 132 index := strings.Index(keyWithoutPrefix, "/") 133 if index == -1 || input.Delimiter == nil { 134 objects = append(objects, &s3.Object{ 135 Key: aws.String(key), 136 }) 137 } else { 138 commonPrefixMap[key[:len(*input.Prefix)+index]] = true 139 } 140 } 141 } 142 commonPrefixes := make([]*s3.CommonPrefix, 0) 143 for k := range commonPrefixMap { 144 commonPrefixes = append(commonPrefixes, &s3.CommonPrefix{ 145 Prefix: aws.String(k), 146 }) 147 } 148 149 sort.SliceStable(objects, func(i, j int) bool { 150 return *objects[i].Key < *objects[j].Key 151 }) 152 maxKeys := 1000 153 if input.MaxKeys != nil { 154 maxKeys = int(*input.MaxKeys) 155 } 156 start := 0 157 if input.ContinuationToken != nil { 158 start, _ = strconv.Atoi(*input.ContinuationToken) 159 } 160 161 if input.StartAfter != nil { 162 for k, v := range objects { 163 if *input.StartAfter == *v.Key { 164 start = k + 1 165 } 166 } 167 } 168 169 isTruncated := false 170 var nextContinuationToken *string 171 if len(objects) > start+maxKeys { 172 isTruncated = true 173 nextContinuationToken = convert.StringPtr(fmt.Sprintf("%d", start+maxKeys)) 174 objects = objects[start : start+maxKeys] 175 } else { 176 objects = objects[start:] 177 } 178 179 if input.StartAfter != nil { 180 for k, v := range commonPrefixes { 181 if *input.StartAfter == *v.Prefix { 182 start = k + 1 183 } 184 } 185 } 186 187 if len(commonPrefixes) > start+maxKeys { 188 isTruncated = true 189 nextContinuationToken = convert.StringPtr(fmt.Sprintf("%d", start+maxKeys)) 190 commonPrefixes = commonPrefixes[start : start+maxKeys] 191 } else if len(commonPrefixes) > 0 { 192 commonPrefixes = commonPrefixes[start:] 193 } 194 195 return &s3.ListObjectsV2Output{ 196 CommonPrefixes: commonPrefixes, 197 Contents: objects, 198 IsTruncated: &isTruncated, 199 NextContinuationToken: nextContinuationToken, 200 }, nil 201 }).AnyTimes() 202 s3cli.EXPECT().PutObjectWithContext(gomock.Any(), gomock.Any()).DoAndReturn(putObjectFn).AnyTimes() 203 204 s3cli.EXPECT().HeadObjectWithContext(gomock.Any(), gomock.Any()).DoAndReturn( 205 func(ctx aws.Context, input *s3.HeadObjectInput, options ...request.Option) (*s3.HeadObjectOutput, error) { 206 _, ok := fs[*input.Bucket+*input.Key] 207 if !ok { 208 return nil, awserr.New("NotFound", "", nil) 209 } 210 211 return &s3.HeadObjectOutput{}, nil 212 }).AnyTimes() 213 214 s3cli.EXPECT().GetObjectWithContext(gomock.Any(), gomock.Any()).DoAndReturn( 215 func(ctx aws.Context, input *s3.GetObjectInput, options ...request.Option) (*s3.GetObjectOutput, error) { 216 _, ok := fs[*input.Bucket+*input.Key] 217 if !ok { 218 return nil, awserr.New(s3.ErrCodeNoSuchKey, "", nil) 219 } 220 221 return &s3.GetObjectOutput{ 222 Body: io.NopCloser(bytes.NewReader(fs[*input.Bucket+*input.Key])), 223 }, nil 224 }).AnyTimes() 225 } 226 227 func (s *historyArchiverSuite) TestValidateURI() { 228 testCases := []struct { 229 URI string 230 expectedErr error 231 }{ 232 { 233 URI: "wrongscheme:///a/b/c", 234 expectedErr: archiver.ErrURISchemeMismatch, 235 }, 236 { 237 URI: "s3://", 238 expectedErr: errNoBucketSpecified, 239 }, 240 { 241 URI: "s3://bucket/a/b/c", 242 expectedErr: errBucketNotExists, 243 }, 244 { 245 URI: testBucketURI, 246 expectedErr: nil, 247 }, 248 } 249 250 s.s3cli.EXPECT().HeadBucketWithContext(gomock.Any(), gomock.Any()).DoAndReturn( 251 func(ctx aws.Context, input *s3.HeadBucketInput, options ...request.Option) (*s3.HeadBucketOutput, error) { 252 if *input.Bucket != s.testArchivalURI.Hostname() { 253 return nil, awserr.New("NotFound", "", nil) 254 } 255 256 return &s3.HeadBucketOutput{}, nil 257 }).AnyTimes() 258 259 historyArchiver := s.newTestHistoryArchiver(nil) 260 for _, tc := range testCases { 261 URI, err := archiver.NewURI(tc.URI) 262 s.NoError(err) 263 s.Equal(tc.expectedErr, historyArchiver.ValidateURI(URI)) 264 } 265 } 266 267 func (s *historyArchiverSuite) TestArchive_Fail_InvalidURI() { 268 historyArchiver := s.newTestHistoryArchiver(nil) 269 request := &archiver.ArchiveHistoryRequest{ 270 NamespaceID: testNamespaceID, 271 Namespace: testNamespace, 272 WorkflowID: testWorkflowID, 273 RunID: testRunID, 274 BranchToken: testBranchToken, 275 NextEventID: testNextEventID, 276 CloseFailoverVersion: testCloseFailoverVersion, 277 } 278 URI, err := archiver.NewURI("wrongscheme://") 279 s.NoError(err) 280 err = historyArchiver.Archive(context.Background(), URI, request) 281 s.Error(err) 282 } 283 284 func (s *historyArchiverSuite) TestArchive_Fail_InvalidRequest() { 285 historyArchiver := s.newTestHistoryArchiver(nil) 286 request := &archiver.ArchiveHistoryRequest{ 287 NamespaceID: testNamespaceID, 288 Namespace: testNamespace, 289 WorkflowID: "", // an invalid request 290 RunID: testRunID, 291 BranchToken: testBranchToken, 292 NextEventID: testNextEventID, 293 CloseFailoverVersion: testCloseFailoverVersion, 294 } 295 err := historyArchiver.Archive(context.Background(), s.testArchivalURI, request) 296 s.Error(err) 297 } 298 299 func (s *historyArchiverSuite) TestArchive_Fail_ErrorOnReadHistory() { 300 historyIterator := archiver.NewMockHistoryIterator(s.controller) 301 gomock.InOrder( 302 historyIterator.EXPECT().HasNext().Return(true), 303 historyIterator.EXPECT().Next(gomock.Any()).Return(nil, errors.New("some random error")), 304 ) 305 306 historyArchiver := s.newTestHistoryArchiver(historyIterator) 307 request := &archiver.ArchiveHistoryRequest{ 308 NamespaceID: testNamespaceID, 309 Namespace: testNamespace, 310 WorkflowID: testWorkflowID, 311 RunID: testRunID, 312 BranchToken: testBranchToken, 313 NextEventID: testNextEventID, 314 CloseFailoverVersion: testCloseFailoverVersion, 315 } 316 err := historyArchiver.Archive(context.Background(), s.testArchivalURI, request) 317 s.Error(err) 318 } 319 320 func (s *historyArchiverSuite) TestArchive_Fail_TimeoutWhenReadingHistory() { 321 historyIterator := archiver.NewMockHistoryIterator(s.controller) 322 gomock.InOrder( 323 historyIterator.EXPECT().HasNext().Return(true), 324 historyIterator.EXPECT().Next(gomock.Any()).Return(nil, serviceerror.NewResourceExhausted(enumspb.RESOURCE_EXHAUSTED_CAUSE_RPS_LIMIT, "")), 325 ) 326 327 historyArchiver := s.newTestHistoryArchiver(historyIterator) 328 request := &archiver.ArchiveHistoryRequest{ 329 NamespaceID: testNamespaceID, 330 Namespace: testNamespace, 331 WorkflowID: testWorkflowID, 332 RunID: testRunID, 333 BranchToken: testBranchToken, 334 NextEventID: testNextEventID, 335 CloseFailoverVersion: testCloseFailoverVersion, 336 } 337 err := historyArchiver.Archive(getCanceledContext(), s.testArchivalURI, request) 338 s.Error(err) 339 } 340 341 func (s *historyArchiverSuite) TestArchive_Fail_HistoryMutated() { 342 historyIterator := archiver.NewMockHistoryIterator(s.controller) 343 historyBatches := []*historypb.History{ 344 { 345 Events: []*historypb.HistoryEvent{ 346 { 347 EventId: common.FirstEventID + 1, 348 EventTime: timestamppb.New(time.Now().UTC()), 349 Version: testCloseFailoverVersion + 1, 350 }, 351 }, 352 }, 353 } 354 historyBlob := &archiverspb.HistoryBlob{ 355 Header: &archiverspb.HistoryBlobHeader{ 356 IsLast: true, 357 }, 358 Body: historyBatches, 359 } 360 gomock.InOrder( 361 historyIterator.EXPECT().HasNext().Return(true), 362 historyIterator.EXPECT().Next(gomock.Any()).Return(historyBlob, nil), 363 ) 364 365 historyArchiver := s.newTestHistoryArchiver(historyIterator) 366 request := &archiver.ArchiveHistoryRequest{ 367 NamespaceID: testNamespaceID, 368 Namespace: testNamespace, 369 WorkflowID: testWorkflowID, 370 RunID: testRunID, 371 BranchToken: testBranchToken, 372 NextEventID: testNextEventID, 373 CloseFailoverVersion: testCloseFailoverVersion, 374 } 375 err := historyArchiver.Archive(context.Background(), s.testArchivalURI, request) 376 s.Error(err) 377 } 378 379 func (s *historyArchiverSuite) TestArchive_Fail_NonRetryableErrorOption() { 380 historyIterator := archiver.NewMockHistoryIterator(s.controller) 381 gomock.InOrder( 382 historyIterator.EXPECT().HasNext().Return(true), 383 historyIterator.EXPECT().Next(gomock.Any()).Return(nil, errors.New("some random error")), 384 ) 385 386 historyArchiver := s.newTestHistoryArchiver(historyIterator) 387 request := &archiver.ArchiveHistoryRequest{ 388 NamespaceID: testNamespaceID, 389 Namespace: testNamespace, 390 WorkflowID: testWorkflowID, 391 RunID: testRunID, 392 BranchToken: testBranchToken, 393 NextEventID: testNextEventID, 394 CloseFailoverVersion: testCloseFailoverVersion, 395 } 396 nonRetryableErr := errors.New("some non-retryable error") 397 err := historyArchiver.Archive(context.Background(), s.testArchivalURI, request, archiver.GetNonRetryableErrorOption(nonRetryableErr)) 398 s.Equal(nonRetryableErr, err) 399 } 400 401 func (s *historyArchiverSuite) TestArchive_Skip() { 402 historyIterator := archiver.NewMockHistoryIterator(s.controller) 403 historyBlob := &archiverspb.HistoryBlob{ 404 Header: &archiverspb.HistoryBlobHeader{ 405 IsLast: false, 406 }, 407 Body: []*historypb.History{ 408 { 409 Events: []*historypb.HistoryEvent{ 410 { 411 EventId: common.FirstEventID, 412 EventTime: timestamppb.New(time.Now().UTC()), 413 Version: testCloseFailoverVersion, 414 }, 415 }, 416 }, 417 }, 418 } 419 gomock.InOrder( 420 historyIterator.EXPECT().HasNext().Return(true), 421 historyIterator.EXPECT().Next(gomock.Any()).Return(historyBlob, nil), 422 historyIterator.EXPECT().HasNext().Return(true), 423 historyIterator.EXPECT().Next(gomock.Any()).Return(nil, serviceerror.NewNotFound("workflow not found")), 424 ) 425 426 historyArchiver := s.newTestHistoryArchiver(historyIterator) 427 request := &archiver.ArchiveHistoryRequest{ 428 NamespaceID: testNamespaceID, 429 Namespace: testNamespace, 430 WorkflowID: testWorkflowID, 431 RunID: testRunID, 432 BranchToken: testBranchToken, 433 NextEventID: testNextEventID, 434 CloseFailoverVersion: testCloseFailoverVersion, 435 } 436 URI, err := archiver.NewURI(testBucketURI + "/TestArchive_Skip") 437 s.NoError(err) 438 err = historyArchiver.Archive(context.Background(), URI, request) 439 s.NoError(err) 440 441 expectedkey := constructHistoryKey("", testNamespaceID, testWorkflowID, testRunID, testCloseFailoverVersion, 0) 442 s.assertKeyExists(expectedkey) 443 } 444 445 func (s *historyArchiverSuite) TestArchive_Success() { 446 historyIterator := archiver.NewMockHistoryIterator(s.controller) 447 historyBatches := []*historypb.History{ 448 { 449 Events: []*historypb.HistoryEvent{ 450 { 451 EventId: common.FirstEventID + 1, 452 EventTime: timestamppb.New(time.Now().UTC()), 453 Version: testCloseFailoverVersion, 454 }, 455 { 456 EventId: common.FirstEventID + 2, 457 EventTime: timestamppb.New(time.Now().UTC()), 458 Version: testCloseFailoverVersion, 459 }, 460 }, 461 }, 462 { 463 Events: []*historypb.HistoryEvent{ 464 { 465 EventId: testNextEventID - 1, 466 EventTime: timestamppb.New(time.Now().UTC()), 467 Version: testCloseFailoverVersion, 468 }, 469 }, 470 }, 471 } 472 historyBlob := &archiverspb.HistoryBlob{ 473 Header: &archiverspb.HistoryBlobHeader{ 474 IsLast: true, 475 }, 476 Body: historyBatches, 477 } 478 gomock.InOrder( 479 historyIterator.EXPECT().HasNext().Return(true), 480 historyIterator.EXPECT().Next(gomock.Any()).Return(historyBlob, nil), 481 historyIterator.EXPECT().HasNext().Return(false), 482 ) 483 484 historyArchiver := s.newTestHistoryArchiver(historyIterator) 485 request := &archiver.ArchiveHistoryRequest{ 486 NamespaceID: testNamespaceID, 487 Namespace: testNamespace, 488 WorkflowID: testWorkflowID, 489 RunID: testRunID, 490 BranchToken: testBranchToken, 491 NextEventID: testNextEventID, 492 CloseFailoverVersion: testCloseFailoverVersion, 493 } 494 URI, err := archiver.NewURI(testBucketURI + "/TestArchive_Success") 495 s.NoError(err) 496 err = historyArchiver.Archive(context.Background(), URI, request) 497 s.NoError(err) 498 499 expectedkey := constructHistoryKey("", testNamespaceID, testWorkflowID, testRunID, testCloseFailoverVersion, 0) 500 s.assertKeyExists(expectedkey) 501 } 502 503 func (s *historyArchiverSuite) TestGet_Fail_InvalidURI() { 504 historyArchiver := s.newTestHistoryArchiver(nil) 505 request := &archiver.GetHistoryRequest{ 506 NamespaceID: testNamespaceID, 507 WorkflowID: testWorkflowID, 508 RunID: testRunID, 509 PageSize: 100, 510 } 511 URI, err := archiver.NewURI("wrongscheme://") 512 s.NoError(err) 513 response, err := historyArchiver.Get(context.Background(), URI, request) 514 s.Nil(response) 515 s.Error(err) 516 } 517 518 func (s *historyArchiverSuite) TestGet_Fail_InvalidRequest() { 519 historyArchiver := s.newTestHistoryArchiver(nil) 520 request := &archiver.GetHistoryRequest{ 521 NamespaceID: testNamespaceID, 522 WorkflowID: testWorkflowID, 523 RunID: testRunID, 524 PageSize: 0, // pageSize should be greater than 0 525 } 526 response, err := historyArchiver.Get(context.Background(), s.testArchivalURI, request) 527 s.Nil(response) 528 s.Error(err) 529 s.IsType(&serviceerror.InvalidArgument{}, err) 530 } 531 532 func (s *historyArchiverSuite) TestGet_Fail_InvalidToken() { 533 historyArchiver := s.newTestHistoryArchiver(nil) 534 request := &archiver.GetHistoryRequest{ 535 NamespaceID: testNamespaceID, 536 WorkflowID: testWorkflowID, 537 RunID: testRunID, 538 PageSize: testPageSize, 539 NextPageToken: []byte{'r', 'a', 'n', 'd', 'o', 'm'}, 540 } 541 URI, err := archiver.NewURI(testBucketURI) 542 s.NoError(err) 543 response, err := historyArchiver.Get(context.Background(), URI, request) 544 s.Nil(response) 545 s.Error(err) 546 s.IsType(&serviceerror.InvalidArgument{}, err) 547 } 548 549 func (s *historyArchiverSuite) TestGet_Fail_KeyNotExist() { 550 historyArchiver := s.newTestHistoryArchiver(nil) 551 testCloseFailoverVersion := testCloseFailoverVersion 552 request := &archiver.GetHistoryRequest{ 553 NamespaceID: testNamespaceID, 554 WorkflowID: testWorkflowID, 555 RunID: testRunID, 556 PageSize: testPageSize, 557 CloseFailoverVersion: &testCloseFailoverVersion, 558 } 559 URI, err := archiver.NewURI("s3://test-bucket/non-existent") 560 s.NoError(err) 561 response, err := historyArchiver.Get(context.Background(), URI, request) 562 s.Nil(response) 563 s.Error(err) 564 s.IsType(&serviceerror.NotFound{}, err) 565 } 566 567 func (s *historyArchiverSuite) TestGet_Success_PickHighestVersion() { 568 historyArchiver := s.newTestHistoryArchiver(nil) 569 request := &archiver.GetHistoryRequest{ 570 NamespaceID: testNamespaceID, 571 WorkflowID: testWorkflowID, 572 RunID: testRunID, 573 PageSize: testPageSize, 574 } 575 URI, err := archiver.NewURI(testBucketURI) 576 s.NoError(err) 577 response, err := historyArchiver.Get(context.Background(), URI, request) 578 s.NoError(err) 579 s.Nil(response.NextPageToken) 580 s.Equal(append(s.historyBatchesV100[0].Body, s.historyBatchesV100[1].Body...), response.HistoryBatches) 581 } 582 583 func (s *historyArchiverSuite) TestGet_Success_UseProvidedVersion() { 584 historyArchiver := s.newTestHistoryArchiver(nil) 585 testCloseFailoverVersion := int64(1) 586 request := &archiver.GetHistoryRequest{ 587 NamespaceID: testNamespaceID, 588 WorkflowID: testWorkflowID, 589 RunID: testRunID, 590 PageSize: testPageSize, 591 CloseFailoverVersion: &testCloseFailoverVersion, 592 } 593 URI, err := archiver.NewURI(testBucketURI) 594 s.NoError(err) 595 response, err := historyArchiver.Get(context.Background(), URI, request) 596 s.NoError(err) 597 s.Nil(response.NextPageToken) 598 s.Equal(s.historyBatchesV1[0].Body, response.HistoryBatches) 599 } 600 601 func (s *historyArchiverSuite) TestGet_Success_SmallPageSize() { 602 historyArchiver := s.newTestHistoryArchiver(nil) 603 testCloseFailoverVersion := testCloseFailoverVersion 604 request := &archiver.GetHistoryRequest{ 605 NamespaceID: testNamespaceID, 606 WorkflowID: testWorkflowID, 607 RunID: testRunID, 608 PageSize: 1, 609 CloseFailoverVersion: &testCloseFailoverVersion, 610 } 611 var combinedHistory []*historypb.History 612 613 URI, err := archiver.NewURI(testBucketURI) 614 s.NoError(err) 615 response, err := historyArchiver.Get(context.Background(), URI, request) 616 s.NoError(err) 617 s.NotNil(response) 618 s.NotNil(response.NextPageToken) 619 s.NotNil(response.HistoryBatches) 620 s.Len(response.HistoryBatches, 1) 621 combinedHistory = append(combinedHistory, response.HistoryBatches...) 622 623 request.NextPageToken = response.NextPageToken 624 response, err = historyArchiver.Get(context.Background(), URI, request) 625 s.NoError(err) 626 s.NotNil(response) 627 s.Nil(response.NextPageToken) 628 s.NotNil(response.HistoryBatches) 629 s.Len(response.HistoryBatches, 1) 630 combinedHistory = append(combinedHistory, response.HistoryBatches...) 631 632 s.Equal(append(s.historyBatchesV100[0].Body, s.historyBatchesV100[1].Body...), combinedHistory) 633 } 634 635 func (s *historyArchiverSuite) TestGet_EmptyHistory_ReturnsNotFoundError() { 636 historyIterator := archiver.NewMockHistoryIterator(s.controller) 637 historyArchiver := s.newTestHistoryArchiver(historyIterator) 638 URI, err := archiver.NewURI(testBucketURI + "/TestArchiveAndGet") 639 s.NoError(err) 640 getRequest := &archiver.GetHistoryRequest{ 641 NamespaceID: testNamespaceID, 642 WorkflowID: testWorkflowID, 643 RunID: testRunID, 644 PageSize: testPageSize, 645 } 646 response, err := historyArchiver.Get(context.Background(), URI, getRequest) 647 s.Error(err) 648 s.Nil(response) 649 s.IsType(&serviceerror.NotFound{}, err) 650 } 651 652 func (s *historyArchiverSuite) TestArchiveAndGet() { 653 historyIterator := archiver.NewMockHistoryIterator(s.controller) 654 gomock.InOrder( 655 historyIterator.EXPECT().HasNext().Return(true), 656 historyIterator.EXPECT().Next(gomock.Any()).Return(s.historyBatchesV100[0], nil), 657 historyIterator.EXPECT().HasNext().Return(true), 658 historyIterator.EXPECT().Next(gomock.Any()).Return(s.historyBatchesV100[1], nil), 659 historyIterator.EXPECT().HasNext().Return(false), 660 ) 661 662 historyArchiver := s.newTestHistoryArchiver(historyIterator) 663 archiveRequest := &archiver.ArchiveHistoryRequest{ 664 NamespaceID: testNamespaceID, 665 Namespace: testNamespace, 666 WorkflowID: testWorkflowID, 667 RunID: testRunID, 668 BranchToken: testBranchToken, 669 NextEventID: testNextEventID, 670 CloseFailoverVersion: testCloseFailoverVersion, 671 } 672 URI, err := archiver.NewURI(testBucketURI + "/TestArchiveAndGet") 673 s.NoError(err) 674 err = historyArchiver.Archive(context.Background(), URI, archiveRequest) 675 s.NoError(err) 676 677 getRequest := &archiver.GetHistoryRequest{ 678 NamespaceID: testNamespaceID, 679 WorkflowID: testWorkflowID, 680 RunID: testRunID, 681 PageSize: testPageSize, 682 } 683 response, err := historyArchiver.Get(context.Background(), URI, getRequest) 684 s.NoError(err) 685 s.NotNil(response) 686 s.Nil(response.NextPageToken) 687 s.Equal(append(s.historyBatchesV100[0].Body, s.historyBatchesV100[1].Body...), response.HistoryBatches) 688 } 689 690 func (s *historyArchiverSuite) newTestHistoryArchiver(historyIterator archiver.HistoryIterator) *historyArchiver { 691 // config := &config.S3Archiver{} 692 // archiver, err := newHistoryArchiver(s.container, config, historyIterator) 693 archiver := &historyArchiver{ 694 container: s.container, 695 s3cli: s.s3cli, 696 historyIterator: historyIterator, 697 } 698 return archiver 699 } 700 701 func (s *historyArchiverSuite) setupHistoryDirectory() { 702 now := time.Date(2020, 8, 22, 1, 2, 3, 4, time.UTC) 703 704 s.historyBatchesV1 = []*archiverspb.HistoryBlob{ 705 { 706 Header: &archiverspb.HistoryBlobHeader{ 707 IsLast: true, 708 }, 709 Body: []*historypb.History{ 710 { 711 Events: []*historypb.HistoryEvent{ 712 { 713 EventId: testNextEventID - 1, 714 EventTime: timestamppb.New(now), 715 Version: 1, 716 }, 717 }, 718 }, 719 }, 720 }, 721 } 722 723 s.historyBatchesV100 = []*archiverspb.HistoryBlob{ 724 { 725 Header: &archiverspb.HistoryBlobHeader{ 726 IsLast: false, 727 }, 728 Body: []*historypb.History{ 729 { 730 Events: []*historypb.HistoryEvent{ 731 { 732 EventId: common.FirstEventID + 1, 733 EventTime: timestamppb.New(now), 734 Version: testCloseFailoverVersion, 735 }, 736 { 737 EventId: common.FirstEventID + 1, 738 EventTime: timestamppb.New(now), 739 Version: testCloseFailoverVersion, 740 }, 741 }, 742 }, 743 }, 744 }, 745 { 746 Header: &archiverspb.HistoryBlobHeader{ 747 IsLast: true, 748 }, 749 Body: []*historypb.History{ 750 { 751 Events: []*historypb.HistoryEvent{ 752 { 753 EventId: testNextEventID - 1, 754 EventTime: timestamppb.New(now), 755 Version: testCloseFailoverVersion, 756 }, 757 }, 758 }, 759 }, 760 }, 761 } 762 763 s.writeHistoryBatchesForGetTest(s.historyBatchesV1, int64(1)) 764 s.writeHistoryBatchesForGetTest(s.historyBatchesV100, testCloseFailoverVersion) 765 } 766 767 func (s *historyArchiverSuite) writeHistoryBatchesForGetTest(historyBatches []*archiverspb.HistoryBlob, version int64) { 768 for i, batch := range historyBatches { 769 encoder := codec.NewJSONPBEncoder() 770 data, err := encoder.Encode(batch) 771 s.Require().NoError(err) 772 key := constructHistoryKey("", testNamespaceID, testWorkflowID, testRunID, version, i) 773 _, err = s.s3cli.PutObjectWithContext(context.Background(), &s3.PutObjectInput{ 774 Bucket: aws.String(testBucket), 775 Key: aws.String(key), 776 Body: bytes.NewReader(data), 777 }) 778 s.Require().NoError(err) 779 } 780 } 781 782 func (s *historyArchiverSuite) assertKeyExists(key string) { 783 _, err := s.s3cli.GetObjectWithContext(context.Background(), &s3.GetObjectInput{ 784 Bucket: aws.String(testBucket), 785 Key: aws.String(key), 786 }) 787 s.NoError(err) 788 } 789 790 func getCanceledContext() context.Context { 791 ctx, cancel := context.WithCancel(context.Background()) 792 cancel() 793 return ctx 794 }