Skip to content

Commit 758b89e

Browse files
committed
Implement prose backpressure retryable writes tests
The relevant spec changes: - https://github.com/mongodb/specifications/blame/ba14b6bdc1dc695aa9cc20ccf9378592da1b2329/source/retryable-writes/tests/README.md#L265-L418 - See also https://jira.mongodb.org/browse/DRIVERS-3432 for the required fixes for "Test 3 Case 3" JAVA-6055
1 parent 4537f37 commit 758b89e

File tree

2 files changed

+212
-0
lines changed

2 files changed

+212
-0
lines changed

driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableWritesProseTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient;
2020
import org.bson.Document;
21+
import org.junit.jupiter.api.Disabled;
2122
import org.junit.jupiter.api.Test;
2223

2324
/**
@@ -67,4 +68,36 @@ void retriesOnSameMongosWhenAnotherNotAvailable() {
6768
SyncMongoClient::new,
6869
mongoCollection -> mongoCollection.insertOne(new Document()), "insert", true);
6970
}
71+
72+
/**
73+
* <a href="https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md#case-1-test-that-drivers-return-the-correct-error-when-receiving-only-errors-without-nowritesperformed">
74+
* 6. Test error propagation after encountering multiple errors.
75+
* Case 1: Test that drivers return the correct error when receiving only errors without NoWritesPerformed</a>.
76+
*/
77+
@Test
78+
@Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-6055")
79+
void errorPropagationAfterEncounteringMultipleErrorsCase1() throws Exception {
80+
com.mongodb.client.RetryableWritesProseTest.errorPropagationAfterEncounteringMultipleErrorsCase1(SyncMongoClient::new);
81+
}
82+
83+
/**
84+
* <a href="https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md#case-2-test-that-drivers-return-the-correct-error-when-receiving-only-errors-with-nowritesperformed">
85+
* 6. Test error propagation after encountering multiple errors.
86+
* Case 2: Test that drivers return the correct error when receiving only errors with NoWritesPerformed</a>.
87+
*/
88+
@Test
89+
void errorPropagationAfterEncounteringMultipleErrorsCase2() throws Exception {
90+
com.mongodb.client.RetryableWritesProseTest.errorPropagationAfterEncounteringMultipleErrorsCase2(SyncMongoClient::new);
91+
}
92+
93+
/**
94+
* <a href="https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md#case-3-test-that-drivers-return-the-correct-error-when-receiving-some-errors-with-nowritesperformed-and-some-without-nowritesperformed">
95+
* 6. Test error propagation after encountering multiple errors.
96+
* Case 3: Test that drivers return the correct error when receiving some errors with NoWritesPerformed and some without NoWritesPerformed</a>.
97+
*/
98+
@Test
99+
@Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-6055, fails on MongoDB 6.0")
100+
void errorPropagationAfterEncounteringMultipleErrorsCase3() throws Exception {
101+
com.mongodb.client.RetryableWritesProseTest.errorPropagationAfterEncounteringMultipleErrorsCase3(SyncMongoClient::new);
102+
}
70103
}

driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.mongodb.ConnectionString;
2020
import com.mongodb.Function;
2121
import com.mongodb.MongoClientSettings;
22+
import com.mongodb.MongoException;
2223
import com.mongodb.MongoServerException;
2324
import com.mongodb.MongoWriteConcernException;
2425
import com.mongodb.ServerAddress;
@@ -34,9 +35,11 @@
3435
import com.mongodb.internal.connection.TestCommandListener;
3536
import com.mongodb.internal.connection.TestConnectionPoolListener;
3637
import com.mongodb.internal.event.ConfigureFailPointCommandListener;
38+
import com.mongodb.lang.Nullable;
3739
import org.bson.BsonDocument;
3840
import org.bson.BsonInt32;
3941
import org.bson.Document;
42+
import org.junit.jupiter.api.Disabled;
4043
import org.junit.jupiter.api.Test;
4144

4245
import java.util.HashSet;
@@ -55,6 +58,8 @@
5558
import static com.mongodb.ClusterFixture.isSharded;
5659
import static com.mongodb.ClusterFixture.isStandalone;
5760
import static com.mongodb.ClusterFixture.serverVersionAtLeast;
61+
import static com.mongodb.MongoException.RETRYABLE_ERROR_LABEL;
62+
import static com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL;
5863
import static com.mongodb.client.Fixture.getDefaultDatabaseName;
5964
import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder;
6065
import static com.mongodb.client.Fixture.getMultiMongosMongoClientSettingsBuilder;
@@ -66,6 +71,7 @@
6671
import static java.util.Collections.singletonList;
6772
import static java.util.concurrent.TimeUnit.SECONDS;
6873
import static org.junit.jupiter.api.Assertions.assertEquals;
74+
import static org.junit.jupiter.api.Assertions.assertFalse;
6975
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
7076
import static org.junit.jupiter.api.Assertions.assertThrows;
7177
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -333,6 +339,179 @@ public static <R> void retriesOnSameMongosWhenAnotherNotAvailable(
333339
}
334340
}
335341

