Skip to content

Commit

Permalink
Raise an actionable error message when retryWrites fails due to
Browse files Browse the repository at this point in the history
using an unsupported storage engine

JAVA-3374
  • Loading branch information
jstewart148 committed Aug 12, 2019
1 parent 4e5fefa commit e45c8f3
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 7 deletions.
33 changes: 32 additions & 1 deletion .evergreen/.evg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ functions:
params:
script: |
${PREPARE_SHELL}
MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} AUTH=${AUTH} SSL=${SSL} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} AUTH=${AUTH} SSL=${SSL} STORAGE_ENGINE=${STORAGE_ENGINE} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
# run-orchestration generates expansion file with the MONGODB_URI for the cluster
- command: expansions.update
params:
Expand Down Expand Up @@ -263,6 +263,16 @@ functions:
# DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
PROJECT_DIRECTORY=${PROJECT_DIRECTORY} JDK=${JDK} MONGODB_URI=${gssapi_auth_mongodb_uri} KDC=${gssapi_auth_kdc} REALM=${gssapi_auth_realm} KEYTAB_BASE64=${gssapi_auth_keytab_base64} .evergreen/run-gssapi-auth-test.sh
"run mmapv1 storage test":
- command: shell.exec
type: test
params:
silent: true
working_dir: "src"
script: |
${PREPARE_SHELL}
PROJECT_DIRECTORY=${PROJECT_DIRECTORY} JDK=${JDK} TOPOLOGY=${TOPOLOGY} STORAGE_ENGINE=${STORAGE_ENGINE} MONGODB_URI="${MONGODB_URI}" .evergreen/run-mmapv1-storage-test.sh
"run atlas test":
- command: shell.exec
type: test
Expand Down Expand Up @@ -433,6 +443,12 @@ tasks:
- func: "bootstrap mongo-orchestration"
- func: "run perf tests"
- func: "send dashboard data"

- name: "mmapv1-storage-test"
commands:
- func: "bootstrap mongo-orchestration"
- func: "run mmapv1 storage test"

axes:
- id: version
display_name: MongoDB Version
Expand Down Expand Up @@ -561,6 +577,15 @@ axes:
variables:
JDK: "jdk6"

# Choice of MongoDB storage engine
- id: storage-engine
display_name: Storage
values:
- id: mmapv1
display_name: MMAPv1
variables:
STORAGE_ENGINE: "mmapv1"

buildvariants:

# Test packaging and other release related routines
Expand Down Expand Up @@ -688,3 +713,9 @@ buildvariants:
run_on: ubuntu1804-test
tasks:
- name: "publish-snapshot"

- matrix_name: "tests-storage-engines"
matrix_spec: { auth: "noauth", ssl: "nossl", jdk: "jdk8", os: "linux", version: ["3.6", "4.0"], topology: ["replicaset", "sharded-cluster"], storage-engine: "mmapv1" }
display_name: "${version} Storage ${storage-engine} ${jdk} ${os} ${topology}"
tasks:
- name: "mmapv1-storage-test"
27 changes: 27 additions & 0 deletions .evergreen/run-mmapv1-storage-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

set -o errexit # Exit the script with error if any of the commands fail

# Supported/used environment variables:
# MONGODB_URI Set the URI, including username/password to use to connect to the server via PLAIN authentication mechanism
# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java
# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9"

JDK=${JDK:-jdk}

############################################
# Main Program #
############################################

echo "Running MMAPv1 Storage Test"

echo "Compiling java driver with jdk9"

# We always compile with the latest version of java
export JAVA_HOME="/opt/java/jdk9"

echo "Running tests with ${JDK}"
./gradlew -version
./gradlew -PjdkHome=/opt/java/${JDK} --stacktrace --info \
-Dorg.mongodb.test.uri=${MONGODB_URI} \
-Dtest.single=RetryableWritesProseTest driver-sync:test driver-async:test
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.async.client;

import com.mongodb.MongoClientException;
import com.mongodb.MongoException;
import com.mongodb.async.FutureResultCallback;
import com.mongodb.client.test.CollectionHelper;
import org.bson.Document;
import org.bson.codecs.DocumentCodec;
import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

