summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Mulrow <jack.mulrow@mongodb.com>2019-12-12 19:15:45 +0000
committerevergreen <evergreen@mongodb.com>2019-12-12 19:15:45 +0000
commit15d3feea932e89bf6ee7f562c5274853c2bcb11c (patch)
tree4573451f17020e86a5e2065beb455544e9f27a37
parent3eecd38739655c84334cb09d03127479c4dd7a50 (diff)
downloadmongo-15d3feea932e89bf6ee7f562c5274853c2bcb11c.tar.gz
SERVER-43720 Add default read write concern commands to mongos and config server
-rw-r--r--jstests/auth/lib/commands_lib.js20
-rw-r--r--jstests/sharding/read_write_concern_defaults_commands_api.js326
-rw-r--r--src/mongo/db/commands/SConscript2
-rw-r--r--src/mongo/db/commands/rwc_defaults_commands.cpp33
-rw-r--r--src/mongo/db/commands/rwc_defaults_commands.idl6
-rw-r--r--src/mongo/db/read_write_concern_defaults.cpp15
-rw-r--r--src/mongo/db/read_write_concern_defaults.h1
-rw-r--r--src/mongo/db/repl/read_concern_args.cpp22
-rw-r--r--src/mongo/db/repl/read_concern_args.h8
-rw-r--r--src/mongo/db/repl/read_concern_args.idl2
-rw-r--r--src/mongo/db/rw_concern_default.idl1
-rw-r--r--src/mongo/s/commands/SConscript2
-rw-r--r--src/mongo/s/commands/cluster_rwc_defaults_commands.cpp160
-rw-r--r--src/mongo/shell/assert.js2
14 files changed, 574 insertions, 26 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 91eeaefe1da..9c5485aeafa 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -3756,6 +3756,16 @@ var authCommandsLib = {
]
},
{
+ testname: "getDefaultRWConcern",
+ command: {getDefaultRWConcern: 1},
+ skipUnlessReplicaSet: true,
+ testcases: [
+ {runOnDb: adminDbName, roles: roles_all, privileges: []},
+ {runOnDb: firstDbName, roles: {}},
+ {runOnDb: secondDbName, roles: {}}
+ ]
+ },
+ {
testname: "getDiagnosticData",
command: {getDiagnosticData: 1},
testcases: [
@@ -5094,6 +5104,16 @@ var authCommandsLib = {
]
},
{
+ testname: "setDefaultRWConcern",
+ command: {setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}},
+ skipUnlessReplicaSet: true,
+ testcases: [
+ {runOnDb: adminDbName, roles: roles_all, privileges: []},
+ {runOnDb: firstDbName, roles: {}},
+ {runOnDb: secondDbName, roles: {}}
+ ]
+ },
+ {
testname: "setFeatureCompatibilityVersion",
command: {setFeatureCompatibilityVersion: "x"},
testcases: [
diff --git a/jstests/sharding/read_write_concern_defaults_commands_api.js b/jstests/sharding/read_write_concern_defaults_commands_api.js
new file mode 100644
index 00000000000..984443b5842
--- /dev/null
+++ b/jstests/sharding/read_write_concern_defaults_commands_api.js
@@ -0,0 +1,326 @@
+// Tests the basic API of the getDefaultRWConcern and setDefaultRWConcern commands against different
+// topologies.
+//
+// @tags: [requires_fcv_44]
+(function() {
+"use strict";
+
+// Asserts a set/get default RWC command response contains the expected fields. Assumes a default
+// read or write concern has been set previously.
+function verifyResponseFields(res, {expectRC, expectWC}) {
+ // These fields are always set once a read or write concern has been set at least once.
+ const expectedFields = ["epoch", "setTime", "localSetTime"];
+ const unexpectedFields = [];
+
+ if (expectRC) {
+ expectedFields.push("defaultReadConcern");
+ } else {
+ unexpectedFields.push("defaultReadConcern");
+ }
+
+ if (expectWC) {
+ expectedFields.push("defaultWriteConcern");
+ } else {
+ unexpectedFields.push("defaultWriteConcern");
+ }
+
+ assert.hasFields(res, expectedFields);
+ unexpectedFields.forEach(field => {
+ assert(!res.hasOwnProperty(field),
+ `response unexpectedly had field '${field}', res: ${tojson(res)}`);
+ });
+}
+
+function verifyDefaultRWCommandsInvalidInput(conn) {
+ //
+ // Test invalid parameters for getDefaultRWConcern.
+ //
+
+ // Invalid inMemory.
+ assert.commandFailedWithCode(conn.adminCommand({getDefaultRWConcern: 1, inMemory: "true"}),
+ ErrorCodes.TypeMismatch);
+
+ //
+ // Test invalid parameters for setDefaultRWConcern.
+ //
+
+ // Must include either wc or rc.
+ assert.commandFailedWithCode(conn.adminCommand({setDefaultRWConcern: 1}), ErrorCodes.BadValue);
+
+ // Invalid write concern.
+ assert.commandFailedWithCode(
+ conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: 1}),
+ ErrorCodes.TypeMismatch);
+
+ // w less than 1.
+ assert.commandFailedWithCode(conn.adminCommand({
+ setDefaultRWConcern: 1,
+ defaultWriteConcern: {w: 0},
+ }),
+ ErrorCodes.BadValue);
+
+ // Invalid read concern.
+ assert.commandFailedWithCode(conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: 1}),
+ ErrorCodes.TypeMismatch);
+
+ // Non-existent level.
+ assert.commandFailedWithCode(
+ conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "dummy"}}),
+ ErrorCodes.FailedToParse);
+
+ // Unsupported level.
+ assert.commandFailedWithCode(
+ conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "linearizable"}}),
+ ErrorCodes.BadValue);
+ assert.commandFailedWithCode(
+ conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "snapshot"}}),
+ ErrorCodes.BadValue);
+
+ // Fields other than level.
+ assert.commandFailedWithCode(conn.adminCommand({
+ setDefaultRWConcern: 1,
+ defaultReadConcern: {level: "local", afterClusterTime: Timestamp(50, 1)}
+ }),
+ ErrorCodes.BadValue);
+ assert.commandFailedWithCode(conn.adminCommand({
+ setDefaultRWConcern: 1,
+ defaultReadConcern: {level: "snapshot", atClusterTime: Timestamp(50, 1)}
+ }),
+ ErrorCodes.BadValue);
+ assert.commandFailedWithCode(conn.adminCommand({
+ setDefaultRWConcern: 1,
+ defaultReadConcern: {level: "local", afterOpTime: {ts: Timestamp(50, 1), t: 1}}
+ }),
+ ErrorCodes.BadValue);
+}
+
+// Sets a default read and write concern.
+function setDefaultRWConcern(conn) {
+ assert.commandWorked(conn.adminCommand({
+ setDefaultRWConcern: 1,
+ defaultReadConcern: {level: "local"},
+ defaultWriteConcern: {w: 1}
+ }));
+}
+
+// Unsets the default read and write concerns.
+function unsetDefaultRWConcern(conn) {
+ assert.commandWorked(conn.adminCommand(
+ {setDefaultRWConcern: 1, defaultReadConcern: {}, defaultWriteConcern: {}}));
+}
+
+// Verifies no fields are returned if neither a default read nor write concern has been set.
+function verifyDefaultResponses(conn) {
+ const res = assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1}));
+ const inMemoryRes =
+ assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: true}));
+
+ const unexpectedFields =
+ ["defaultReadConcern", "defaultWriteConcern", "epoch", "setTime", "localSetTime"];
+ unexpectedFields.forEach(field => {
+ assert(!res.hasOwnProperty(field),
+ `response unexpectedly had field '${field}', res: ${tojson(res)}`);
+ assert(!inMemoryRes.hasOwnProperty(field),
+ `inMemory=true response unexpectedly had field '${field}', res: ${tojson(res)}`);
+ });
+}
+
+function verifyDefaultRWCommandsValidInput(conn) {
+ //
+ // Test parameters for getDefaultRWConcern.
+ //
+
+ // No parameters is allowed.
+ assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1}));
+
+ // inMemory parameter is allowed.
+ assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: true}));
+ assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: false}));
+
+ //
+ // Test parameters for setDefaultRWConcern.
+ //
+
+ // Setting only rc is allowed.
+ assert.commandWorked(
+ conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}));
+ assert.commandWorked(
+ conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "majority"}}));
+
+ // Setting only wc is allowed.
+ assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}}));
+ assert.commandWorked(
+ conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {w: 1, j: false}}));
+ assert.commandWorked(
+ conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {w: "majority"}}));
+
+ // Setting both wc and rc is allowed.
+ assert.commandWorked(conn.adminCommand({
+ setDefaultRWConcern: 1,
+ defaultWriteConcern: {w: 1},
+ defaultReadConcern: {level: "local"}
+ }));
+
+ // Empty write concern is allowed.
+ assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {}}));
+
+ // Empty read concern is allowed.
+ assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {}}));
+}
+
+function verifyDefaultRWCommandsSuccessfulResponses(conn) {
+ //
+ // Test responses for getDefaultRWConcern.
+ //
+
+ // When neither read nor write concern is set.
+ unsetDefaultRWConcern(conn);
+ verifyResponseFields(assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
+ {expectRC: false, expectWC: false});
+ verifyResponseFields(
+ assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: true})),
+ {expectRC: false, expectWC: false});
+
+ // When only read concern is set.
+ assert.commandWorked(conn.adminCommand(
+ {setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}, defaultWriteConcern: {}}));
+ verifyResponseFields(assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
+ {expectRC: true, expectWC: false});
+ verifyResponseFields(
+ assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: true})),
+ {expectRC: true, expectWC: false});
+
+ // When only write concern is set.
+ assert.commandWorked(conn.adminCommand(
+ {setDefaultRWConcern: 1, defaultReadConcern: {}, defaultWriteConcern: {w: 1}}));
+ verifyResponseFields(assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
+ {expectRC: false, expectWC: true});
+ verifyResponseFields(
+ assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: true})),
+ {expectRC: false, expectWC: true});
+
+ // When both read and write concern are set.
+ assert.commandWorked(conn.adminCommand({
+ setDefaultRWConcern: 1,
+ defaultReadConcern: {level: "local"},
+ defaultWriteConcern: {w: 1}
+ }));
+ verifyResponseFields(assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
+ {expectRC: true, expectWC: true});
+ verifyResponseFields(
+ assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: true})),
+ {expectRC: true, expectWC: true});
+
+ //
+ // Test responses for setDefaultRWConcern.
+ //
+
+ // When unsetting both read and write concern.
+ setDefaultRWConcern(conn);
+ verifyResponseFields(
+ assert.commandWorked(conn.adminCommand(
+ {setDefaultRWConcern: 1, defaultReadConcern: {}, defaultWriteConcern: {}})),
+ {expectRC: false, expectWC: false});
+
+ // When unsetting only read concern.
+ setDefaultRWConcern(conn);
+ verifyResponseFields(
+ assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {}})),
+ {expectRC: false, expectWC: true});
+
+ // When unsetting only write concern.
+ setDefaultRWConcern(conn);
+ verifyResponseFields(
+ assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {}})),
+ {expectRC: true, expectWC: false});
+
+ // When setting only write concern.
+ unsetDefaultRWConcern(conn);
+ verifyResponseFields(assert.commandWorked(conn.adminCommand(
+ {setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}})),
+ {expectRC: false, expectWC: true});
+
+ // When setting only read concern.
+ unsetDefaultRWConcern(conn);
+ verifyResponseFields(assert.commandWorked(conn.adminCommand(
+ {setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}})),
+ {expectRC: true, expectWC: false});
+
+ // When setting both read and write concern.
+ unsetDefaultRWConcern(conn);
+ verifyResponseFields(assert.commandWorked(conn.adminCommand({
+ setDefaultRWConcern: 1,
+ defaultReadConcern: {level: "local"},
+ defaultWriteConcern: {w: 1}
+ })),
+ {expectRC: true, expectWC: true});
+}
+
+// Verifies the error code returned by connections to nodes that do not support the get/set default
+// rw concern commands.
+function verifyDefaultRWCommandsFailWithCode(conn, {failureCode}) {
+ assert.commandFailedWithCode(conn.adminCommand({getDefaultRWConcern: 1}), failureCode);
+ assert.commandFailedWithCode(
+ conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}),
+ failureCode);
+}
+
+jsTestLog("Testing standalone mongod...");
+{
+ const standalone = MongoRunner.runMongod();
+
+ // Standalone node fails.
+ verifyDefaultRWCommandsFailWithCode(standalone, {failureCode: 51300});
+
+ MongoRunner.stopMongod(standalone);
+}
+
+jsTestLog("Testing standalone replica set...");
+{
+ const rst = new ReplSetTest({nodes: 2});
+ rst.startSet();
+ rst.initiate();
+
+ // Primary succeeds.
+ verifyDefaultResponses(rst.getPrimary());
+ verifyDefaultRWCommandsValidInput(rst.getPrimary());
+ verifyDefaultRWCommandsInvalidInput(rst.getPrimary());
+ verifyDefaultRWCommandsSuccessfulResponses(rst.getPrimary());
+
+ // Secondary succeeds.
+ assert.commandWorked(rst.getSecondary().adminCommand({getDefaultRWConcern: 1}));
+ // TODO SERVER-44890 Assert setDefaultRWConcern fails with NotMaster instead.
+ assert.commandWorked(rst.getSecondary().adminCommand(
+ {setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}));
+
+ rst.stopSet();
+}
+
+jsTestLog("Testing sharded cluster...");
+{
+ const st = new ShardingTest({shards: 1, rs: {nodes: 2}});
+
+ // Mongos succeeds.
+ verifyDefaultResponses(st.s);
+ verifyDefaultRWCommandsValidInput(st.s);
+ verifyDefaultRWCommandsInvalidInput(st.s);
+ verifyDefaultRWCommandsSuccessfulResponses(st.s);
+
+ // Shard node fails.
+ verifyDefaultRWCommandsFailWithCode(st.rs0.getPrimary(), {failureCode: 51301});
+ verifyDefaultRWCommandsFailWithCode(st.rs0.getSecondary(), {failureCode: 51301});
+
+ // Config server primary succeeds.
+ verifyDefaultRWCommandsValidInput(st.configRS.getPrimary());
+ verifyDefaultRWCommandsInvalidInput(st.configRS.getPrimary());
+ verifyDefaultRWCommandsSuccessfulResponses(st.configRS.getPrimary());
+
+ // Config server secondary succeeds.
+ assert.commandWorked(st.configRS.getSecondary().adminCommand({getDefaultRWConcern: 1}));
+ // TODO SERVER-44890 Assert setDefaultRWConcern fails instead.
+ assert.commandWorked(st.configRS.getSecondary().adminCommand(
+ {setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}));
+
+ st.stop();
+}
+})();
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index 1e1b927a553..f309978cb67 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -142,7 +142,6 @@ env.Library(
'logical_session_server_status_section.cpp',
'mr_common.cpp',
'reap_logical_session_cache_now.cpp',
- 'rwc_defaults_commands.cpp',
'traffic_recording_cmds.cpp',
'user_management_commands_common.cpp',
env.Idlc('drop_connections.idl')[0],
@@ -369,6 +368,7 @@ env.Library(
"oplog_note.cpp",
"resize_oplog.cpp",
"restart_catalog_command.cpp",
+ 'rwc_defaults_commands.cpp',
"set_feature_compatibility_version_command.cpp",
"set_index_commit_quorum_command.cpp",
"shutdown_d.cpp",
diff --git a/src/mongo/db/commands/rwc_defaults_commands.cpp b/src/mongo/db/commands/rwc_defaults_commands.cpp
index e251d4dfdc5..b6fb559c9a8 100644
--- a/src/mongo/db/commands/rwc_defaults_commands.cpp
+++ b/src/mongo/db/commands/rwc_defaults_commands.cpp
@@ -34,11 +34,24 @@
#include "mongo/db/commands.h"
#include "mongo/db/commands/rwc_defaults_commands_gen.h"
#include "mongo/db/read_write_concern_defaults.h"
+#include "mongo/db/repl/read_concern_args.h"
+#include "mongo/db/repl/replication_coordinator.h"
#include "mongo/util/log.h"
namespace mongo {
namespace {
+void assertNotStandaloneOrShardServer(OperationContext* opCtx, StringData cmdName) {
+ const auto replCoord = repl::ReplicationCoordinator::get(opCtx);
+ uassert(51300,
+ str::stream() << "'" << cmdName << "' is not supported on standalone nodes.",
+ replCoord->isReplEnabled());
+
+ uassert(51301,
+ str::stream() << "'" << cmdName << "' is not supported on shard nodes.",
+ serverGlobalParams.clusterRole != ClusterRole::ShardServer);
+}
+
class SetDefaultRWConcernCommand : public TypedCommand<SetDefaultRWConcernCommand> {
public:
AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
@@ -62,17 +75,11 @@ public:
using InvocationBase::InvocationBase;
auto typedRun(OperationContext* opCtx) {
- auto rc = request().getDefaultReadConcern();
- auto wc = request().getDefaultWriteConcern();
- uassert(ErrorCodes::BadValue,
- str::stream() << "At least one of the \""
- << SetDefaultRWConcern::kDefaultReadConcernFieldName << "\" or \""
- << SetDefaultRWConcern::kDefaultWriteConcernFieldName
- << "\" fields must be present",
- rc || wc);
+ assertNotStandaloneOrShardServer(opCtx, SetDefaultRWConcern::kCommandName);
auto& rwcDefaults = ReadWriteConcernDefaults::get(opCtx->getServiceContext());
- auto newDefaults = rwcDefaults.setConcerns(opCtx, rc, wc);
+ auto newDefaults = rwcDefaults.setConcerns(
+ opCtx, request().getDefaultReadConcern(), request().getDefaultWriteConcern());
log() << "successfully set RWC defaults to " << newDefaults.toBSON();
return newDefaults;
}
@@ -83,7 +90,7 @@ public:
}
void doCheckAuthorization(OperationContext*) const override {
- // TODO: add and use privilege action
+ // TODO SERVER-45038: add and use privilege action
}
NamespaceString ns() const override {
@@ -112,6 +119,10 @@ public:
using InvocationBase::InvocationBase;
auto typedRun(OperationContext* opCtx) {
+ assertNotStandaloneOrShardServer(opCtx, GetDefaultRWConcern::kCommandName);
+
+ // TODO SERVER-43720 Implement inMemory option.
+
auto& rwcDefaults = ReadWriteConcernDefaults::get(opCtx->getServiceContext());
return rwcDefaults.getDefault(opCtx);
}
@@ -122,7 +133,7 @@ public:
}
void doCheckAuthorization(OperationContext*) const override {
- // TODO: add and use privilege action
+ // TODO SERVER-45038: add and use privilege action
}
NamespaceString ns() const override {
diff --git a/src/mongo/db/commands/rwc_defaults_commands.idl b/src/mongo/db/commands/rwc_defaults_commands.idl
index e4f2c2bb1b5..ad8efef676f 100644
--- a/src/mongo/db/commands/rwc_defaults_commands.idl
+++ b/src/mongo/db/commands/rwc_defaults_commands.idl
@@ -34,7 +34,6 @@ imports:
- "mongo/db/repl/read_concern_args.idl"
- "mongo/db/write_concern_options.idl"
-
commands:
setDefaultRWConcern:
description: "set the current read/write concern defaults (cluster-wide)"
@@ -52,3 +51,8 @@ commands:
getDefaultRWConcern:
description: "get the current read/write concern defaults being applied by this node"
namespace: ignored
+ fields:
+ inMemory:
+ type: bool
+ description: "If true, return the locally cached read/write concern defaults"
+ optional: true
diff --git a/src/mongo/db/read_write_concern_defaults.cpp b/src/mongo/db/read_write_concern_defaults.cpp
index 1e2c2197c83..aaf861e0a03 100644
--- a/src/mongo/db/read_write_concern_defaults.cpp
+++ b/src/mongo/db/read_write_concern_defaults.cpp
@@ -83,7 +83,12 @@ void ReadWriteConcernDefaults::_setDefault(RWConcernDefault&& rwc) {
RWConcernDefault ReadWriteConcernDefaults::setConcerns(OperationContext* opCtx,
const boost::optional<ReadConcern>& rc,
const boost::optional<WriteConcern>& wc) {
- invariant(rc || wc);
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "At least one of the \""
+ << RWConcernDefault::kDefaultReadConcernFieldName << "\" or \""
+ << RWConcernDefault::kDefaultWriteConcernFieldName
+ << "\" fields must be present",
+ rc || wc);
if (rc) {
checkSuitabilityAsDefault(*rc);
@@ -96,8 +101,12 @@ RWConcernDefault ReadWriteConcernDefaults::setConcerns(OperationContext* opCtx,
auto epoch = LogicalClock::get(opCtx->getServiceContext())->getClusterTime().asTimestamp();
RWConcernDefault rwc;
- rwc.setDefaultReadConcern(rc);
- rwc.setDefaultWriteConcern(wc);
+ if (rc && !rc->isEmpty()) {
+ rwc.setDefaultReadConcern(rc);
+ }
+ if (wc && !wc->usedDefaultW) {
+ rwc.setDefaultWriteConcern(wc);
+ }
rwc.setEpoch(epoch);
rwc.setSetTime(now);
rwc.setLocalSetTime(now);
diff --git a/src/mongo/db/read_write_concern_defaults.h b/src/mongo/db/read_write_concern_defaults.h
index de21b27836d..761d8a08fbc 100644
--- a/src/mongo/db/read_write_concern_defaults.h
+++ b/src/mongo/db/read_write_concern_defaults.h
@@ -82,6 +82,7 @@ public:
* Interface when an admin has run the command to change the defaults.
* At least one of the `rc` or `wc` params must be set.
* Will generate and use a new epoch and setTime for the updated defaults, which are returned.
+ * Validates the supplied read and write concerns can serve as defaults.
*/
RWConcernDefault setConcerns(OperationContext* opCtx,
const boost::optional<ReadConcern>& rc,
diff --git a/src/mongo/db/repl/read_concern_args.cpp b/src/mongo/db/repl/read_concern_args.cpp
index d7ef1ce6ff1..45e826482ee 100644
--- a/src/mongo/db/repl/read_concern_args.cpp
+++ b/src/mongo/db/repl/read_concern_args.cpp
@@ -82,6 +82,12 @@ BSONObj ReadConcernArgs::toBSON() const {
return bob.obj();
}
+BSONObj ReadConcernArgs::toBSONInner() const {
+ BSONObjBuilder bob;
+ _appendInfoInner(&bob);
+ return bob.obj();
+}
+
bool ReadConcernArgs::isEmpty() const {
return !_afterClusterTime && !_opTime && !_atClusterTime && !_level;
}
@@ -261,25 +267,27 @@ bool ReadConcernArgs::isSpeculativeMajority() const {
_majorityReadMechanism == MajorityReadMechanism::kSpeculative;
}
-void ReadConcernArgs::appendInfo(BSONObjBuilder* builder) const {
- BSONObjBuilder rcBuilder(builder->subobjStart(kReadConcernFieldName));
-
+void ReadConcernArgs::_appendInfoInner(BSONObjBuilder* builder) const {
if (_level) {
- rcBuilder.append(kLevelFieldName, readConcernLevels::toString(_level.get()));
+ builder->append(kLevelFieldName, readConcernLevels::toString(_level.get()));
}
if (_opTime) {
- _opTime->append(&rcBuilder, kAfterOpTimeFieldName.toString());
+ _opTime->append(builder, kAfterOpTimeFieldName.toString());
}
if (_afterClusterTime) {
- rcBuilder.append(kAfterClusterTimeFieldName, _afterClusterTime->asTimestamp());
+ builder->append(kAfterClusterTimeFieldName, _afterClusterTime->asTimestamp());
}
if (_atClusterTime) {
- rcBuilder.append(kAtClusterTimeFieldName, _atClusterTime->asTimestamp());
+ builder->append(kAtClusterTimeFieldName, _atClusterTime->asTimestamp());
}
+}
+void ReadConcernArgs::appendInfo(BSONObjBuilder* builder) const {
+ BSONObjBuilder rcBuilder(builder->subobjStart(kReadConcernFieldName));
+ _appendInfoInner(&rcBuilder);
rcBuilder.done();
}
diff --git a/src/mongo/db/repl/read_concern_args.h b/src/mongo/db/repl/read_concern_args.h
index 110624649bd..8a34b4248b9 100644
--- a/src/mongo/db/repl/read_concern_args.h
+++ b/src/mongo/db/repl/read_concern_args.h
@@ -130,7 +130,7 @@ public:
bool isSpeculativeMajority() const;
/**
- * Appends level and afterOpTime.
+ * Appends level, afterOpTime, and any other sub-fields in a 'readConcern' sub-object.
*/
void appendInfo(BSONObjBuilder* builder) const;
@@ -165,10 +165,16 @@ public:
boost::optional<LogicalTime> getArgsAtClusterTime() const;
BSONObj toBSON() const;
+ BSONObj toBSONInner() const;
std::string toString() const;
private:
/**
+ * Appends level, afterOpTime, and the other "inner" fields of the read concern args.
+ */
+ void _appendInfoInner(BSONObjBuilder* builder) const;
+
+ /**
* Read data after the OpTime of an operation on this replica set. Deprecated.
* The only user is for read-after-optime calls using the config server optime.
*/
diff --git a/src/mongo/db/repl/read_concern_args.idl b/src/mongo/db/repl/read_concern_args.idl
index 1954f6dddf2..e791e370bdd 100644
--- a/src/mongo/db/repl/read_concern_args.idl
+++ b/src/mongo/db/repl/read_concern_args.idl
@@ -40,5 +40,5 @@ types:
description: "An object representing a read concern."
bson_serialization_type: object
cpp_type: "mongo::repl::ReadConcernArgs"
- serializer: "mongo::repl::ReadConcernArgs::toBSON"
+ serializer: "mongo::repl::ReadConcernArgs::toBSONInner"
deserializer: "mongo::repl::ReadConcernArgs::fromBSONThrows"
diff --git a/src/mongo/db/rw_concern_default.idl b/src/mongo/db/rw_concern_default.idl
index 396c12264c1..94aca23c7b9 100644
--- a/src/mongo/db/rw_concern_default.idl
+++ b/src/mongo/db/rw_concern_default.idl
@@ -38,6 +38,7 @@ imports:
structs:
RWConcernDefault:
description: "Represents a set of read/write concern defaults, and associated metadata"
+ strict: false
fields:
defaultReadConcern:
description: "The default read concern"
diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript
index d9c0f9b6f12..a279e7535ee 100644
--- a/src/mongo/s/commands/SConscript
+++ b/src/mongo/s/commands/SConscript
@@ -79,6 +79,7 @@ env.Library(
'cluster_repl_set_get_status_cmd.cpp',
'cluster_reset_error_cmd.cpp',
'cluster_restart_catalog_command.cpp',
+ 'cluster_rwc_defaults_commands.cpp',
'cluster_set_index_commit_quorum_cmd.cpp',
'cluster_set_feature_compatibility_version_cmd.cpp',
'cluster_set_free_monitoring.cpp' if get_option("enable-free-mon") == 'on' else [],
@@ -116,6 +117,7 @@ env.Library(
'$BUILD_DIR/mongo/db/ftdc/ftdc_server',
'$BUILD_DIR/mongo/db/shared_request_handling',
'$BUILD_DIR/mongo/db/logical_session_cache_impl',
+ '$BUILD_DIR/mongo/db/read_write_concern_defaults',
'$BUILD_DIR/mongo/db/pipeline/aggregation',
'$BUILD_DIR/mongo/db/query/command_request_response',
'$BUILD_DIR/mongo/db/query/map_reduce_output_format',
diff --git a/src/mongo/s/commands/cluster_rwc_defaults_commands.cpp b/src/mongo/s/commands/cluster_rwc_defaults_commands.cpp
new file mode 100644
index 00000000000..8286a8d42c4
--- /dev/null
+++ b/src/mongo/s/commands/cluster_rwc_defaults_commands.cpp
@@ -0,0 +1,160 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/commands/rwc_defaults_commands_gen.h"
+#include "mongo/db/namespace_string.h"
+#include "mongo/db/read_write_concern_defaults.h"
+#include "mongo/db/rw_concern_default_gen.h"
+#include "mongo/s/cluster_commands_helpers.h"
+#include "mongo/s/grid.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+namespace {
+
+/**
+ * Implements the setDefaultRWConcern command on mongos. Inherits from BasicCommand because this
+ * command forwards the user's request to the config server and does not need to parse it.
+ */
+class ClusterSetDefaultRWConcernCommand : public BasicCommand {
+public:
+ ClusterSetDefaultRWConcernCommand() : BasicCommand("setDefaultRWConcern") {}
+
+ bool run(OperationContext* opCtx,
+ const std::string& dbName,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) override {
+ auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard();
+ auto cmdResponse = uassertStatusOK(configShard->runCommandWithFixedRetryAttempts(
+ opCtx,
+ ReadPreferenceSetting(ReadPreference::PrimaryOnly),
+ NamespaceString::kAdminDb.toString(),
+ CommandHelpers::appendMajorityWriteConcern(
+ CommandHelpers::filterCommandRequestForPassthrough(cmdObj)),
+ Shard::RetryPolicy::kNotIdempotent));
+
+ uassertStatusOK(cmdResponse.commandStatus);
+ uassertStatusOK(cmdResponse.writeConcernStatus);
+
+ CommandHelpers::filterCommandReplyForPassthrough(cmdResponse.response, &result);
+ return true;
+ }
+
+ bool supportsWriteConcern(const BSONObj& cmd) const override {
+ return true;
+ }
+
+ Status checkAuthForOperation(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj) const override {
+ // TODO SERVER-45038: add and use privilege action
+ return Status::OK();
+ }
+
+ std::string help() const override {
+ return "Sets the default read or write concern for a cluster";
+ }
+
+ bool adminOnly() const override {
+ return true;
+ }
+
+ AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
+ return AllowedOnSecondary::kNever;
+ }
+} clusterSetDefaultRWConcernCommand;
+
+/**
+ * Implements the getDefaultRWConcern command on mongos.
+ */
+class ClusterGetDefaultRWConcernCommand final
+ : public TypedCommand<ClusterGetDefaultRWConcernCommand> {
+public:
+ using Request = GetDefaultRWConcern;
+ using Response = RWConcernDefault;
+
+ class Invocation final : public InvocationBase {
+ public:
+ using InvocationBase::InvocationBase;
+
+ Response typedRun(OperationContext* opCtx) {
+ // TODO SERVER-43720 Implement inMemory option.
+
+ GetDefaultRWConcern configsvrRequest;
+ configsvrRequest.setDbName(request().getDbName());
+
+ auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard();
+ auto cmdResponse = uassertStatusOK(configShard->runCommandWithFixedRetryAttempts(
+ opCtx,
+ ReadPreferenceSetting(ReadPreference::PrimaryOnly),
+ NamespaceString::kAdminDb.toString(),
+ applyReadWriteConcern(opCtx, this, configsvrRequest.toBSON({})),
+ Shard::RetryPolicy::kIdempotent));
+
+ uassertStatusOK(cmdResponse.commandStatus);
+
+ return Response::parse(IDLParserErrorContext("ClusterGetDefaultRWConcernResponse"),
+ cmdResponse.response);
+ }
+
+ private:
+ bool supportsWriteConcern() const override {
+ return false;
+ }
+
+ void doCheckAuthorization(OperationContext*) const override {
+ // TODO SERVER-45038: add and use privilege action
+ }
+
+ NamespaceString ns() const override {
+ return NamespaceString(request().getDbName(), "");
+ }
+ };
+
+ std::string help() const override {
+ return "Gets the default read or write concern for a cluster";
+ }
+
+ bool adminOnly() const override {
+ return true;
+ }
+
+ AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
+ return AllowedOnSecondary::kNever;
+ }
+} clusterGetDefaultRWConcernCommand;
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/shell/assert.js b/src/mongo/shell/assert.js
index 53e283b540a..07bf0c31bfd 100644
--- a/src/mongo/shell/assert.js
+++ b/src/mongo/shell/assert.js
@@ -284,7 +284,7 @@ assert = (function() {
if (count != arr.length) {
doassert(_buildAssertionMessage(
- msg, "None of values from " + tojson(arr) + " was in " + tojson(result)));
+ msg, "Not all of the values from " + tojson(arr) + " were in " + tojson(result)));
}
};