|
| 1 | +# Driver Session Tests |
| 2 | + |
| 3 | +______________________________________________________________________ |
| 4 | + |
| 5 | +## Introduction |
| 6 | + |
| 7 | +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of |
| 8 | +sessions. These tests utilize the [Unified Test Format](../../unified-test-format/unified-test-format.md). |
| 9 | + |
| 10 | +### Snapshot session tests |
| 11 | + |
| 12 | +The default snapshot history window on the server is 5 minutes. Running the test in debug mode, or in any other slow |
| 13 | +configuration may lead to `SnapshotTooOld` errors. Drivers can work around this issue by increasing the server's |
| 14 | +`minSnapshotHistoryWindowInSeconds` parameter, for example: |
| 15 | + |
| 16 | +```python |
| 17 | +client.admin.command('setParameter', 1, minSnapshotHistoryWindowInSeconds=600) |
| 18 | +``` |
| 19 | + |
| 20 | +### Testing against servers that do not support sessions |
| 21 | + |
| 22 | +Since all regular 3.6+ servers support sessions, the prose tests which test for session non-support SHOULD use a |
| 23 | +mongocryptd server as the test server (available with server versions 4.2+); however, if future versions of mongocryptd |
| 24 | +support sessions or if mongocryptd is not a viable option for the driver implementing these tests, another server MAY be |
| 25 | +substituted as long as it does not return a non-null value for `logicalSessionTimeoutMinutes`; in the event that no such |
| 26 | +server is readily available, a mock server may be used as a last resort. |
| 27 | + |
| 28 | +As part of the test setup for these cases, create a `MongoClient` pointed at the test server with the options specified |
| 29 | +in the test case and verify that the test server does NOT define a value for `logicalSessionTimeoutMinutes` by sending a |
| 30 | +hello command and checking the response. |
| 31 | + |
| 32 | +## Prose tests |
| 33 | + |
| 34 | +### 1. Setting both `snapshot` and `causalConsistency` to true is not allowed |
| 35 | + |
| 36 | +Snapshot sessions tests require server of version 5.0 or higher and replica set or a sharded cluster deployment. |
| 37 | + |
| 38 | +- `client.startSession(snapshot = true, causalConsistency = true)` |
| 39 | +- Assert that an error was raised by driver |
| 40 | + |
| 41 | +### 2. Pool is LIFO |
| 42 | + |
| 43 | +This test applies to drivers with session pools. |
| 44 | + |
| 45 | +- Call `MongoClient.startSession` twice to create two sessions, let us call them `A` and `B`. |
| 46 | +- Call `A.endSession`, then `B.endSession`. |
| 47 | +- Call `MongoClient.startSession`: the resulting session must have the same session ID as `B`. |
| 48 | +- Call `MongoClient.startSession` again: the resulting session must have the same session ID as `A`. |
| 49 | + |
| 50 | +### 3. `$clusterTime` in commands |
| 51 | + |
| 52 | +- Register a command-started and a command-succeeded APM listener. If the driver has no APM support, inspect |
| 53 | + commands/replies in another idiomatic way, such as monkey-patching or a mock server. |
| 54 | +- Send a `ping` command to the server with the generic `runCommand` method. |
| 55 | +- Assert that the command passed to the command-started listener includes `$clusterTime` if and only if `maxWireVersion` |
| 56 | + > = 6. |
| 57 | +- Record the `$clusterTime`, if any, in the reply passed to the command-succeeded APM listener. |
| 58 | +- Send another `ping` command. |
| 59 | +- Assert that `$clusterTime` in the command passed to the command-started listener, if any, equals the `$clusterTime` in |
| 60 | + the previous server reply. |
| 61 | + |
| 62 | +Repeat the above for: |
| 63 | + |
| 64 | +- An aggregate command from the `aggregate` helper method |
| 65 | +- A find command from the `find` helper method |
| 66 | +- An insert command from the `insert_one` helper method |
| 67 | + |
| 68 | +### 4. Explicit and implicit session arguments |
| 69 | + |
| 70 | +- Register a command-started APM listener. If the driver has no APM support, inspect commands in another idiomatic way, |
| 71 | + such as monkey-patching or a mock server. |
| 72 | +- Create `client1` |
| 73 | +- Get `database` from `client1` |
| 74 | +- Get `collection` from `database` |
| 75 | +- Start `session` from `client1` |
| 76 | +- Call `collection.insertOne(session,...)` |
| 77 | +- Assert that the command passed to the command-started listener contained the session `lsid` from `session`. |
| 78 | +- Call `collection.insertOne(,...)` (*without* a session argument) |
| 79 | +- Assert that the command passed to the command-started listener contained a session `lsid`. |
| 80 | + |
| 81 | +Repeat the above for all methods that take a session parameter. |
| 82 | + |
| 83 | +### 5. Session argument is for the right client |
| 84 | + |
| 85 | +- Create `client1` and `client2` |
| 86 | +- Get `database` from `client1` |
| 87 | +- Get `collection` from `database` |
| 88 | +- Start `session` from `client2` |
| 89 | +- Call `collection.insertOne(session,...)` |
| 90 | +- Assert that an error was reported because `session` was not started from `client1` |
| 91 | + |
| 92 | +Repeat the above for all methods that take a session parameter. |
| 93 | + |
| 94 | +### 6. No further operations can be performed using a session after `endSession` has been called |
| 95 | + |
| 96 | +- Start a `session` |
| 97 | +- End the `session` |
| 98 | +- Call `collection.InsertOne(session, ...)` |
| 99 | +- Assert that the proper error was reported |
| 100 | + |
| 101 | +Repeat the above for all methods that take a session parameter. |
| 102 | + |
| 103 | +If your driver implements a platform dependent idiomatic disposal pattern, test that also (if the idiomatic disposal |
| 104 | +pattern calls `endSession` it would be sufficient to only test the disposal pattern since that ends up calling |
| 105 | +`endSession`). |
| 106 | + |
| 107 | +### 7. Authenticating as multiple users suppresses implicit sessions |
| 108 | + |
| 109 | +Skip this test if your driver does not allow simultaneous authentication with multiple users. |
| 110 | + |
| 111 | +- Authenticate as two users |
| 112 | +- Call `findOne` with no explicit session |
| 113 | +- Capture the command sent to the server |
| 114 | +- Assert that the command sent to the server does not have an `lsid` field |
| 115 | + |
| 116 | +### 8. Client-side cursor that exhausts the results on the initial query immediately returns the implicit session to the pool |
| 117 | + |
| 118 | +- Insert two documents into a collection |
| 119 | +- Execute a find operation on the collection and iterate past the first document |
| 120 | +- Assert that the implicit session is returned to the pool. This can be done in several ways: |
| 121 | + - Track in-use count in the server session pool and assert that the count has dropped to zero |
| 122 | + - Track the lsid used for the find operation (e.g. with APM) and then do another operation and assert that the same |
| 123 | + lsid is used as for the find operation. |
| 124 | + |
| 125 | +### 9. Client-side cursor that exhausts the results after a `getMore` immediately returns the implicit session to the pool |
| 126 | + |
| 127 | +- Insert five documents into a collection |
| 128 | +- Execute a find operation on the collection with batch size of 3 |
| 129 | +- Iterate past the first four documents, forcing the final `getMore` operation |
| 130 | +- Assert that the implicit session is returned to the pool prior to iterating past the last document |
| 131 | + |
| 132 | +### 10. No remaining sessions are checked out after each functional test |
| 133 | + |
| 134 | +At the end of every individual functional test of the driver, there SHOULD be an assertion that there are no remaining |
| 135 | +sessions checked out from the pool. This may require changes to existing tests to ensure that they close any explicit |
| 136 | +client sessions and any unexhausted cursors. |
| 137 | + |
| 138 | +### 11. For every combination of topology and readPreference, ensure that `find` and `getMore` both send the same session id |
| 139 | + |
| 140 | +- Insert three documents into a collection |
| 141 | +- Execute a `find` operation on the collection with a batch size of 2 |
| 142 | +- Assert that the server receives a non-zero lsid |
| 143 | +- Iterate through enough documents (3) to force a `getMore` |
| 144 | +- Assert that the server receives a non-zero lsid equal to the lsid that `find` sent. |
| 145 | + |
| 146 | +### 12. Session pool can be cleared after forking without calling `endSession` |
| 147 | + |
| 148 | +Skip this test if your driver does not allow forking. |
| 149 | + |
| 150 | +- Create ClientSession |
| 151 | +- Record its lsid |
| 152 | +- Delete it (so the lsid is pushed into the pool) |
| 153 | +- Fork |
| 154 | +- In the parent, create a ClientSession and assert its lsid is the same. |
| 155 | +- In the child, create a ClientSession and assert its lsid is different. |
| 156 | + |
| 157 | +### 13. Existing sessions are not checked into a cleared pool after forking |
| 158 | + |
| 159 | +Skip this test if your driver does not allow forking. |
| 160 | + |
| 161 | +- Create ClientSession |
| 162 | +- Record its lsid |
| 163 | +- Fork |
| 164 | +- In the parent, return the ClientSession to the pool, create a new ClientSession, and assert its lsid is the same. |
| 165 | +- In the child, return the ClientSession to the pool, create a new ClientSession, and assert its lsid is different. |
| 166 | + |
| 167 | +### 14. Implicit sessions only allocate their server session after a successful connection checkout |
| 168 | + |
| 169 | +- Create a MongoClient with the following options: `maxPoolSize=1` and `retryWrites=true`. If testing against a sharded |
| 170 | + deployment, the test runner MUST ensure that the MongoClient connects to only a single mongos host. |
| 171 | +- Attach a command started listener that collects each command's lsid |
| 172 | +- Initiate the following concurrent operations |
| 173 | + - `insertOne({ }),` |
| 174 | + - `deleteOne({ }),` |
| 175 | + - `updateOne({ }, { $set: { a: 1 } }),` |
| 176 | + - `bulkWrite([{ updateOne: { filter: { }, update: { $set: { a: 1 } } } }]),` |
| 177 | + - `findOneAndDelete({ }),` |
| 178 | + - `findOneAndUpdate({ }, { $set: { a: 1 } }),` |
| 179 | + - `findOneAndReplace({ }, { a: 1 }),` |
| 180 | + - `find().toArray()` |
| 181 | +- Wait for all operations to complete successfully |
| 182 | +- Assert the following across at least 5 retries of the above test: |
| 183 | + - Drivers MUST assert that exactly one session is used for all operations at least once across the retries of this |
| 184 | + test. |
| 185 | + - Note that it's possible, although rare, for >1 server session to be used because the session is not released until |
| 186 | + after the connection is checked in. |
| 187 | + - Drivers MUST assert that the number of allocated sessions is strictly less than the number of concurrent operations |
| 188 | + in every retry of this test. In this instance it would be less than (but NOT equal to) 8. |
| 189 | + |
| 190 | +### 15. `lsid` is added inside `$query` when using OP_QUERY |
| 191 | + |
| 192 | +This test only applies to drivers that have not implemented OP_MSG and still use OP_QUERY. |
| 193 | + |
| 194 | +- For a command to a mongos that includes a readPreference, verify that the `lsid` on query commands is added inside the |
| 195 | + `$query` field, and NOT as a top-level field. |
| 196 | + |
| 197 | +### 16. Authenticating as a second user after starting a session results in a server error |
| 198 | + |
| 199 | +This test only applies to drivers that allow authentication to be changed on the fly. |
| 200 | + |
| 201 | +- Authenticate as the first user |
| 202 | +- Start a session by calling `startSession` |
| 203 | +- Authenticate as a second user |
| 204 | +- Call `findOne` using the session as an explicit session |
| 205 | +- Assert that the driver returned an error because multiple users are authenticated |
| 206 | + |
| 207 | +### 17. Driver verifies that the session is owned by the current user |
| 208 | + |
| 209 | +This test only applies to drivers that allow authentication to be changed on the fly. |
| 210 | + |
| 211 | +- Authenticate as user A |
| 212 | +- Start a session by calling `startSession` |
| 213 | +- Logout user A |
| 214 | +- Authenticate as user B |
| 215 | +- Call `findOne` using the session as an explicit session |
| 216 | +- Assert that the driver returned an error because the session is owned by a different user |
| 217 | + |
| 218 | +### 18. Implicit session is ignored if connection does not support sessions |
| 219 | + |
| 220 | +Refer to [Testing against servers that do not support sessions](#testing-against-servers-that-do-not-support-sessions) |
| 221 | +and configure a `MongoClient` with command monitoring enabled. |
| 222 | + |
| 223 | +- Send a read command to the server (e.g., `findOne`), ignoring any errors from the server response |
| 224 | +- Check the corresponding `commandStarted` event: verify that `lsid` is not set |
| 225 | +- Send a write command to the server (e.g., `insertOne`), ignoring any errors from the server response |
| 226 | +- Check the corresponding `commandStarted` event: verify that lsid is not set |
| 227 | + |
| 228 | +### 19. Explicit session raises an error if connection does not support sessions |
| 229 | + |
| 230 | +Refer to [Testing against servers that do not support sessions](#testing-against-servers-that-do-not-support-sessions) |
| 231 | +and configure a `MongoClient` with default options. |
| 232 | + |
| 233 | +- Create a new explicit session by calling `startSession` (this MUST NOT error) |
| 234 | +- Attempt to send a read command to the server (e.g., `findOne`) with the explicit session passed in |
| 235 | +- Assert that a client-side error is generated indicating that sessions are not supported |
| 236 | +- Attempt to send a write command to the server (e.g., `insertOne`) with the explicit session passed in |
| 237 | +- Assert that a client-side error is generated indicating that sessions are not supported |
| 238 | + |
| 239 | +### 20. Drivers do not gossip `$clusterTime` on SDAM commands. |
| 240 | + |
| 241 | +- Skip this test when connected to a deployment that does not support cluster times |
| 242 | +- Create a client, C1, directly connected to a writable server and a small heartbeatFrequencyMS |
| 243 | + - `c1 = MongoClient(directConnection=True, heartbeatFrequencyMS=10)` |
| 244 | +- Run a ping command using C1 and record the `$clusterTime` in the response, as `clusterTime`. |
| 245 | + - `clusterTime = c1.admin.command({"ping": 1})["$clusterTime"]` |
| 246 | +- Using a separate client, C2, run an insert to advance the cluster time |
| 247 | + - `c2.test.test.insert_one({"advance": "$clusterTime"})` |
| 248 | +- Next, wait until the client C1 processes the next pair of SDAM heartbeat started + succeeded events. |
| 249 | + - If possible, assert the SDAM heartbeats do not send `$clusterTime` |
| 250 | +- Run a ping command using C1 and assert that `$clusterTime` sent is the same as the `clusterTime` recorded earlier. |
| 251 | + This assertion proves that C1's `$clusterTime` was not advanced by gossiping through SDAM. |
| 252 | + |
| 253 | +## Changelog |
| 254 | + |
| 255 | +- 2025-02-24: Test drivers do not gossip $clusterTime on SDAM. |
| 256 | +- 2024-05-08: Migrated from reStructuredText to Markdown. |
| 257 | +- 2019-05-15: Initial version. |
| 258 | +- 2021-06-15: Added snapshot-session tests. Introduced legacy and unified folders. |
| 259 | +- 2021-07-30: Use numbering for prose test |
| 260 | +- 2022-02-11: Convert legacy tests to unified format |
| 261 | +- 2022-06-13: Relocate prose test from spec document and apply new ordering |
| 262 | +- 2023-02-24: Fix formatting and add new prose tests 18 and 19 |
0 commit comments