560 lines
15 KiB
YAML
560 lines
15 KiB
YAML
# Test that all the operations go to the same mongos.
|
|
#
|
|
# In tests that don't include command-started events the assertion is implicit:
|
|
# that all the read operations succeed. If the driver does not properly pin to
|
|
# a single mongos then one of the operations in a transaction will eventually
|
|
# be sent to a different mongos, which is unaware of the transaction, and the
|
|
# mongos will return a command error. An example of such an error is:
|
|
# {
|
|
# 'ok': 0.0,
|
|
# 'errmsg': 'cannot continue txnId -1 for session 28938f50-9d29-4ca5-8de5-ddaf261267c4 - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= with txnId 1',
|
|
# 'code': 251,
|
|
# 'codeName': 'NoSuchTransaction',
|
|
# 'errorLabels': ['TransientTransactionError']
|
|
# }
|
|
runOn:
|
|
-
|
|
minServerVersion: "4.1.8"
|
|
topology: ["sharded"]
|
|
# serverless proxy doesn't append error labels to errors in transactions
|
|
# caused by failpoints (CLOUDP-88216)
|
|
serverless: "forbid"
|
|
|
|
database_name: &database_name "transaction-tests"
|
|
collection_name: &collection_name "test"
|
|
|
|
data: &data
|
|
- {_id: 1}
|
|
- {_id: 2}
|
|
|
|
tests:
|
|
- description: countDocuments
|
|
useMultipleMongoses: true
|
|
operations:
|
|
- &startTransaction
|
|
name: startTransaction
|
|
object: session0
|
|
- &countDocuments
|
|
name: countDocuments
|
|
object: collection
|
|
arguments:
|
|
filter:
|
|
_id: 2
|
|
session: session0
|
|
result: 1
|
|
- *countDocuments
|
|
- *countDocuments
|
|
- *countDocuments
|
|
- *countDocuments
|
|
- *countDocuments
|
|
- *countDocuments
|
|
- *countDocuments
|
|
- &commitTransaction
|
|
name: commitTransaction
|
|
object: session0
|
|
|
|
outcome:
|
|
collection:
|
|
data: *data
|
|
|
|
- description: distinct
|
|
useMultipleMongoses: true
|
|
operations:
|
|
- *startTransaction
|
|
- &distinct
|
|
name: distinct
|
|
object: collection
|
|
arguments:
|
|
fieldName: _id
|
|
session: session0
|
|
result: [1, 2]
|
|
- *distinct
|
|
- *distinct
|
|
- *distinct
|
|
- *distinct
|
|
- *distinct
|
|
- *distinct
|
|
- *distinct
|
|
- *commitTransaction
|
|
|
|
outcome:
|
|
collection:
|
|
data: *data
|
|
|
|
- description: find
|
|
useMultipleMongoses: true
|
|
operations:
|
|
- name: startTransaction
|
|
object: session0
|
|
- &find
|
|
name: find
|
|
object: collection
|
|
arguments:
|
|
filter:
|
|
_id: 2
|
|
session: session0
|
|
result:
|
|
- {_id: 2}
|
|
- *find
|
|
- *find
|
|
- *find
|
|
- *find
|
|
- *find
|
|
- *find
|
|
- *find
|
|
- *commitTransaction
|
|
|
|
outcome:
|
|
collection:
|
|
data: *data
|
|
|
|
- description: insertOne
|
|
useMultipleMongoses: true
|
|
operations:
|
|
- name: startTransaction
|
|
object: session0
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 3
|
|
session: session0
|
|
result:
|
|
insertedId: 3
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 4
|
|
session: session0
|
|
result:
|
|
insertedId: 4
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 5
|
|
session: session0
|
|
result:
|
|
insertedId: 5
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 6
|
|
session: session0
|
|
result:
|
|
insertedId: 6
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 7
|
|
session: session0
|
|
result:
|
|
insertedId: 7
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 8
|
|
session: session0
|
|
result:
|
|
insertedId: 8
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 9
|
|
session: session0
|
|
result:
|
|
insertedId: 9
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 10
|
|
session: session0
|
|
result:
|
|
insertedId: 10
|
|
- *commitTransaction
|
|
|
|
outcome:
|
|
collection:
|
|
data:
|
|
- {_id: 1}
|
|
- {_id: 2}
|
|
- {_id: 3}
|
|
- {_id: 4}
|
|
- {_id: 5}
|
|
- {_id: 6}
|
|
- {_id: 7}
|
|
- {_id: 8}
|
|
- {_id: 9}
|
|
- {_id: 10}
|
|
|
|
- description: mixed read write operations
|
|
useMultipleMongoses: true
|
|
operations:
|
|
- name: startTransaction
|
|
object: session0
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 3
|
|
session: session0
|
|
result:
|
|
insertedId: 3
|
|
- &countDocuments
|
|
name: countDocuments
|
|
object: collection
|
|
arguments:
|
|
filter:
|
|
_id: 3
|
|
session: session0
|
|
result: 1
|
|
- *countDocuments
|
|
- *countDocuments
|
|
- *countDocuments
|
|
- *countDocuments
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 4
|
|
session: session0
|
|
result:
|
|
insertedId: 4
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 5
|
|
session: session0
|
|
result:
|
|
insertedId: 5
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 6
|
|
session: session0
|
|
result:
|
|
insertedId: 6
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
document:
|
|
_id: 7
|
|
session: session0
|
|
result:
|
|
insertedId: 7
|
|
- *commitTransaction
|
|
|
|
outcome:
|
|
collection:
|
|
data:
|
|
- {_id: 1}
|
|
- {_id: 2}
|
|
- {_id: 3}
|
|
- {_id: 4}
|
|
- {_id: 5}
|
|
- {_id: 6}
|
|
- {_id: 7}
|
|
|
|
- description: multiple commits
|
|
useMultipleMongoses: true
|
|
operations:
|
|
- name: startTransaction
|
|
object: session0
|
|
- name: insertMany
|
|
object: collection
|
|
arguments:
|
|
documents:
|
|
- _id: 3
|
|
- _id: 4
|
|
session: session0
|
|
result:
|
|
insertedIds: {0: 3, 1: 4}
|
|
# Session is pinned and remains pinned after successful commits.
|
|
- &assertSessionPinned
|
|
name: assertSessionPinned
|
|
object: testRunner
|
|
arguments:
|
|
session: session0
|
|
- *commitTransaction
|
|
- *assertSessionPinned
|
|
- *commitTransaction
|
|
- *commitTransaction
|
|
- *commitTransaction
|
|
- *commitTransaction
|
|
- *commitTransaction
|
|
- *commitTransaction
|
|
- *commitTransaction
|
|
- *commitTransaction
|
|
- *commitTransaction
|
|
- *assertSessionPinned
|
|
|
|
outcome:
|
|
collection:
|
|
data:
|
|
- {_id: 1}
|
|
- {_id: 2}
|
|
- {_id: 3}
|
|
- {_id: 4}
|
|
|
|
- description: remain pinned after non-transient error on commit
|
|
useMultipleMongoses: true
|
|
operations:
|
|
- name: startTransaction
|
|
object: session0
|
|
- name: insertMany
|
|
object: collection
|
|
arguments:
|
|
documents:
|
|
- _id: 3
|
|
- _id: 4
|
|
session: session0
|
|
result:
|
|
insertedIds: {0: 3, 1: 4}
|
|
# Session is pinned.
|
|
- *assertSessionPinned
|
|
# Fail the commit with a non-transient error.
|
|
- name: targetedFailPoint
|
|
object: testRunner
|
|
arguments:
|
|
session: session0
|
|
failPoint:
|
|
configureFailPoint: failCommand
|
|
mode: { times: 1 }
|
|
data:
|
|
failCommands: ["commitTransaction"]
|
|
errorCode: 51 # ManualInterventionRequired
|
|
- name: commitTransaction
|
|
object: session0
|
|
result:
|
|
errorLabelsOmit: ["TransientTransactionError"]
|
|
errorCode: 51
|
|
- *assertSessionPinned
|
|
# The next commit should succeed.
|
|
- name: commitTransaction
|
|
object: session0
|
|
- *assertSessionPinned
|
|
|
|
outcome:
|
|
collection:
|
|
data:
|
|
- {_id: 1}
|
|
- {_id: 2}
|
|
- {_id: 3}
|
|
- {_id: 4}
|
|
|
|
- description: unpin after transient error within a transaction
|
|
useMultipleMongoses: true
|
|
operations:
|
|
- name: startTransaction
|
|
object: session0
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
session: session0
|
|
document:
|
|
_id: 3
|
|
result:
|
|
insertedId: 3
|
|
# Enable the fail point only on the Mongos that session0 is pinned to.
|
|
- name: targetedFailPoint
|
|
object: testRunner
|
|
arguments:
|
|
session: session0
|
|
failPoint:
|
|
configureFailPoint: failCommand
|
|
mode: { times: 1 }
|
|
data:
|
|
failCommands: ["insert"]
|
|
closeConnection: true
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
session: session0
|
|
document:
|
|
_id: 4
|
|
result:
|
|
errorLabelsContain: ["TransientTransactionError"]
|
|
errorLabelsOmit: ["UnknownTransactionCommitResult"]
|
|
# Session unpins from the first mongos after the insert error and
|
|
# abortTransaction succeeds immediately on any mongos.
|
|
- name: abortTransaction
|
|
object: session0
|
|
|
|
expectations:
|
|
- command_started_event:
|
|
command:
|
|
insert: *collection_name
|
|
documents:
|
|
- _id: 3
|
|
ordered: true
|
|
readConcern:
|
|
lsid: session0
|
|
txnNumber:
|
|
$numberLong: "1"
|
|
startTransaction: true
|
|
autocommit: false
|
|
writeConcern:
|
|
command_name: insert
|
|
database_name: *database_name
|
|
- command_started_event:
|
|
command:
|
|
insert: *collection_name
|
|
documents:
|
|
- _id: 4
|
|
ordered: true
|
|
readConcern:
|
|
lsid: session0
|
|
txnNumber:
|
|
$numberLong: "1"
|
|
startTransaction:
|
|
autocommit: false
|
|
writeConcern:
|
|
command_name: insert
|
|
database_name: *database_name
|
|
- command_started_event:
|
|
command:
|
|
abortTransaction: 1
|
|
lsid: session0
|
|
txnNumber:
|
|
$numberLong: "1"
|
|
startTransaction:
|
|
autocommit: false
|
|
writeConcern:
|
|
recoveryToken: 42
|
|
command_name: abortTransaction
|
|
database_name: admin
|
|
|
|
outcome:
|
|
collection:
|
|
data:
|
|
- _id: 1
|
|
- _id: 2
|
|
|
|
# Applications should not run commitTransaction after transient errors but
|
|
# the transactions API allows it and this test confirms unpinning behavior.
|
|
# In a sharded cluster, a transient error within a transaction unpins the
|
|
# session. This way a subsequent abort can "succeed" immediately instead of
|
|
# blocking for serverSelectionTimeoutMS in the case the mongos went down.
|
|
# However since the abortTransaction helper ignores errors, this test uses
|
|
# commitTransaction to prove the session was unpinned.
|
|
- description: unpin after transient error within a transaction and commit
|
|
useMultipleMongoses: true
|
|
clientOptions:
|
|
# Increase heartbeatFrequencyMS to avoid the race condition where an in
|
|
# flight heartbeat refreshes the first mongoes' SDAM state in between
|
|
# the insert connection error and the single commit attempt.
|
|
heartbeatFrequencyMS: 30000
|
|
operations:
|
|
- name: startTransaction
|
|
object: session0
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
session: session0
|
|
document:
|
|
_id: 3
|
|
result:
|
|
insertedId: 3
|
|
# Enable the fail point only on the Mongos that session0 is pinned to.
|
|
# Fail hello/legacy hello to prevent the heartbeat requested directly after the
|
|
# insert error from racing with server selection for the commit.
|
|
# Note: times: 7 is slightly artbitrary but it accounts for one failed
|
|
# insert and some SDAM heartbeats. A test runner will have multiple
|
|
# clients connected to this server so this fail point configuration
|
|
# is also racy.
|
|
- name: targetedFailPoint
|
|
object: testRunner
|
|
arguments:
|
|
session: session0
|
|
failPoint:
|
|
configureFailPoint: failCommand
|
|
mode: { times: 7 }
|
|
data:
|
|
failCommands: ["insert", "isMaster", "hello"]
|
|
closeConnection: true
|
|
- name: insertOne
|
|
object: collection
|
|
arguments:
|
|
session: session0
|
|
document:
|
|
_id: 4
|
|
result:
|
|
errorLabelsContain: ["TransientTransactionError"]
|
|
errorLabelsOmit: ["UnknownTransactionCommitResult"]
|
|
# Session unpins from the first mongos after the insert error and
|
|
# commitTransaction selects the second mongos which is unaware of the
|
|
# transaction and therefore fails with NoSuchTransaction error. If this
|
|
# commit succeeds it indicates a bug, either:
|
|
# - the driver mistakenly remained pinned even after the insert error, or
|
|
# - the test client was initialized with a single mongos seed
|
|
#
|
|
# Note that the commit attempt should not select the original mongos
|
|
# because that server's SDAM state is reset by the connection error,
|
|
# heartbeatFrequencyMS is high, and subsequent heartbeats
|
|
# should fail.
|
|
- name: commitTransaction
|
|
object: session0
|
|
result:
|
|
errorLabelsContain: ["TransientTransactionError"]
|
|
errorLabelsOmit: ["UnknownTransactionCommitResult"]
|
|
errorCodeName: NoSuchTransaction
|
|
|
|
|
|
expectations:
|
|
- command_started_event:
|
|
command:
|
|
insert: *collection_name
|
|
documents:
|
|
- _id: 3
|
|
ordered: true
|
|
readConcern:
|
|
lsid: session0
|
|
txnNumber:
|
|
$numberLong: "1"
|
|
startTransaction: true
|
|
autocommit: false
|
|
writeConcern:
|
|
command_name: insert
|
|
database_name: *database_name
|
|
- command_started_event:
|
|
command:
|
|
insert: *collection_name
|
|
documents:
|
|
- _id: 4
|
|
ordered: true
|
|
readConcern:
|
|
lsid: session0
|
|
txnNumber:
|
|
$numberLong: "1"
|
|
startTransaction:
|
|
autocommit: false
|
|
writeConcern:
|
|
command_name: insert
|
|
database_name: *database_name
|
|
- command_started_event:
|
|
command:
|
|
commitTransaction: 1
|
|
lsid: session0
|
|
txnNumber:
|
|
$numberLong: "1"
|
|
startTransaction:
|
|
autocommit: false
|
|
writeConcern:
|
|
recoveryToken: 42
|
|
command_name: commitTransaction
|
|
database_name: admin
|
|
|
|
outcome:
|
|
collection:
|
|
data:
|
|
- _id: 1
|
|
- _id: 2
|