summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJudah Schvimer <judah@mongodb.com>2017-10-30 17:41:22 -0400
committerJudah Schvimer <judah@mongodb.com>2017-10-31 18:24:51 -0400
commitced3d3341a2aac6a11297f7dcc2c3c6d2c0e3bec (patch)
treedf25a0edd5cb8d62bb238ac216a57728222a18cb
parent6709d704407f5c8aa5d7c1070ffe9d0671707334 (diff)
downloadmongo-r3.6.0-rc2.tar.gz
SERVER-31507 add option to specify oplog application mode in applyOpsr3.6.0-rc2
-rw-r--r--jstests/noPassthrough/apply_ops_mode.js92
-rw-r--r--src/mongo/db/repl/apply_ops.cpp41
2 files changed, 124 insertions, 9 deletions
diff --git a/jstests/noPassthrough/apply_ops_mode.js b/jstests/noPassthrough/apply_ops_mode.js
new file mode 100644
index 00000000000..f9bfb5a0017
--- /dev/null
+++ b/jstests/noPassthrough/apply_ops_mode.js
@@ -0,0 +1,92 @@
+/**
+ * Tests that applyOps correctly respects the 'oplogApplicationMode' and 'alwaysUpsert' flags.
+ * 'alwaysUpsert' defaults to true and 'oplogApplicationMode' defaults to 'ApplyOps'. We test
+ * that these default values do not lead to command failure.
+ */
+
+(function() {
+ 'use strict';
+
+ var standalone = MongoRunner.runMongod();
+ var db = standalone.getDB("test");
+
+ var coll = db.getCollection("apply_ops_mode1");
+ coll.drop();
+ assert.writeOK(coll.insert({_id: 1}));
+
+ // ------------ Testing normal updates ---------------
+
+ var id = ObjectId();
+ var updateOp = {op: 'u', ns: coll.getFullName(), o: {_id: id, x: 1}, o2: {_id: id}};
+ assert.commandFailed(db.adminCommand({applyOps: [updateOp], alwaysUpsert: false}));
+ assert.eq(coll.count({x: 1}), 0);
+
+ // Test that 'InitialSync' does not override 'alwaysUpsert: false'.
+ assert.commandFailed(db.adminCommand(
+ {applyOps: [updateOp], alwaysUpsert: false, oplogApplicationMode: "InitialSync"}));
+ assert.eq(coll.count({x: 1}), 0);
+
+ // Test parsing failure.
+ assert.commandFailedWithCode(
+ db.adminCommand({applyOps: [updateOp], oplogApplicationMode: "BadMode"}),
+ ErrorCodes.FailedToParse);
+ assert.commandFailedWithCode(db.adminCommand({applyOps: [updateOp], oplogApplicationMode: 5}),
+ ErrorCodes.TypeMismatch);
+
+ // Test default succeeds.
+ assert.commandWorked(db.adminCommand({applyOps: [updateOp]}));
+ assert.eq(coll.count({x: 1}), 1);
+
+ // Use new collection to make logs cleaner.
+ coll = db.getCollection("apply_ops_mode2");
+ coll.drop();
+ updateOp.ns = coll.getFullName();
+ assert.writeOK(coll.insert({_id: 1}));
+
+ // Test default succeeds in 'InitialSync' mode.
+ assert.commandWorked(
+ db.adminCommand({applyOps: [updateOp], oplogApplicationMode: "InitialSync"}));
+ assert.eq(coll.count({x: 1}), 1);
+
+ // ------------ Testing fCV updates ---------------
+
+ var adminDB = db.getSiblingDB("admin");
+ const systemVersionColl = adminDB.getCollection("system.version");
+
+ updateOp = {
+ op: 'u',
+ ns: systemVersionColl.getFullName(),
+ o: {_id: "featureCompatibilityVersion", version: "3.4"},
+ o2: {_id: "featureCompatibilityVersion"}
+ };
+ assert.commandFailed(
+ db.adminCommand({applyOps: [updateOp], oplogApplicationMode: "InitialSync"}));
+
+ assert.commandWorked(db.adminCommand({applyOps: [updateOp], oplogApplicationMode: "ApplyOps"}));
+
+ // Test default succeeds.
+ updateOp.o.targetVersion = "3.6";
+ assert.commandWorked(db.adminCommand({
+ applyOps: [updateOp],
+ }));
+
+ // ------------ Testing commands on the fCV collection ---------------
+
+ var collModOp = {
+ op: 'c',
+ ns: systemVersionColl.getDB() + ".$cmd",
+ o: {collMod: systemVersionColl.getName(), validationLevel: "off"},
+ };
+ assert.commandFailed(
+ db.adminCommand({applyOps: [collModOp], oplogApplicationMode: "InitialSync"}));
+
+ assert.commandWorked(
+ db.adminCommand({applyOps: [collModOp], oplogApplicationMode: "ApplyOps"}));
+
+ // Test default succeeds.
+ collModOp.o.usePowerOf2Sizes = true;
+ assert.commandWorked(db.adminCommand({
+ applyOps: [collModOp],
+ }));
+
+})();
diff --git a/src/mongo/db/repl/apply_ops.cpp b/src/mongo/db/repl/apply_ops.cpp
index 3d17a8c5291..f6651330cbe 100644
--- a/src/mongo/db/repl/apply_ops.cpp
+++ b/src/mongo/db/repl/apply_ops.cpp
@@ -58,6 +58,7 @@ namespace mongo {
namespace {
const auto kPreconditionFieldName = "preCondition"_sd;
+const auto kOplogApplicationModeFieldName = "oplogApplicationMode"_sd;
// If enabled, causes loop in _applyOps() to hang after applying current operation.
MONGO_FP_DECLARE(applyOpsPauseBetweenOperations);
@@ -115,6 +116,30 @@ Status _applyOps(OperationContext* opCtx,
applyOpCmd.hasField("alwaysUpsert") ? applyOpCmd["alwaysUpsert"].trueValue() : true;
const bool haveWrappingWUOW = opCtx->lockState()->inAWriteUnitOfWork();
+ // TODO (SERVER-31384): This code is run when applying 'applyOps' oplog entries. Pass through
+ // oplog application mode from applyCommand_inlock as default and consider proper behavior
+ // if default differs from oplog entry.
+ repl::OplogApplication::Mode oplogApplicationMode = repl::OplogApplication::Mode::kApplyOps;
+ std::string oplogApplicationModeString;
+ auto status = bsonExtractStringField(
+ applyOpCmd, kOplogApplicationModeFieldName, &oplogApplicationModeString);
+ if (status.isOK()) {
+ auto modeSW = repl::OplogApplication::parseMode(oplogApplicationModeString);
+ if (!modeSW.isOK()) {
+ return Status(modeSW.getStatus().code(),
+ str::stream() << "Could not parse " << kOplogApplicationModeFieldName
+ << ": "
+ << modeSW.getStatus().reason());
+ }
+ oplogApplicationMode = modeSW.getValue();
+ } else if (status != ErrorCodes::NoSuchKey) {
+ // NoSuchKey means the user did not supply a mode.
+ return Status(status.code(),
+ str::stream() << "Could not parse out " << kOplogApplicationModeFieldName
+ << ": "
+ << status.reason());
+ }
+
while (i.more()) {
BSONElement e = i.next();
const BSONObj& opObj = e.Obj();
@@ -172,17 +197,19 @@ Status _applyOps(OperationContext* opCtx,
OldClientContext ctx(opCtx, nss.ns());
status = repl::applyOperation_inlock(
- opCtx, ctx.db(), opObj, alwaysUpsert, repl::OplogApplication::Mode::kApplyOps);
+ opCtx, ctx.db(), opObj, alwaysUpsert, oplogApplicationMode);
if (!status.isOK())
return status;
} else {
try {
status = writeConflictRetry(
- opCtx, "applyOps", nss.ns(), [opCtx, nss, opObj, opType, alwaysUpsert] {
+ opCtx,
+ "applyOps",
+ nss.ns(),
+ [opCtx, nss, opObj, opType, alwaysUpsert, oplogApplicationMode] {
if (*opType == 'c') {
invariant(opCtx->lockState()->isW());
- return repl::applyCommand_inlock(
- opCtx, opObj, repl::OplogApplication::Mode::kApplyOps);
+ return repl::applyCommand_inlock(opCtx, opObj, oplogApplicationMode);
}
AutoGetCollection autoColl(opCtx, nss, MODE_IX);
@@ -204,11 +231,7 @@ Status _applyOps(OperationContext* opCtx,
if (!nss.isSystemDotIndexes()) {
return repl::applyOperation_inlock(
- opCtx,
- ctx.db(),
- opObj,
- alwaysUpsert,
- repl::OplogApplication::Mode::kApplyOps);
+ opCtx, ctx.db(), opObj, alwaysUpsert, oplogApplicationMode);
}
auto fieldO = opObj["o"];