@@ -8,6 +8,7 @@ package integration
8
8
9
9
import (
10
10
"context"
11
+ "fmt"
11
12
"testing"
12
13
"time"
13
14
@@ -19,16 +20,18 @@ import (
19
20
20
21
func TestSDAMErrorHandling (t * testing.T ) {
21
22
mt := mtest .New (t , noClientOpts )
22
- clientOpts := options .Client ().
23
- ApplyURI (mt .ConnString ()).
24
- SetRetryWrites (false ).
25
- SetPoolMonitor (poolMonitor ).
26
- SetWriteConcern (mtest .MajorityWc )
23
+ baseClientOpts := func () * options.ClientOptions {
24
+ return options .Client ().
25
+ ApplyURI (mt .ConnString ()).
26
+ SetRetryWrites (false ).
27
+ SetPoolMonitor (poolMonitor ).
28
+ SetWriteConcern (mtest .MajorityWc )
29
+ }
27
30
baseMtOpts := func () * mtest.Options {
28
31
mtOpts := mtest .NewOptions ().
29
32
Topologies (mtest .ReplicaSet ). // Don't run on sharded clusters to avoid complexity of sharded failpoints.
30
33
MinServerVersion ("4.0" ). // 4.0+ is required to use failpoints on replica sets.
31
- ClientOptions (clientOpts )
34
+ ClientOptions (baseClientOpts () )
32
35
33
36
if mt .TopologyKind () == mtest .Sharded {
34
37
// Pin to a single mongos because the tests use failpoints.
@@ -173,5 +176,97 @@ func TestSDAMErrorHandling(t *testing.T) {
173
176
assert .False (mt , isPoolCleared (), "expected pool to not be cleared but was" )
174
177
})
175
178
})
179
+ mt .RunOpts ("server errors" , noClientOpts , func (mt * mtest.T ) {
180
+ // Integration tests for the SDAM error handling code path for errors in server response documents. These
181
+ // errors can be part of the top-level document in ok:0 responses or in a nested writeConcernError document.
182
+
183
+ // On 4.4, some state change errors include a topologyVersion field. Because we're triggering these errors
184
+ // via failCommand, the topologyVersion does not actually change as it would in an actual state change.
185
+ // This causes the SDAM error handling code path to think we've already handled this state change and
186
+ // ignore the error because it's stale. To avoid this altogether, we cap the test to <= 4.2.
187
+ serverErrorsMtOpts := baseMtOpts ().
188
+ MinServerVersion ("4.0" ). // failCommand support
189
+ MaxServerVersion ("4.2" ).
190
+ ClientOptions (baseClientOpts ().SetRetryWrites (false ))
191
+
192
+ testCases := []struct {
193
+ name string
194
+ errorCode int32
195
+
196
+ // For shutdown errors, the pool is always cleared. For non-shutdown errors, the pool is only cleared
197
+ // for pre-4.2 servers.
198
+ isShutdownError bool
199
+ }{
200
+ // "node is recovering" errors
201
+ {"InterruptedAtShutdown" , 11600 , true },
202
+ {"InterruptedDueToReplStateChange, not shutdown" , 11602 , false },
203
+ {"NotMasterOrSecondary" , 13436 , false },
204
+ {"PrimarySteppedDown" , 189 , false },
205
+ {"ShutdownInProgress" , 91 , true },
206
+
207
+ // "not master" errors
208
+ {"NotMaster" , 10107 , false },
209
+ {"NotMasterNoSlaveOk" , 13435 , false },
210
+ }
211
+ for _ , tc := range testCases {
212
+ mt .RunOpts (fmt .Sprintf ("command error - %s" , tc .name ), serverErrorsMtOpts , func (mt * mtest.T ) {
213
+ clearPoolChan ()
214
+
215
+ // Cause the next insert to fail with an ok:0 response.
216
+ fp := mtest.FailPoint {
217
+ ConfigureFailPoint : "failCommand" ,
218
+ Mode : mtest.FailPointMode {
219
+ Times : 1 ,
220
+ },
221
+ Data : mtest.FailPointData {
222
+ FailCommands : []string {"insert" },
223
+ ErrorCode : tc .errorCode ,
224
+ },
225
+ }
226
+ mt .SetFailPoint (fp )
227
+
228
+ runServerErrorsTest (mt , tc .isShutdownError )
229
+ })
230
+ mt .RunOpts (fmt .Sprintf ("write concern error - %s" , tc .name ), serverErrorsMtOpts , func (mt * mtest.T ) {
231
+ clearPoolChan ()
232
+
233
+ // Cause the next insert to fail with a write concern error.
234
+ fp := mtest.FailPoint {
235
+ ConfigureFailPoint : "failCommand" ,
236
+ Mode : mtest.FailPointMode {
237
+ Times : 1 ,
238
+ },
239
+ Data : mtest.FailPointData {
240
+ FailCommands : []string {"insert" },
241
+ WriteConcernError : & mtest.WriteConcernErrorData {
242
+ Code : tc .errorCode ,
243
+ },
244
+ },
245
+ }
246
+ mt .SetFailPoint (fp )
247
+
248
+ runServerErrorsTest (mt , tc .isShutdownError )
249
+ })
250
+ }
251
+ })
176
252
})
177
253
}
254
+
255
+ func runServerErrorsTest (mt * mtest.T , isShutdownError bool ) {
256
+ mt .Helper ()
257
+
258
+ _ , err := mt .Coll .InsertOne (mtest .Background , bson.D {{"x" , 1 }})
259
+ assert .NotNil (mt , err , "expected InsertOne error, got nil" )
260
+
261
+ // The pool should always be cleared for shutdown errors, regardless of server version.
262
+ if isShutdownError {
263
+ assert .True (mt , isPoolCleared (), "expected pool to be cleared, but was not" )
264
+ return
265
+ }
266
+
267
+ // For non-shutdown errors, the pool is only cleared if the error is from a pre-4.2 server.
268
+ wantCleared := mtest .CompareServerVersions (mt .ServerVersion (), "4.2" ) < 0
269
+ gotCleared := isPoolCleared ()
270
+ assert .Equal (mt , wantCleared , gotCleared , "expected pool to be cleared: %v; pool was cleared: %v" ,
271
+ wantCleared , gotCleared )
272
+ }
0 commit comments