342+
/**
343+
* <a href="https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md#case-1-test-that-drivers-return-the-correct-error-when-receiving-only-errors-without-nowritesperformed">
344+
* 6. Test error propagation after encountering multiple errors.
345+
* Case 1: Test that drivers return the correct error when receiving only errors without NoWritesPerformed</a>.
346+
*/
347+
@Test
348+
@Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-6055")
349+
void errorPropagationAfterEncounteringMultipleErrorsCase1() throws Exception {
350+
errorPropagationAfterEncounteringMultipleErrorsCase1(MongoClients::create);
351+
}
352+
353+
public static void errorPropagationAfterEncounteringMultipleErrorsCase1(final Function<MongoClientSettings, MongoClient> clientCreator)
354+
throws Exception {
355+
BsonDocument configureFailPoint = BsonDocument.parse(
356+
"{\n"
357+
+ " configureFailPoint: 'failCommand',\n"
358+
+ " mode: {'times': 1},\n"
359+
+ " data: {\n"
360+
+ " failCommands: ['insert'],\n"
361+
+ " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "'],\n"
362+
+ " errorCode: 91\n"
363+
+ " }\n"
364+
+ "}\n");
365+
BsonDocument configureFailPointFromListener = BsonDocument.parse(
366+
"{\n"
367+
+ " configureFailPoint: \"failCommand\",\n"
368+
+ " mode: 'alwaysOn',\n"
369+
+ " data: {\n"
370+
+ " failCommands: ['insert'],\n"
371+
+ " errorCode: 10107,\n"
372+
+ " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "']\n"
373+
+ " }\n"
374+
+ "}\n");
375+
Predicate<CommandEvent> configureFailPointEventMatcher = event -> {
376+
if (event instanceof CommandFailedEvent) {
377+
CommandFailedEvent commandFailedEvent = (CommandFailedEvent) event;
378+
MongoException cause = assertInstanceOf(MongoException.class, commandFailedEvent.getThrowable());
379+
return cause.getCode() == 91;
380+
}
381+
return false;
382+
};
383+
errorPropagationAfterEncounteringMultipleErrors(
384+
clientCreator,
385+
configureFailPoint,
386+
configureFailPointFromListener,
387+
configureFailPointEventMatcher,
388+
10107,
389+
null);
390+
}
391+
392+
/**
393+
* <a href="https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md#case-2-test-that-drivers-return-the-correct-error-when-receiving-only-errors-with-nowritesperformed">
394+
* 6. Test error propagation after encountering multiple errors.
395+
* Case 2: Test that drivers return the correct error when receiving only errors with NoWritesPerformed</a>.
396+
*/
397+
@Test
398+
void errorPropagationAfterEncounteringMultipleErrorsCase2() throws Exception {
399+
errorPropagationAfterEncounteringMultipleErrorsCase2(MongoClients::create);
400+
}
401+
402+
public static void errorPropagationAfterEncounteringMultipleErrorsCase2(final Function<MongoClientSettings, MongoClient> clientCreator)
403+
throws Exception {
404+
BsonDocument configureFailPoint = BsonDocument.parse(
405+
"{\n"
406+
+ " configureFailPoint: 'failCommand',\n"
407+
+ " mode: {'times': 1},\n"
408+
+ " data: {\n"
409+
+ " failCommands: ['insert'],\n"
410+
+ " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL
411+
+ "', '" + NO_WRITES_PERFORMED_ERROR_LABEL + "'],\n"
412+
+ " errorCode: 91\n"
413+
+ " }\n"
414+
+ "}\n");
415+
BsonDocument configureFailPointFromListener = BsonDocument.parse(
416+
"{\n"
417+
+ " configureFailPoint: \"failCommand\",\n"
418+
+ " mode: 'alwaysOn',\n"
419+
+ " data: {\n"
420+
+ " failCommands: ['insert'],\n"
421+
+ " errorCode: 10107,\n"
422+
+ " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL
423+
+ "', '" + NO_WRITES_PERFORMED_ERROR_LABEL + "'],\n"
424+
+ " }\n"
425+
+ "}\n");
426+
Predicate<CommandEvent> configureFailPointEventMatcher = event -> {
427+
if (event instanceof CommandFailedEvent) {
428+
CommandFailedEvent commandFailedEvent = (CommandFailedEvent) event;
429+
MongoException cause = assertInstanceOf(MongoException.class, commandFailedEvent.getThrowable());
430+
return cause.getCode() == 91;
431+
}
432+
return false;
433+
};
434+
errorPropagationAfterEncounteringMultipleErrors(
435+
clientCreator,
436+
configureFailPoint,
437+
configureFailPointFromListener,
438+
configureFailPointEventMatcher,
439+
91,
440+
null);
441+
}
442+
443+
/**
444+
* <a href="https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md#case-3-test-that-drivers-return-the-correct-error-when-receiving-some-errors-with-nowritesperformed-and-some-without-nowritesperformed">
445+
* 6. Test error propagation after encountering multiple errors.
446+
* Case 3: Test that drivers return the correct error when receiving some errors with NoWritesPerformed and some without NoWritesPerformed</a>.
447+
*/
448+
@Test
449+
@Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-6055, fails on MongoDB 6.0")
450+
void errorPropagationAfterEncounteringMultipleErrorsCase3() throws Exception {
451+
errorPropagationAfterEncounteringMultipleErrorsCase3(MongoClients::create);
452+
}
453+
454+
public static void errorPropagationAfterEncounteringMultipleErrorsCase3(final Function<MongoClientSettings, MongoClient> clientCreator)
455+
throws Exception {
456+
BsonDocument configureFailPoint = BsonDocument.parse(
457+
"{\n"
458+
+ " configureFailPoint: 'failCommand',\n"
459+
+ " mode: {'times': 1},\n"
460+
+ " data: {\n"
461+
+ " failCommands: ['insert'],\n"
462+
+ " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "'],\n"
463+
+ " errorCode: 91\n"
464+
+ " }\n"
465+
+ "}\n");
466+
BsonDocument configureFailPointFromListener = BsonDocument.parse(
467+
"{\n"
468+
+ " configureFailPoint: \"failCommand\",\n"
469+
+ " mode: 'alwaysOn',\n"
470+
+ " data: {\n"
471+
+ " failCommands: ['insert'],\n"
472+
+ " errorCode: 91,\n"
473+
+ " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL
474+
+ "', '" + NO_WRITES_PERFORMED_ERROR_LABEL + "'],\n"
475+
+ " }\n"
476+
+ "}\n");
477+
Predicate<CommandEvent> configureFailPointEventMatcher = event -> event instanceof CommandFailedEvent;
478+
errorPropagationAfterEncounteringMultipleErrors(
479+
clientCreator,
480+
configureFailPoint,
481+
configureFailPointFromListener,
482+
configureFailPointEventMatcher,
483+
91,
484+
NO_WRITES_PERFORMED_ERROR_LABEL);
485+
}
486+
487+
/**
488+
* @param unexpectedErrorLabel {@code null} means there is no expectation.
489+
*/
490+
private static void errorPropagationAfterEncounteringMultipleErrors(
491+
final Function<MongoClientSettings, MongoClient> clientCreator,
492+
final BsonDocument configureFailPoint,
493+
final BsonDocument configureFailPointFromListener,
494+
final Predicate<CommandEvent> configureFailPointEventMatcher,
495+
final int expectedErrorCode,
496+
@Nullable final String unexpectedErrorLabel) throws Exception {
497+
assumeTrue(serverVersionAtLeast(6, 0));
498+
assumeTrue(isDiscoverableReplicaSet());
499+
try (ConfigureFailPointCommandListener commandListener = new ConfigureFailPointCommandListener(
500+
configureFailPointFromListener, getPrimary(), configureFailPointEventMatcher);
501+
MongoClient client = clientCreator.apply(getMongoClientSettingsBuilder()
502+
.retryWrites(true)
503+
.addCommandListener(commandListener)
504+
.build());
505+
FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) {
506+
MongoCollection<Document> collection = dropAndGetCollection("errorPropagationAfterEncounteringMultipleErrors", client);
507+
MongoException e = assertThrows(MongoException.class, () -> collection.insertOne(new Document()));
508+
assertEquals(expectedErrorCode, e.getCode());
509+
if (unexpectedErrorLabel != null) {
510+
assertFalse(e.hasErrorLabel(unexpectedErrorLabel));
511+
}
512+
}
513+
}
514+
336515
private static MongoCollection<Document> dropAndGetCollection(final String name, final MongoClient client) {
337516
MongoCollection<Document> result = client.getDatabase(getDefaultDatabaseName()).getCollection(name);
338517
result.drop();

0 commit comments

Comments
 (0)