go.temporal.io/server@v1.23.0/common/persistence/tests/cassandra_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 tests 26 27 import ( 28 "context" 29 "strconv" 30 "testing" 31 "time" 32 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 "github.com/stretchr/testify/suite" 36 "go.temporal.io/api/enums/v1" 37 "go.temporal.io/api/serviceerror" 38 39 persistencespb "go.temporal.io/server/api/persistence/v1" 40 "go.temporal.io/server/common/log" 41 "go.temporal.io/server/common/log/tag" 42 "go.temporal.io/server/common/persistence" 43 "go.temporal.io/server/common/persistence/cassandra" 44 "go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql" 45 persistencetests "go.temporal.io/server/common/persistence/persistence-tests" 46 "go.temporal.io/server/common/persistence/persistencetest" 47 "go.temporal.io/server/common/persistence/serialization" 48 _ "go.temporal.io/server/common/persistence/sql/sqlplugin/mysql" 49 ) 50 51 type ( 52 // failingSession is a [gocql.Session] which fails any query whose template matches a string in failingQueries. 53 failingSession struct { 54 gocql.Session 55 failingQueries []string 56 } 57 // failingQuery is a [gocql.Query] which fails when executed. 58 failingQuery struct { 59 gocql.Query 60 } 61 // recordingSession is a [gocql.Session] which records all queries executed on it. 62 recordingSession struct { 63 gocql.Session 64 statements []statement 65 } 66 statement struct { 67 query string 68 args []interface{} 69 } 70 // failingIter is a [gocql.Iter] which fails when iterated. 71 failingIter struct{} 72 // blockingSession is a [gocql.Session] designed for testing concurrent inserts. 73 // See cassandra.ErrQueueMessageIDConflict for more. 74 blockingSession struct { 75 gocql.Session 76 queryToBlockOn string 77 queryStarted chan struct{} 78 queryCanContinue chan struct{} 79 } 80 // enqueueMessageResult contains the result of a call to persistence.QueueV2.EnqueueMessage. 81 enqueueMessageResult struct { 82 // id of the inserted message 83 id int 84 // err if the call failed 85 err error 86 } 87 testQueueParams struct { 88 logger log.Logger 89 } 90 testLogger struct { 91 log.Logger 92 warningMsgs []string 93 } 94 rangeDeleteTestQueue struct { 95 persistence.QueueV2 96 session *blockingSession 97 deleteErrs chan error 98 maxIDToDelete int 99 } 100 ) 101 102 func (f failingIter) Scan(...interface{}) bool { 103 return false 104 } 105 106 func (f failingIter) MapScan(map[string]interface{}) bool { 107 return false 108 } 109 110 func (f failingIter) PageState() []byte { 111 return nil 112 } 113 114 func (f failingIter) Close() error { 115 return assert.AnError 116 } 117 118 func (q failingQuery) Iter() gocql.Iter { 119 return failingIter{} 120 } 121 122 func (q failingQuery) Scan(...interface{}) error { 123 return assert.AnError 124 } 125 126 func (q failingQuery) Exec() error { 127 return assert.AnError 128 } 129 130 func (l *testLogger) Warn(msg string, _ ...tag.Tag) { 131 l.warningMsgs = append(l.warningMsgs, msg) 132 } 133 134 func (s *recordingSession) Query(query string, args ...interface{}) gocql.Query { 135 s.statements = append(s.statements, statement{ 136 query: query, 137 args: args, 138 }) 139 140 return s.Session.Query(query, args...) 141 } 142 143 func TestCassandraShardStoreSuite(t *testing.T) { 144 testData, tearDown := setUpCassandraTest(t) 145 defer tearDown() 146 147 shardStore, err := testData.Factory.NewShardStore() 148 if err != nil { 149 t.Fatalf("unable to create Cassandra DB: %v", err) 150 } 151 152 s := NewShardSuite( 153 t, 154 shardStore, 155 serialization.NewSerializer(), 156 testData.Logger, 157 ) 158 suite.Run(t, s) 159 } 160 161 func TestCassandraExecutionMutableStateStoreSuite(t *testing.T) { 162 testData, tearDown := setUpCassandraTest(t) 163 defer tearDown() 164 165 shardStore, err := testData.Factory.NewShardStore() 166 if err != nil { 167 t.Fatalf("unable to create Cassandra DB: %v", err) 168 } 169 executionStore, err := testData.Factory.NewExecutionStore() 170 if err != nil { 171 t.Fatalf("unable to create Cassandra DB: %v", err) 172 } 173 174 s := NewExecutionMutableStateSuite( 175 t, 176 shardStore, 177 executionStore, 178 serialization.NewSerializer(), 179 testData.Logger, 180 ) 181 suite.Run(t, s) 182 } 183 184 func TestCassandraExecutionMutableStateTaskStoreSuite(t *testing.T) { 185 testData, tearDown := setUpCassandraTest(t) 186 defer tearDown() 187 188 shardStore, err := testData.Factory.NewShardStore() 189 if err != nil { 190 t.Fatalf("unable to create Cassandra DB: %v", err) 191 } 192 executionStore, err := testData.Factory.NewExecutionStore() 193 if err != nil { 194 t.Fatalf("unable to create Cassandra DB: %v", err) 195 } 196 197 s := NewExecutionMutableStateTaskSuite( 198 t, 199 shardStore, 200 executionStore, 201 serialization.NewSerializer(), 202 testData.Logger, 203 ) 204 suite.Run(t, s) 205 } 206 207 // TODO: Merge persistence-tests into the tests directory. 208 209 func TestCassandraHistoryStoreSuite(t *testing.T) { 210 testData, tearDown := setUpCassandraTest(t) 211 defer tearDown() 212 213 store, err := testData.Factory.NewExecutionStore() 214 if err != nil { 215 t.Fatalf("unable to create Cassandra DB: %v", err) 216 } 217 218 s := NewHistoryEventsSuite(t, store, testData.Logger) 219 suite.Run(t, s) 220 } 221 222 func TestCassandraTaskQueueSuite(t *testing.T) { 223 testData, tearDown := setUpCassandraTest(t) 224 defer tearDown() 225 226 taskQueueStore, err := testData.Factory.NewTaskStore() 227 if err != nil { 228 t.Fatalf("unable to create Cassandra DB: %v", err) 229 } 230 231 s := NewTaskQueueSuite(t, taskQueueStore, testData.Logger) 232 suite.Run(t, s) 233 } 234 235 func TestCassandraTaskQueueTaskSuite(t *testing.T) { 236 testData, tearDown := setUpCassandraTest(t) 237 defer tearDown() 238 239 taskQueueStore, err := testData.Factory.NewTaskStore() 240 if err != nil { 241 t.Fatalf("unable to create Cassandra DB: %v", err) 242 } 243 244 s := NewTaskQueueTaskSuite(t, taskQueueStore, testData.Logger) 245 suite.Run(t, s) 246 } 247 248 func TestCassandraVisibilityPersistence(t *testing.T) { 249 s := &VisibilityPersistenceSuite{ 250 TestBase: persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{}), 251 } 252 suite.Run(t, s) 253 } 254 255 func TestCassandraHistoryV2Persistence(t *testing.T) { 256 s := new(persistencetests.HistoryV2PersistenceSuite) 257 s.TestBase = persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{}) 258 s.TestBase.Setup(nil) 259 suite.Run(t, s) 260 } 261 262 func TestCassandraMetadataPersistenceV2(t *testing.T) { 263 s := new(persistencetests.MetadataPersistenceSuiteV2) 264 s.TestBase = persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{}) 265 s.TestBase.Setup(nil) 266 suite.Run(t, s) 267 } 268 269 func TestCassandraClusterMetadataPersistence(t *testing.T) { 270 s := new(persistencetests.ClusterMetadataManagerSuite) 271 s.TestBase = persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{}) 272 s.TestBase.Setup(nil) 273 suite.Run(t, s) 274 } 275 276 func TestCassandraQueuePersistence(t *testing.T) { 277 s := new(persistencetests.QueuePersistenceSuite) 278 s.TestBase = persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{}) 279 s.TestBase.Setup(nil) 280 suite.Run(t, s) 281 } 282 283 func TestCassandraQueueV2Persistence(t *testing.T) { 284 // This test function is split up into two parts: 285 // 1. Test the generic queue functionality, which is independent of the database choice (Cassandra here). 286 // This is done by calling the generic RunQueueV2TestSuite function. 287 // 2. Test the Cassandra-specific implementation of the queue. For example, things like queue message ID conflicts 288 // can only happen in Cassandra due to its lack of transactions, so we need to test those here. 289 290 t.Parallel() 291 292 cluster := persistencetests.NewTestClusterForCassandra(&persistencetests.TestBaseOptions{}, log.NewNoopLogger()) 293 cluster.SetupTestDatabase() 294 t.Cleanup(cluster.TearDownTestDatabase) 295 296 t.Run("Generic", func(t *testing.T) { 297 t.Parallel() 298 RunQueueV2TestSuite(t, newQueueV2Store(cluster.GetSession())) 299 }) 300 t.Run("CassandraSpecific", func(t *testing.T) { 301 t.Parallel() 302 testCassandraQueueV2(t, cluster) 303 }) 304 } 305 306 func testCassandraQueueV2DataCorruption(t *testing.T, cluster *cassandra.TestCluster) { 307 t.Run("ErrInvalidQueueMessageEncodingType", func(t *testing.T) { 308 t.Parallel() 309 testCassandraQueueV2ErrInvalidQueueMessageEncodingType(t, cluster) 310 }) 311 t.Run("ErrInvalidPayloadEncodingType", func(t *testing.T) { 312 t.Parallel() 313 testCassandraQueueV2ErrInvalidPayloadEncodingType(t, cluster) 314 }) 315 t.Run("ErrInvalidPayload", func(t *testing.T) { 316 t.Parallel() 317 testCassandraQueueV2ErrInvalidPayload(t, cluster) 318 }) 319 } 320 321 func testCassandraQueueV2(t *testing.T, cluster *cassandra.TestCluster) { 322 t.Run("DataCorruption", func(t *testing.T) { 323 t.Parallel() 324 testCassandraQueueV2DataCorruption(t, cluster) 325 }) 326 t.Run("QueryErrors", func(t *testing.T) { 327 t.Parallel() 328 testCassandraQueueV2QueryErrors(t, cluster) 329 }) 330 t.Run("RangeDeleteUpperBoundHigherThanMaxMessageID", func(t *testing.T) { 331 t.Parallel() 332 testCassandraQueueV2RangeDeleteUpperBoundHigherThanMaxMessageID(t, cluster) 333 }) 334 t.Run("RepeatedRangeDelete", func(t *testing.T) { 335 t.Parallel() 336 testCassandraQueueV2RepeatedRangeDelete(t, cluster) 337 }) 338 t.Run("MinMessageIDOptimization", func(t *testing.T) { 339 t.Parallel() 340 testCassandraQueueV2MinMessageIDOptimization(t, cluster) 341 }) 342 t.Run("ConcurrentConflicts", func(t *testing.T) { 343 testCassandraQueueV2ConcurrentConflicts(t, cluster) 344 }) 345 t.Run("MultiplePartitions", func(t *testing.T) { 346 testCassandraQueueV2MultiplePartitions(t, cluster) 347 }) 348 } 349 350 func testCassandraQueueV2ConcurrentConflicts(t *testing.T, cluster *cassandra.TestCluster) { 351 t.Run("EnqueueMessage", func(t *testing.T) { 352 t.Parallel() 353 testCassandraQueueV2EnqueueErrEnqueueMessageConflict(t, cluster) 354 }) 355 t.Run("RangeDeleteMessages", func(t *testing.T) { 356 t.Parallel() 357 testCassandraQueueV2ConcurrentRangeDeleteMessages(t, cluster) 358 }) 359 } 360 361 func testCassandraQueueV2QueryErrors(t *testing.T, cluster *cassandra.TestCluster) { 362 t.Run("GetQueueQuery", func(t *testing.T) { 363 t.Parallel() 364 testCassandraQueueV2ErrGetQueueQuery(t, cluster) 365 }) 366 t.Run("CreateQueueQuery", func(t *testing.T) { 367 t.Parallel() 368 testCassandraQueueV2ErrCreateQueueQuery(t, cluster) 369 }) 370 t.Run("RangeDeleteMessagesQuery", func(t *testing.T) { 371 t.Parallel() 372 testCassandraQueueV2ErrRangeDeleteMessagesQuery(t, cluster) 373 }) 374 t.Run("ErrReadMessagesQuery", func(t *testing.T) { 375 t.Parallel() 376 testCassandraQueueV2ErrGetMessagesQuery(t, cluster) 377 }) 378 t.Run("ErrEnqueueMessageQuery", func(t *testing.T) { 379 t.Parallel() 380 testCassandraQueueV2ErrEnqueueMessageQuery(t, cluster) 381 }) 382 t.Run("EnqueueMessageGetMaxMessageIDQuery", func(t *testing.T) { 383 t.Parallel() 384 testCassandraQueueV2ErrEnqueueMessageGetMaxMessageIDQuery(t, cluster) 385 }) 386 t.Run("ListQueuesGetMaxMessageIDQuery", func(t *testing.T) { 387 t.Parallel() 388 testCassandraQueueV2ErrListQueuesGetMaxMessageIDQuery(t, cluster) 389 }) 390 t.Run("RangeDeleteMessagesGetMaxMessageIDQuery", func(t *testing.T) { 391 t.Parallel() 392 testCassandraQueueV2ErrRangeDeleteMessagesGetMaxMessageIDQuery(t, cluster) 393 }) 394 t.Run("RangeDeleteMessagesUpdateQueueQuery", func(t *testing.T) { 395 t.Parallel() 396 testCassandraQueueV2ErrRangeDeleteMessagesUpdateQueueQuery(t, cluster) 397 }) 398 } 399 400 func testCassandraQueueV2ErrRangeDeleteMessagesUpdateQueueQuery(t *testing.T, cluster *cassandra.TestCluster) { 401 q := newQueueV2Store(failingSession{ 402 Session: cluster.GetSession(), 403 failingQueries: []string{cassandra.TemplateUpdateQueueMetadataQuery}, 404 }) 405 ctx := context.Background() 406 queueType := persistence.QueueTypeHistoryNormal 407 queueName := "test-queue-" + t.Name() 408 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 409 QueueType: queueType, 410 QueueName: queueName, 411 }) 412 require.NoError(t, err) 413 persistencetest.EnqueueMessagesForDelete(t, q, queueName, queueType) 414 err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID) 415 require.Error(t, err) 416 assert.ErrorAs(t, err, new(*serviceerror.Unavailable)) 417 assert.ErrorContains(t, err, assert.AnError.Error()) 418 assert.ErrorContains(t, err, "QueueV2UpdateQueueMetadata") 419 } 420 421 func testCassandraQueueV2ErrRangeDeleteMessagesGetMaxMessageIDQuery(t *testing.T, cluster *cassandra.TestCluster) { 422 session := &failingSession{ 423 Session: cluster.GetSession(), 424 failingQueries: []string{}, 425 } 426 q := newQueueV2Store(session) 427 ctx := context.Background() 428 queueType := persistence.QueueTypeHistoryNormal 429 queueName := "test-queue-" + t.Name() 430 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 431 QueueType: queueType, 432 QueueName: queueName, 433 }) 434 require.NoError(t, err) 435 persistencetest.EnqueueMessagesForDelete(t, q, queueName, queueType) 436 session.failingQueries = []string{cassandra.TemplateGetMaxMessageIDQuery} 437 err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID) 438 require.Error(t, err) 439 assert.ErrorAs(t, err, new(*serviceerror.Unavailable)) 440 assert.ErrorContains(t, err, assert.AnError.Error()) 441 assert.ErrorContains(t, err, "QueueV2GetMaxMessageID") 442 } 443 444 func testCassandraQueueV2ErrEnqueueMessageGetMaxMessageIDQuery(t *testing.T, cluster *cassandra.TestCluster) { 445 q := newQueueV2Store(failingSession{ 446 Session: cluster.GetSession(), 447 failingQueries: []string{cassandra.TemplateGetMaxMessageIDQuery}, 448 }) 449 ctx := context.Background() 450 queueType := persistence.QueueTypeHistoryNormal 451 queueName := "test-queue-" + t.Name() 452 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 453 QueueType: queueType, 454 QueueName: queueName, 455 }) 456 require.NoError(t, err) 457 _, err = persistencetest.EnqueueMessage(ctx, q, queueType, queueName) 458 require.Error(t, err) 459 assert.ErrorAs(t, err, new(*serviceerror.Unavailable)) 460 assert.ErrorContains(t, err, assert.AnError.Error()) 461 assert.ErrorContains(t, err, "QueueV2GetMaxMessageID") 462 } 463 464 func testCassandraQueueV2ErrListQueuesGetMaxMessageIDQuery(t *testing.T, cluster *cassandra.TestCluster) { 465 q := newQueueV2Store(failingSession{ 466 Session: cluster.GetSession(), 467 failingQueries: []string{cassandra.TemplateGetMaxMessageIDQuery}, 468 }) 469 ctx := context.Background() 470 queueType := persistence.QueueTypeHistoryDLQ 471 queueName := "test-queue-" + t.Name() 472 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 473 QueueType: queueType, 474 QueueName: queueName, 475 }) 476 require.NoError(t, err) 477 _, err = q.ListQueues(ctx, &persistence.InternalListQueuesRequest{ 478 QueueType: queueType, 479 PageSize: 100, 480 }) 481 require.Error(t, err) 482 assert.ErrorAs(t, err, new(*serviceerror.Unavailable)) 483 assert.ErrorContains(t, err, assert.AnError.Error()) 484 assert.ErrorContains(t, err, "QueueV2GetMaxMessageID") 485 } 486 487 func testCassandraQueueV2MultiplePartitions(t *testing.T, cluster *cassandra.TestCluster) { 488 t.Run("RangeDeleteMessages", func(t *testing.T) { 489 t.Parallel() 490 testCassandraQueueV2MultiplePartitionsRangeDelete(t, cluster) 491 }) 492 t.Run("ReadMessages", func(t *testing.T) { 493 t.Parallel() 494 testCassandraQueueV2MultiplePartitionsReadMessages(t, cluster) 495 }) 496 t.Run("ListQueues", func(t *testing.T) { 497 t.Parallel() 498 testCassandraQueueV2MultiplePartitionsListQueues(t, cluster) 499 }) 500 } 501 502 // Query checks if the query matches queryToBlockOn, and, if so, it notifies the test and then blocks until the test 503 // unblocks it. 504 func (f *blockingSession) Query(query string, args ...interface{}) gocql.Query { 505 if query == f.queryToBlockOn { 506 f.queryStarted <- struct{}{} 507 <-f.queryCanContinue 508 } 509 510 return f.Session.Query(query, args...) 511 } 512 513 // testCassandraQueueV2EnqueueErrEnqueueMessageConflict tests that when there are concurrent inserts to the queue, only one of 514 // them is accepted if they try to enqueue a message with the same ID, and the other clients are given the correct 515 // error. 516 func testCassandraQueueV2EnqueueErrEnqueueMessageConflict(t *testing.T, cluster *cassandra.TestCluster) { 517 const numConcurrentWrites = 3 518 519 session := &blockingSession{ 520 Session: cluster.GetSession(), 521 queryToBlockOn: cassandra.TemplateEnqueueMessageQuery, 522 queryStarted: make(chan struct{}, numConcurrentWrites), 523 queryCanContinue: make(chan struct{}), 524 } 525 526 q := newQueueV2Store(session) 527 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 528 t.Cleanup(cancel) 529 530 queueType := persistence.QueueTypeHistoryNormal 531 queueName := "test-queue-" + t.Name() 532 533 results := make(chan enqueueMessageResult, numConcurrentWrites) 534 535 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 536 QueueType: queueType, 537 QueueName: queueName, 538 }) 539 require.NoError(t, err) 540 for i := 0; i < numConcurrentWrites; i++ { 541 go func() { 542 res, err := persistencetest.EnqueueMessage(ctx, q, queueType, queueName) 543 if err != nil { 544 select { 545 case <-ctx.Done(): 546 return 547 case results <- enqueueMessageResult{err: err}: 548 } 549 } else { 550 select { 551 case <-ctx.Done(): 552 return 553 case results <- enqueueMessageResult{id: int(res.Metadata.ID)}: 554 } 555 } 556 }() 557 } 558 559 for i := 0; i < numConcurrentWrites; i++ { 560 select { 561 case <-ctx.Done(): 562 printResults(t, results) 563 t.Fatal("timed out waiting for enqueue to be called") 564 case <-session.queryStarted: 565 } 566 } 567 close(session.queryCanContinue) 568 569 numConflicts := 0 570 writtenMessageIDs := make([]int, 0, 1) 571 572 for i := 0; i < numConcurrentWrites; i++ { 573 var res enqueueMessageResult 574 select { 575 case <-ctx.Done(): 576 t.Fatal("timed out waiting for enqueue to return") 577 case res = <-results: 578 } 579 if res.err != nil { 580 assert.ErrorIs(t, res.err, cassandra.ErrEnqueueMessageConflict) 581 582 numConflicts++ 583 } else { 584 writtenMessageIDs = append(writtenMessageIDs, res.id) 585 } 586 } 587 588 assert.Equal(t, numConcurrentWrites-1, numConflicts, 589 "every query other than the accepted one should have failed") 590 assert.Len(t, writtenMessageIDs, 1, 591 "only one message should have been written") 592 593 messages, err := q.ReadMessages(ctx, &persistence.InternalReadMessagesRequest{ 594 QueueType: queueType, 595 QueueName: queueName, 596 PageSize: numConcurrentWrites, 597 NextPageToken: nil, 598 }) 599 600 require.NoError(t, err) 601 require.Len(t, messages.Messages, 1, 602 "there should only be one message in the queue") 603 assert.Equal(t, writtenMessageIDs[0], int(messages.Messages[0].MetaData.ID), 604 "the message in the queue should be the one that Cassandra told us was accepted") 605 } 606 607 func printResults(t *testing.T, results chan enqueueMessageResult) { 608 for { 609 select { 610 case res := <-results: 611 if res.err != nil { 612 t.Error("got unexpected error:", res.err) 613 } 614 default: 615 return 616 } 617 } 618 } 619 620 func testCassandraQueueV2ErrInvalidQueueMessageEncodingType(t *testing.T, cluster *cassandra.TestCluster) { 621 session := cluster.GetSession() 622 q := newQueueV2Store(session) 623 queueType := persistence.QueueTypeHistoryNormal 624 queueName := "test-queue-" + t.Name() 625 _, err := q.CreateQueue(context.Background(), &persistence.InternalCreateQueueRequest{ 626 QueueType: queueType, 627 QueueName: queueName, 628 }) 629 require.NoError(t, err) 630 err = session.Query( 631 cassandra.TemplateEnqueueMessageQuery, 632 queueType, 633 queueName, 634 0, // partition 635 1, // messageID 636 []byte("test"), 637 "bad-encoding-type", 638 ).Exec() 639 require.NoError(t, err) 640 _, err = q.ReadMessages(context.Background(), &persistence.InternalReadMessagesRequest{ 641 QueueType: queueType, 642 QueueName: queueName, 643 PageSize: 1, 644 }) 645 require.Error(t, err) 646 assert.ErrorAs(t, err, new(*serialization.UnknownEncodingTypeError)) 647 } 648 649 func (q failingQuery) MapScanCAS(map[string]interface{}) (bool, error) { 650 return false, assert.AnError 651 } 652 653 func (q failingQuery) WithContext(context.Context) gocql.Query { 654 return q 655 } 656 657 func (f failingSession) Query(query string, args ...interface{}) gocql.Query { 658 for _, q := range f.failingQueries { 659 if q == query { 660 return failingQuery{} 661 } 662 } 663 return f.Session.Query(query, args...) 664 } 665 666 func testCassandraQueueV2ErrGetMessagesQuery(t *testing.T, cluster *cassandra.TestCluster) { 667 q := newQueueV2Store(failingSession{ 668 Session: cluster.GetSession(), 669 failingQueries: []string{cassandra.TemplateGetMessagesQuery}, 670 }) 671 ctx := context.Background() 672 queueType := persistence.QueueTypeHistoryNormal 673 queueName := "test-queue-" + t.Name() 674 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 675 QueueType: queueType, 676 QueueName: queueName, 677 }) 678 require.NoError(t, err) 679 _, err = q.ReadMessages(ctx, &persistence.InternalReadMessagesRequest{ 680 QueueType: queueType, 681 QueueName: queueName, 682 PageSize: 1, 683 }) 684 require.Error(t, err) 685 assert.ErrorAs(t, err, new(*serviceerror.Unavailable)) 686 assert.ErrorContains(t, err, assert.AnError.Error()) 687 assert.ErrorContains(t, err, "QueueV2ReadMessages") 688 } 689 690 func testCassandraQueueV2ErrEnqueueMessageQuery(t *testing.T, cluster *cassandra.TestCluster) { 691 q := newQueueV2Store(failingSession{ 692 Session: cluster.GetSession(), 693 failingQueries: []string{cassandra.TemplateEnqueueMessageQuery}, 694 }) 695 ctx := context.Background() 696 queueType := persistence.QueueTypeHistoryNormal 697 queueName := "test-queue-" + t.Name() 698 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 699 QueueType: queueType, 700 QueueName: queueName, 701 }) 702 require.NoError(t, err) 703 _, err = persistencetest.EnqueueMessage(context.Background(), q, queueType, queueName) 704 require.Error(t, err) 705 assert.ErrorAs(t, err, new(*serviceerror.Unavailable)) 706 assert.ErrorContains(t, err, assert.AnError.Error()) 707 assert.ErrorContains(t, err, "QueueV2EnqueueMessage") 708 } 709 710 func testCassandraQueueV2ErrInvalidPayloadEncodingType(t *testing.T, cluster *cassandra.TestCluster) { 711 // Manually insert a row into the queues table that has an invalid metadata payload encoding type and then verify 712 // that we gracefully handle the error when we try to read from this queue. 713 714 session := cluster.GetSession() 715 q := newQueueV2Store(session) 716 // Using a different QueueType so that ListQueue tests are not failing because of corrupt queue metadata. 717 queueType := persistence.QueueV2Type(3) 718 queueName := "test-queue-" + t.Name() 719 err := session.Query( 720 cassandra.TemplateCreateQueueQuery, 721 queueType, 722 queueName, 723 []byte("test"), // payload 724 "bad-encoding-type", // payload encoding type 725 0, // version 726 ).Exec() 727 require.NoError(t, err) 728 _, err = q.ReadMessages(context.Background(), &persistence.InternalReadMessagesRequest{ 729 QueueType: queueType, 730 QueueName: queueName, 731 PageSize: 1, 732 }) 733 require.Error(t, err) 734 assert.ErrorAs(t, err, new(*serialization.UnknownEncodingTypeError)) 735 assert.ErrorContains(t, err, "bad-encoding-type") 736 assert.ErrorContains(t, err, strconv.Itoa(int(queueType))) 737 assert.ErrorContains(t, err, queueName) 738 739 _, err = q.ListQueues(context.Background(), &persistence.InternalListQueuesRequest{ 740 QueueType: queueType, 741 PageSize: 100, 742 }) 743 require.Error(t, err) 744 assert.ErrorAs(t, err, new(*serialization.UnknownEncodingTypeError)) 745 assert.ErrorContains(t, err, "bad-encoding-type") 746 assert.ErrorContains(t, err, strconv.Itoa(int(queueType))) 747 assert.ErrorContains(t, err, queueName) 748 } 749 750 func testCassandraQueueV2ErrInvalidPayload(t *testing.T, cluster *cassandra.TestCluster) { 751 // Manually insert a row into the queues table that has some invalid bytes for the queue metadata proto payload and 752 // then verify that we gracefully handle the error when we try to read from this queue. 753 754 session := cluster.GetSession() 755 q := newQueueV2Store(session) 756 queueType := persistence.QueueTypeHistoryNormal 757 queueName := "test-queue-" + t.Name() 758 err := session.Query( 759 cassandra.TemplateCreateQueueQuery, 760 queueType, 761 queueName, 762 []byte("invalid-payload"), // payload 763 enums.ENCODING_TYPE_PROTO3.String(), // payload encoding type 764 0, // version 765 ).Exec() 766 require.NoError(t, err) 767 _, err = q.ReadMessages(context.Background(), &persistence.InternalReadMessagesRequest{ 768 QueueType: queueType, 769 QueueName: queueName, 770 PageSize: 1, 771 }) 772 require.Error(t, err) 773 assert.ErrorAs(t, err, new(*serialization.DeserializationError)) 774 assert.ErrorContains(t, err, "unmarshal") 775 assert.ErrorContains(t, err, strconv.Itoa(int(queueType))) 776 assert.ErrorContains(t, err, queueName) 777 } 778 779 func testCassandraQueueV2ErrGetQueueQuery(t *testing.T, cluster *cassandra.TestCluster) { 780 q := newQueueV2Store(failingSession{ 781 Session: cluster.GetSession(), 782 failingQueries: []string{cassandra.TemplateGetQueueQuery}, 783 }) 784 ctx := context.Background() 785 queueType := persistence.QueueTypeHistoryNormal 786 queueName := "test-queue-" + t.Name() 787 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 788 QueueType: queueType, 789 QueueName: queueName, 790 }) 791 require.NoError(t, err) 792 _, err = q.ReadMessages(ctx, &persistence.InternalReadMessagesRequest{ 793 QueueType: queueType, 794 QueueName: queueName, 795 PageSize: 1, 796 }) 797 require.Error(t, err) 798 assert.ErrorAs(t, err, new(*serviceerror.Unavailable)) 799 assert.ErrorContains(t, err, assert.AnError.Error()) 800 assert.ErrorContains(t, err, "QueueV2GetQueue") 801 } 802 803 func testCassandraQueueV2ErrCreateQueueQuery(t *testing.T, cluster *cassandra.TestCluster) { 804 q := newQueueV2Store(failingSession{ 805 Session: cluster.GetSession(), 806 failingQueries: []string{cassandra.TemplateCreateQueueQuery}, 807 }) 808 ctx := context.Background() 809 queueType := persistence.QueueTypeHistoryNormal 810 queueName := "test-queue-" + t.Name() 811 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 812 QueueType: queueType, 813 QueueName: queueName, 814 }) 815 require.Error(t, err) 816 assert.ErrorAs(t, err, new(*serviceerror.Unavailable)) 817 assert.ErrorContains(t, err, assert.AnError.Error()) 818 assert.ErrorContains(t, err, "QueueV2CreateQueue") 819 } 820 821 func testCassandraQueueV2ErrRangeDeleteMessagesQuery(t *testing.T, cluster *cassandra.TestCluster) { 822 q := newQueueV2Store(failingSession{ 823 Session: cluster.GetSession(), 824 failingQueries: []string{cassandra.TemplateRangeDeleteMessagesQuery}, 825 }) 826 ctx := context.Background() 827 queueType := persistence.QueueTypeHistoryNormal 828 queueName := "test-queue-" + t.Name() 829 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 830 QueueType: queueType, 831 QueueName: queueName, 832 }) 833 require.NoError(t, err) 834 persistencetest.EnqueueMessagesForDelete(t, q, queueName, queueType) 835 err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID) 836 require.Error(t, err) 837 assert.ErrorAs(t, err, new(*serviceerror.Unavailable)) 838 assert.ErrorContains(t, err, assert.AnError.Error()) 839 assert.ErrorContains(t, err, "QueueV2RangeDeleteMessages") 840 } 841 842 func testCassandraQueueV2MinMessageIDOptimization(t *testing.T, cluster *cassandra.TestCluster) { 843 session := &recordingSession{ 844 Session: cluster.GetSession(), 845 } 846 q := newQueueV2Store(session) 847 ctx := context.Background() 848 queueType := persistence.QueueTypeHistoryNormal 849 queueName := "test-queue-" + t.Name() 850 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 851 QueueType: queueType, 852 QueueName: queueName, 853 }) 854 require.NoError(t, err) 855 for i := 0; i < 2; i++ { 856 _, err = persistencetest.EnqueueMessage(context.Background(), q, queueType, queueName) 857 require.NoError(t, err) 858 } 859 err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID) 860 require.NoError(t, err) 861 pageSize := 10 862 response, err := q.ReadMessages(ctx, &persistence.InternalReadMessagesRequest{ 863 QueueType: queueType, 864 QueueName: queueName, 865 PageSize: pageSize, 866 }) 867 require.NoError(t, err) 868 require.Len(t, response.Messages, 1) 869 assert.Equal(t, int64(persistence.FirstQueueMessageID+1), response.Messages[0].MetaData.ID) 870 var lastReadStmt *statement 871 for _, stmt := range session.statements { 872 if stmt.query == cassandra.TemplateGetMessagesQuery { 873 stmt := stmt 874 lastReadStmt = &stmt 875 } 876 } 877 require.NotNil(t, lastReadStmt, "expected to find a query to get messages") 878 args := lastReadStmt.args 879 require.Len(t, args, 5) 880 assert.Equal(t, queueType, args[0]) 881 assert.Equal(t, queueName, args[1]) 882 assert.Equal(t, 0, args[2]) 883 assert.Equal(t, persistence.FirstQueueMessageID+1, args[3], "We should skip the first "+ 884 "message ID because we deleted it") 885 assert.Equal(t, pageSize, args[4]) 886 } 887 888 func testCassandraQueueV2ConcurrentRangeDeleteMessages(t *testing.T, cluster *cassandra.TestCluster) { 889 // This test simulates a race condition between two RangeDeleteMessages calls. First, we enqueue 3 messages, then we 890 // start a request to delete the first message, and then we start another request to delete the first two messages. 891 // We have two cases, one where the first request to delete the first message is the leader and one where the second 892 // request is the leader. Both requests rendezvous at the query to update queue metadata, but the leader query goes 893 // first, and the follower query goes only after the leader query has completely finished. In the first case, the 894 // first request should succeed and the second request should fail with a conflict error. In the second case, the 895 // second request should succeed and the first request should fail with a conflict error. In both cases, only the 896 // third message should be in the queue. More importantly, after we retry the failing request, it should succeed, 897 // and the queue metadata should now record the min_message_id as 2. 898 899 for _, tc := range []struct { 900 name string 901 smallerDeleteIsLeader bool 902 }{ 903 { 904 name: "smaller delete goes first", 905 smallerDeleteIsLeader: true, 906 }, 907 { 908 name: "larger delete goes first", 909 smallerDeleteIsLeader: false, 910 }, 911 } { 912 tc := tc 913 t.Run(tc.name, func(t *testing.T) { 914 t.Parallel() 915 916 // Make two queue stores to simulate the leader and follower queries. We use two separate queue stores 917 // because it makes it easier to control which query goes first when they have separate blockingSession 918 // instances. 919 qs := make([]rangeDeleteTestQueue, 2) 920 for i, q := range qs { 921 // We need to use a blocking session here because we need to block the query to update the queue 922 // metadata. 923 q.session = &blockingSession{ 924 Session: cluster.GetSession(), 925 queryToBlockOn: cassandra.TemplateUpdateQueueMetadataQuery, 926 queryStarted: make(chan struct{}, 1), 927 queryCanContinue: make(chan struct{}, 1), 928 } 929 q.QueueV2 = newQueueV2Store(q.session) 930 q.deleteErrs = make(chan error, 1) 931 q.maxIDToDelete = persistence.FirstQueueMessageID + i 932 qs[i] = q 933 } 934 935 // Create the queue 936 ctx := context.Background() 937 queueType := persistence.QueueTypeHistoryNormal 938 queueName := "test-queue-" + t.Name() 939 _, err := qs[0].CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 940 QueueType: queueType, 941 QueueName: queueName, 942 }) 943 require.NoError(t, err) 944 945 // Enqueue 3 messages 946 for i := 0; i < 3; i++ { 947 _, err := persistencetest.EnqueueMessage(ctx, qs[0].QueueV2, queueType, queueName) 948 require.NoError(t, err) 949 } 950 951 // Start both RangeDeleteMessages call 952 for _, q := range qs { 953 q := q 954 go func() { 955 err := deleteMessages(ctx, q.QueueV2, queueType, queueName, q.maxIDToDelete) 956 q.deleteErrs <- err 957 }() 958 } 959 960 // Wait for both queries to start 961 for i := 0; i < 2; i++ { 962 <-qs[i].session.queryStarted 963 } 964 965 // Choose which query will be the leader and which will be the follower 966 var leader, follower *rangeDeleteTestQueue 967 if tc.smallerDeleteIsLeader { 968 leader = &qs[0] 969 follower = &qs[1] 970 } else { 971 leader = &qs[1] 972 follower = &qs[0] 973 } 974 975 // Let the leader query finish 976 close(leader.session.queryCanContinue) 977 err = <-leader.deleteErrs 978 require.NoError(t, err) 979 980 // Let the follower query finish 981 close(follower.session.queryCanContinue) 982 err = <-follower.deleteErrs 983 984 // Verify that the follower query failed 985 require.Error(t, err) 986 assert.ErrorIs(t, err, cassandra.ErrUpdateQueueConflict) 987 assert.ErrorContains(t, err, strconv.Itoa(int(queueType))) 988 assert.ErrorContains(t, err, queueName) 989 990 // Verify that the queue metadata was updated by the leader query 991 q, err := cassandra.GetQueue(ctx, qs[0].session, queueName, queueType) 992 require.NoError(t, err) 993 require.Len(t, q.Metadata.Partitions, 1) 994 if tc.smallerDeleteIsLeader { 995 // The smaller delete should have updated the min message ID to 1 because it succeeded, and the follower 996 // query received a conflict. However, the follower query will get retried, so the min message ID will 997 // eventually be updated correctly. 998 assert.Equal(t, int64(persistence.FirstQueueMessageID+1), q.Metadata.Partitions[0].MinMessageId) 999 } else { 1000 // The bigger delete should have updated the min message ID to 2 because it succeeded, so the min 1001 // message ID is already correct. However, the follower query will still get retried, so we need to 1002 // verify later that this retry does not change the min message ID to 1. 1003 assert.Equal(t, int64(persistence.FirstQueueMessageID+2), q.Metadata.Partitions[0].MinMessageId) 1004 } 1005 1006 // Retry the follower query. Note that this would fail if it actually tried to update the queue metadata 1007 // since we haven't unblocked it, so this implicitly tests that both operations are idempotent. 1008 err = deleteMessages(ctx, follower.QueueV2, queueType, queueName, follower.maxIDToDelete) 1009 require.NoError(t, err) 1010 1011 // Verify that the queue metadata was updated to reflect the new min message ID 1012 q, err = cassandra.GetQueue(ctx, qs[0].session, queueName, queueType) 1013 require.NoError(t, err) 1014 require.Len(t, q.Metadata.Partitions, 1) 1015 assert.Equal(t, int64(persistence.FirstQueueMessageID+2), q.Metadata.Partitions[0].MinMessageId) 1016 1017 // Verify that the first two messages were deleted no matter which query was the leader 1018 response, err := qs[0].ReadMessages(ctx, &persistence.InternalReadMessagesRequest{ 1019 QueueType: queueType, 1020 QueueName: queueName, 1021 PageSize: 10, 1022 }) 1023 require.NoError(t, err) 1024 require.Len(t, response.Messages, 1) 1025 assert.Equal(t, int64(persistence.FirstQueueMessageID+2), response.Messages[0].MetaData.ID) 1026 }) 1027 } 1028 } 1029 1030 func testCassandraQueueV2MultiplePartitionsRangeDelete(t *testing.T, cluster *cassandra.TestCluster) { 1031 // Manually insert a row into the queues table that has multiple partitions and then verify that we gracefully 1032 // handle the error when we try to range delete messages from this queue. 1033 1034 session := cluster.GetSession() 1035 logger := &testLogger{} 1036 q := newQueueV2Store(session, func(params *testQueueParams) { 1037 params.logger = logger 1038 }) 1039 queueType := persistence.QueueTypeHistoryNormal 1040 queueName := "test-queue-" + t.Name() 1041 insertQueueMetadataWithMultiplePartitions(t, session, queueType, queueName) 1042 err := deleteMessages(context.Background(), q, queueType, queueName, 1) 1043 require.Error(t, err) 1044 assert.ErrorContains(t, err, "partitions") 1045 } 1046 1047 func testCassandraQueueV2MultiplePartitionsReadMessages(t *testing.T, cluster *cassandra.TestCluster) { 1048 // Manually insert a row into the queues table that has multiple partitions and then verify that we gracefully 1049 // handle the error when we try to read messages from this queue. 1050 1051 session := cluster.GetSession() 1052 logger := &testLogger{} 1053 q := newQueueV2Store(session, func(params *testQueueParams) { 1054 params.logger = logger 1055 }) 1056 queueType := persistence.QueueTypeHistoryNormal 1057 queueName := "test-queue-" + t.Name() 1058 insertQueueMetadataWithMultiplePartitions(t, session, queueType, queueName) 1059 _, err := q.ReadMessages(context.Background(), &persistence.InternalReadMessagesRequest{ 1060 QueueType: queueType, 1061 QueueName: queueName, 1062 PageSize: 1, 1063 }) 1064 require.Error(t, err) 1065 assert.ErrorContains(t, err, "partitions") 1066 } 1067 1068 func testCassandraQueueV2MultiplePartitionsListQueues(t *testing.T, cluster *cassandra.TestCluster) { 1069 // Manually insert a row into the queues table that has multiple partitions and then verify that we gracefully 1070 // handle the error when we try to list queues. 1071 1072 session := cluster.GetSession() 1073 logger := &testLogger{} 1074 q := newQueueV2Store(session, func(params *testQueueParams) { 1075 params.logger = logger 1076 }) 1077 queueType := persistence.QueueTypeHistoryNormal 1078 queueName := "test-queue-" + t.Name() 1079 insertQueueMetadataWithMultiplePartitions(t, session, queueType, queueName) 1080 _, err := q.ListQueues(context.Background(), &persistence.InternalListQueuesRequest{ 1081 QueueType: queueType, 1082 PageSize: 100, 1083 }) 1084 require.Error(t, err) 1085 assert.ErrorContains(t, err, "partitions") 1086 } 1087 1088 func testCassandraQueueV2RangeDeleteUpperBoundHigherThanMaxMessageID(t *testing.T, cluster *cassandra.TestCluster) { 1089 q := newQueueV2Store(cluster.GetSession()) 1090 ctx := context.Background() 1091 queueType := persistence.QueueTypeHistoryNormal 1092 queueName := "test-queue-" + t.Name() 1093 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 1094 QueueType: queueType, 1095 QueueName: queueName, 1096 }) 1097 require.NoError(t, err) 1098 _, err = persistencetest.EnqueueMessage(ctx, q, queueType, queueName) 1099 require.NoError(t, err) 1100 err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID+2) 1101 require.NoError(t, err) 1102 res, err := persistencetest.EnqueueMessage(ctx, q, queueType, queueName) 1103 require.NoError(t, err) 1104 assert.Equal(t, int64(persistence.FirstQueueMessageID+1), res.Metadata.ID) 1105 } 1106 1107 func testCassandraQueueV2RepeatedRangeDelete(t *testing.T, cluster *cassandra.TestCluster) { 1108 // We never delete the last message from the queue. However, on a subsequent delete, the previous message with the 1109 // min_message_id of the queue should be deleted. This test verifies that. 1110 1111 q := newQueueV2Store(cluster.GetSession()) 1112 ctx := context.Background() 1113 queueType := persistence.QueueTypeHistoryNormal 1114 queueName := "test-queue-" + t.Name() 1115 _, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{ 1116 QueueType: queueType, 1117 QueueName: queueName, 1118 }) 1119 require.NoError(t, err) 1120 numMessages := 3 1121 for i := 0; i < numMessages; i++ { 1122 _, err := persistencetest.EnqueueMessage(ctx, q, queueType, queueName) 1123 require.NoError(t, err) 1124 } 1125 numRemainingMessages := getNumMessages(t, cluster, queueType, queueName, numMessages) 1126 assert.Equal(t, 3, numRemainingMessages) 1127 err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID) 1128 require.NoError(t, err) 1129 err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID+1) 1130 require.NoError(t, err) 1131 err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID+2) 1132 require.NoError(t, err) 1133 numRemainingMessages = getNumMessages(t, cluster, queueType, queueName, numMessages) 1134 assert.Equal(t, 1, numRemainingMessages, "expected only one message to remain in the queue"+ 1135 " because we never delete the last message, but the first two messages should have been deleted") 1136 } 1137 1138 func getNumMessages( 1139 t *testing.T, 1140 cluster *cassandra.TestCluster, 1141 queueType persistence.QueueV2Type, 1142 queueName string, 1143 numMessages int, 1144 ) int { 1145 // We query the database directly here to get the actual count of messages deleted. If we just rely on the store 1146 // method to read messages, that would indicate that we're updating min_message_id correctly, but it wouldn't verify 1147 // that any messages less than min_message_id are actually deleted from the database. 1148 1149 iter := cluster.GetSession().Query( 1150 cassandra.TemplateGetMessagesQuery, 1151 queueType, 1152 queueName, 1153 0, // partition 1154 persistence.FirstQueueMessageID, // minMessageID 1155 numMessages, // limit 1156 ).Iter() 1157 numRemainingMessages := 0 1158 for iter.MapScan(map[string]interface{}{}) { 1159 numRemainingMessages++ 1160 } 1161 require.NoError(t, iter.Close()) 1162 return numRemainingMessages 1163 } 1164 1165 func newQueueV2Store(session gocql.Session, opts ...func(params *testQueueParams)) persistence.QueueV2 { 1166 p := testQueueParams{ 1167 logger: log.NewTestLogger(), 1168 } 1169 for _, opt := range opts { 1170 opt(&p) 1171 } 1172 return cassandra.NewQueueV2Store(session, p.logger) 1173 } 1174 1175 func insertQueueMetadataWithMultiplePartitions( 1176 t *testing.T, 1177 session gocql.Session, 1178 queueType persistence.QueueV2Type, 1179 queueName string, 1180 ) { 1181 t.Helper() 1182 1183 queuePB := persistencespb.Queue{ 1184 Partitions: map[int32]*persistencespb.QueuePartition{ 1185 0: {}, 1186 1: {}, 1187 }, 1188 } 1189 bytes, _ := queuePB.Marshal() 1190 err := session.Query( 1191 cassandra.TemplateCreateQueueQuery, 1192 queueType, 1193 queueName, 1194 bytes, // payload 1195 enums.ENCODING_TYPE_PROTO3.String(), // payload encoding type 1196 0, // version 1197 ).Exec() 1198 require.NoError(t, err) 1199 } 1200 1201 func deleteMessages( 1202 ctx context.Context, 1203 q persistence.QueueV2, 1204 queueType persistence.QueueV2Type, 1205 queueName string, 1206 maxID int, 1207 ) error { 1208 _, err := q.RangeDeleteMessages(ctx, &persistence.InternalRangeDeleteMessagesRequest{ 1209 QueueType: queueType, 1210 QueueName: queueName, 1211 InclusiveMaxMessageMetadata: persistence.MessageMetadata{ 1212 ID: int64(maxID), 1213 }, 1214 }) 1215 1216 return err 1217 }