diff options
author | Jack Mulrow <jack.mulrow@mongodb.com> | 2019-12-12 19:15:45 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-12-12 19:15:45 +0000 |
commit | 15d3feea932e89bf6ee7f562c5274853c2bcb11c (patch) | |
tree | 4573451f17020e86a5e2065beb455544e9f27a37 | |
parent | 3eecd38739655c84334cb09d03127479c4dd7a50 (diff) | |
download | mongo-15d3feea932e89bf6ee7f562c5274853c2bcb11c.tar.gz |
SERVER-43720 Add default read write concern commands to mongos and config server
-rw-r--r-- | jstests/auth/lib/commands_lib.js | 20 | ||||
-rw-r--r-- | jstests/sharding/read_write_concern_defaults_commands_api.js | 326 | ||||
-rw-r--r-- | src/mongo/db/commands/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/commands/rwc_defaults_commands.cpp | 33 | ||||
-rw-r--r-- | src/mongo/db/commands/rwc_defaults_commands.idl | 6 | ||||
-rw-r--r-- | src/mongo/db/read_write_concern_defaults.cpp | 15 | ||||
-rw-r--r-- | src/mongo/db/read_write_concern_defaults.h | 1 | ||||
-rw-r--r-- | src/mongo/db/repl/read_concern_args.cpp | 22 | ||||
-rw-r--r-- | src/mongo/db/repl/read_concern_args.h | 8 | ||||
-rw-r--r-- | src/mongo/db/repl/read_concern_args.idl | 2 | ||||
-rw-r--r-- | src/mongo/db/rw_concern_default.idl | 1 | ||||
-rw-r--r-- | src/mongo/s/commands/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_rwc_defaults_commands.cpp | 160 | ||||
-rw-r--r-- | src/mongo/shell/assert.js | 2 |
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))); } }; |