import static com.mongodb.ClusterFixture.getServerStatus;
import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet;
import static com.mongodb.ClusterFixture.isSharded;
import static com.mongodb.ClusterFixture.serverVersionAtLeast;
import static com.mongodb.ClusterFixture.serverVersionLessThan;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;

public class RetryableWritesProseTest extends DatabaseTestCase {
private CollectionHelper<Document> collectionHelper;

@Before
@Override
public void setUp() {
assumeTrue(canRunTests());
super.setUp();

collectionHelper = new CollectionHelper<Document>(new DocumentCodec(), collection.getNamespace());
collectionHelper.create();
}

@Test
public void testRetryWritesWithInsertOneAgainstMMAPv1RaisesError() {
boolean exceptionFound = false;

try {
FutureResultCallback<Void> callback = new FutureResultCallback<Void>();
collection.insertOne(Document.parse("{ x : 1 }"), callback);
futureResult(callback);
} catch (MongoClientException e) {
assertTrue(e.getMessage().equals("This MongoDB deployment does not support retryable writes. "
+ "Please add retryWrites=false to your connection string."));
assertTrue(((MongoException) e.getCause()).getCode() == 20);
assertTrue(e.getCause().getMessage().contains("Transaction numbers"));
exceptionFound = true;
}
assertTrue(exceptionFound);
}

@Test
public void testRetryWritesWithFindOneAndDeleteAgainstMMAPv1RaisesError() {
boolean exceptionFound = false;

try {
FutureResultCallback<Document> callback = new FutureResultCallback<Document>();
collection.findOneAndDelete(Document.parse("{ x : 1 }"), callback);
futureResult(callback);
} catch (MongoClientException e) {
assertTrue(e.getMessage().equals("This MongoDB deployment does not support retryable writes. "
+ "Please add retryWrites=false to your connection string."));
assertTrue(((MongoException) e.getCause()).getCode() == 20);
assertTrue(e.getCause().getMessage().contains("Transaction numbers"));
exceptionFound = true;
}
assertTrue(exceptionFound);
}

private boolean canRunTests() {
Document storageEngine = (Document) getServerStatus().get("storageEngine");

return ((isSharded() || isDiscoverableReplicaSet())
&& storageEngine != null && storageEngine.get("name").equals("mmapv1")
&& serverVersionAtLeast(3, 6) && serverVersionLessThan(4, 1));
}

private <T> T futureResult(final FutureResultCallback<T> callback) {
try {
return callback.get(30, TimeUnit.SECONDS);
} catch (Throwable t) {
throw MongoException.fromThrowable(t);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.mongodb.operation;

import com.mongodb.Function;
import com.mongodb.MongoClientException;
import com.mongodb.MongoCommandException;
import com.mongodb.MongoException;
import com.mongodb.MongoNodeIsRecoveringException;
Expand Down Expand Up @@ -714,7 +715,7 @@ public R call(final ConnectionSource source, final Connection connection) {
if (isRetryWritesEnabled(command)) {
logUnableToRetry(command.getFirstKey(), e);
}
throw exception;
throw transformWriteException(exception);
}
} finally {
connection.release();
Expand Down Expand Up @@ -824,7 +825,8 @@ private void checkRetryableException(final Throwable originalError, final Single
if (isRetryWritesEnabled(command)) {
logUnableToRetry(command.getFirstKey(), originalError);
}
releasingCallback.onResult(null, originalError);
releasingCallback.onResult(null, originalError instanceof MongoException
? transformWriteException((MongoException) originalError) : originalError);
} else {
oldConnection.release();
oldSource.release();
Expand Down Expand Up @@ -1009,6 +1011,14 @@ static void logUnableToRetry(final String operation, final Throwable originalErr
}
}

static MongoException transformWriteException(final MongoException exception) {
if (exception.getCode() == 20 && exception.getMessage().contains("Transaction numbers")) {
return new MongoClientException("This MongoDB deployment does not support retryable writes. "
+ "Please add retryWrites=false to your connection string.", exception);
}
return exception;
}

private CommandOperationHelper() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import static com.mongodb.operation.CommandOperationHelper.logRetryExecute;
import static com.mongodb.operation.CommandOperationHelper.logUnableToRetry;
import static com.mongodb.operation.CommandOperationHelper.shouldAttemptToRetryWrite;
import static com.mongodb.operation.CommandOperationHelper.transformWriteException;
import static com.mongodb.operation.OperationHelper.AsyncCallableWithConnection;
import static com.mongodb.operation.OperationHelper.AsyncCallableWithConnectionAndSource;
import static com.mongodb.operation.OperationHelper.CallableWithConnectionAndSource;
Expand Down Expand Up @@ -290,7 +291,7 @@ private BulkWriteResult executeBulkWriteBatch(final WriteBinding binding, final
if (originalBatch.getRetryWrites()) {
logUnableToRetry(originalBatch.getPayload().getPayloadType().toString(), exception);
}
throw exception;
throw transformWriteException(exception);
} else {
return retryExecuteBatches(binding, currentBatch, exception);
}
Expand Down Expand Up @@ -487,7 +488,7 @@ public void onResult(final BsonDocument result, final Throwable t) {
addBatchResult((BsonDocument) ((MongoWriteConcernWithResponseException) t).getResponse(), binding, connection,
batch, retryWrites, callback);
} else {
callback.onResult(null, t);
callback.onResult(null, t instanceof MongoException ? transformWriteException((MongoException) t) : t);
}
} else {
retryExecuteBatchesAsync(binding, batch, t, callback.releaseConnectionAndGetWrapped());
Expand Down Expand Up @@ -521,7 +522,7 @@ private void addBatchResult(final BsonDocument result, final AsyncWriteBinding b
if (retryWrites) {
logUnableToRetry(batch.getPayload().getPayloadType().toString(), batch.getError());
}
callback.onResult(null, batch.getError());
callback.onResult(null, transformWriteException(batch.getError()));
} else {
callback.onResult(batch.getResult(), null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public static boolean serverVersionLessThan(final List<Integer> versionArray) {
}

public static boolean serverVersionLessThan(final int majorVersion, final int minorVersion) {
return serverVersionAtLeast(asList(majorVersion, minorVersion, 0));
return serverVersionLessThan(asList(majorVersion, minorVersion, 0));
}

public static boolean serverVersionLessThan(final String versionString) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.client;

import com.mongodb.MongoClientException;
import com.mongodb.MongoException;
import org.bson.Document;
import org.junit.Before;
import org.junit.Test;

import static com.mongodb.ClusterFixture.getServerStatus;
import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet;
import static com.mongodb.ClusterFixture.isSharded;
import static com.mongodb.ClusterFixture.serverVersionAtLeast;
import static com.mongodb.ClusterFixture.serverVersionLessThan;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;

public class RetryableWritesProseTest extends DatabaseTestCase {

@Before
@Override
public void setUp() {
assumeTrue(canRunTests());
super.setUp();
}

@Test
public void testRetryWritesWithInsertOneAgainstMMAPv1RaisesError() {
boolean exceptionFound = false;

try {
collection.insertOne(Document.parse("{x: 1}"));
} catch (MongoClientException e) {
assertTrue(e.getMessage().equals("This MongoDB deployment does not support retryable writes. "
+ "Please add retryWrites=false to your connection string."));
assertTrue(((MongoException) e.getCause()).getCode() == 20);
assertTrue(e.getCause().getMessage().contains("Transaction numbers"));
exceptionFound = true;
}
assertTrue(exceptionFound);
}

@Test
public void testRetryWritesWithFindOneAndDeleteAgainstMMAPv1RaisesError() {
boolean exceptionFound = false;

try {
collection.findOneAndDelete(Document.parse("{x: 1}"));
} catch (MongoClientException e) {
assertTrue(e.getMessage().equals("This MongoDB deployment does not support retryable writes. "
+ "Please add retryWrites=false to your connection string."));
assertTrue(((MongoException) e.getCause()).getCode() == 20);
assertTrue(e.getCause().getMessage().contains("Transaction numbers"));
exceptionFound = true;
}
assertTrue(exceptionFound);
}

private boolean canRunTests() {
Document storageEngine = (Document) getServerStatus().get("storageEngine");

return ((isSharded() || isDiscoverableReplicaSet())
&& storageEngine != null && storageEngine.get("name").equals("mmapv1")
&& serverVersionAtLeast(3, 6) && serverVersionLessThan(4, 1));
}
}

0 comments on commit e45c8f3

Please sign in to comment.