diff options
Diffstat (limited to 'src/mongo')
28 files changed, 302 insertions, 51 deletions
diff --git a/src/mongo/client/SConscript b/src/mongo/client/SConscript index bea8a3d755a..f6d531a737e 100644 --- a/src/mongo/client/SConscript +++ b/src/mongo/client/SConscript @@ -182,6 +182,7 @@ clientDriverEnv.Library( 'dbclient_base.cpp', 'dbclient_cursor.cpp', 'index_spec.cpp', + env.Idlc('client_api_version_parameters.idl')[0], ], LIBDEPS=[ '$BUILD_DIR/mongo/db/dbmessage', @@ -196,6 +197,7 @@ clientDriverEnv.Library( 'connection_string', ], LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/idl/idl_parser', '$BUILD_DIR/mongo/util/net/ssl_manager', ], ) diff --git a/src/mongo/client/client_api_version_parameters.idl b/src/mongo/client/client_api_version_parameters.idl new file mode 100644 index 00000000000..26a89f01d56 --- /dev/null +++ b/src/mongo/client/client_api_version_parameters.idl @@ -0,0 +1,56 @@ +# Copyright (C) 2020-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. + +global: + cpp_namespace: "mongo" + +imports: + - "mongo/idl/basic_types.idl" + +structs: + + # Follow the MongoDB Drivers API for passing API Version parameters in clients. The drivers API + # is like MongoClient(uri, api={version: "1", strict: true, deprecationErrors: true}). + + ClientAPIVersionParameters: + description: "Parser for Versioned API parameters passed to 'new Mongo()' in the mongo shell" + strict: true + fields: + version: + description: "The requested API version" + type: string + # The server requires the apiVersion parameter in commands, leave enforcement to the server. + optional: true + strict: + description: "Whether to restrict the connection to behaviors in the requested API version" + type: bool + optional: true + deprecationErrors: + description: "Whether to restrict the connection to non-deprecated behaviors in the + requested API version" + type: bool + optional: true diff --git a/src/mongo/client/connection_string.h b/src/mongo/client/connection_string.h index 493c916528f..67f62116d16 100644 --- a/src/mongo/client/connection_string.h +++ b/src/mongo/client/connection_string.h @@ -43,6 +43,7 @@ namespace mongo { +class ClientAPIVersionParameters; class DBClientBase; class MongoURI; @@ -121,10 +122,12 @@ public: bool operator==(const ConnectionString& other) const; bool operator!=(const ConnectionString& other) const; - std::unique_ptr<DBClientBase> connect(StringData applicationName, - std::string& errmsg, - double socketTimeout = 0, - const MongoURI* uri = nullptr) const; + std::unique_ptr<DBClientBase> connect( + StringData applicationName, + std::string& errmsg, + double socketTimeout = 0, + const MongoURI* uri = nullptr, + const ClientAPIVersionParameters* apiParameters = nullptr) const; static StatusWith<ConnectionString> parse(const std::string& url); @@ -147,9 +150,11 @@ public: virtual ~ConnectionHook() {} // Returns an alternative connection object for a string - virtual std::unique_ptr<DBClientBase> connect(const ConnectionString& c, - std::string& errmsg, - double socketTimeout) = 0; + virtual std::unique_ptr<DBClientBase> connect( + const ConnectionString& c, + std::string& errmsg, + double socketTimeout, + const ClientAPIVersionParameters* apiParameters = nullptr) = 0; }; static void setConnectionHook(ConnectionHook* hook) { diff --git a/src/mongo/client/connection_string_connect.cpp b/src/mongo/client/connection_string_connect.cpp index 498bf1db74d..735fc47d2af 100644 --- a/src/mongo/client/connection_string_connect.cpp +++ b/src/mongo/client/connection_string_connect.cpp @@ -46,10 +46,12 @@ namespace mongo { Mutex ConnectionString::_connectHookMutex = MONGO_MAKE_LATCH(); ConnectionString::ConnectionHook* ConnectionString::_connectHook = nullptr; -std::unique_ptr<DBClientBase> ConnectionString::connect(StringData applicationName, - std::string& errmsg, - double socketTimeout, - const MongoURI* uri) const { +std::unique_ptr<DBClientBase> ConnectionString::connect( + StringData applicationName, + std::string& errmsg, + double socketTimeout, + const MongoURI* uri, + const ClientAPIVersionParameters* apiParameters) const { MongoURI newURI{}; if (uri) { newURI = *uri; @@ -58,7 +60,8 @@ std::unique_ptr<DBClientBase> ConnectionString::connect(StringData applicationNa switch (_type) { case MASTER: { for (const auto& server : _servers) { - auto c = std::make_unique<DBClientConnection>(true, 0, newURI); + auto c = std::make_unique<DBClientConnection>( + true, 0, newURI, DBClientConnection::HandshakeValidationHook(), apiParameters); c->setSoTimeout(socketTimeout); LOGV2_DEBUG(20109, @@ -76,8 +79,12 @@ std::unique_ptr<DBClientBase> ConnectionString::connect(StringData applicationNa } case SET: { - auto set = std::make_unique<DBClientReplicaSet>( - _setName, _servers, applicationName, socketTimeout, std::move(newURI)); + auto set = std::make_unique<DBClientReplicaSet>(_setName, + _servers, + applicationName, + socketTimeout, + std::move(newURI), + apiParameters); if (!set->connect()) { errmsg = "connect failed to replica set "; errmsg += toString(); @@ -98,7 +105,8 @@ std::unique_ptr<DBClientBase> ConnectionString::connect(StringData applicationNa _connectHook); // Double-checked lock, since this will never be active during normal operation - auto replacementConn = _connectHook->connect(*this, errmsg, socketTimeout); + auto replacementConn = + _connectHook->connect(*this, errmsg, socketTimeout, apiParameters); LOGV2(20111, "Replacing connection to {oldConnString} with {newConnString}", diff --git a/src/mongo/client/dbclient_base.cpp b/src/mongo/client/dbclient_base.cpp index 34408b9fddd..04748a525af 100644 --- a/src/mongo/client/dbclient_base.cpp +++ b/src/mongo/client/dbclient_base.cpp @@ -45,10 +45,12 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/bson/util/builder.h" #include "mongo/client/authenticate.h" +#include "mongo/client/client_api_version_parameters_gen.h" #include "mongo/client/constants.h" #include "mongo/client/dbclient_cursor.h" #include "mongo/config.h" #include "mongo/db/commands.h" +#include "mongo/db/initialize_api_parameters_gen.h" #include "mongo/db/json.h" #include "mongo/db/namespace_string.h" #include "mongo/db/query/kill_cursors_gen.h" @@ -194,6 +196,43 @@ DBClientBase* DBClientBase::runFireAndForgetCommand(OpMsgRequest request) { return this; } +namespace { +void appendAPIVersionParameters(BSONObjBuilder& bob, + const ClientAPIVersionParameters& apiParameters) { + if (!apiParameters.getVersion()) { + return; + } + + bool hasVersion = false, hasStrict = false, hasDeprecationErrors = false; + BSONObjIterator i = bob.iterator(); + while (i.more()) { + auto elem = i.next(); + if (elem.fieldNameStringData() == APIParametersFromClient::kApiVersionFieldName) { + hasVersion = true; + } else if (elem.fieldNameStringData() == APIParametersFromClient::kApiStrictFieldName) { + hasStrict = true; + } else if (elem.fieldNameStringData() == + APIParametersFromClient::kApiDeprecationErrorsFieldName) { + hasDeprecationErrors = true; + } + } + + if (!hasVersion) { + bob.append(APIParametersFromClient::kApiVersionFieldName, *apiParameters.getVersion()); + } + + // Include apiStrict/apiDeprecationErrors if they are not boost::none. + if (!hasStrict && apiParameters.getStrict()) { + bob.append(APIParametersFromClient::kApiStrictFieldName, *apiParameters.getStrict()); + } + + if (!hasDeprecationErrors && apiParameters.getDeprecationErrors()) { + bob.append(APIParametersFromClient::kApiDeprecationErrorsFieldName, + *apiParameters.getDeprecationErrors()); + } +} +} // namespace + std::pair<rpc::UniqueReply, DBClientBase*> DBClientBase::runCommandWithTarget( OpMsgRequest request) { // Make sure to reconnect if needed before building our request, since the request depends on @@ -204,9 +243,13 @@ std::pair<rpc::UniqueReply, DBClientBase*> DBClientBase::runCommandWithTarget( auto host = getServerAddress(); auto opCtx = haveClient() ? cc().getOperationContext() : nullptr; - if (_metadataWriter) { + + if (_metadataWriter || _apiParameters.getVersion()) { BSONObjBuilder metadataBob(std::move(request.body)); - uassertStatusOK(_metadataWriter(opCtx, &metadataBob)); + if (_metadataWriter) { + uassertStatusOK(_metadataWriter(opCtx, &metadataBob)); + } + appendAPIVersionParameters(metadataBob, _apiParameters); request.body = metadataBob.obj(); } diff --git a/src/mongo/client/dbclient_base.h b/src/mongo/client/dbclient_base.h index 92e4b8e245f..19bf9d2c417 100644 --- a/src/mongo/client/dbclient_base.h +++ b/src/mongo/client/dbclient_base.h @@ -34,6 +34,7 @@ #include "mongo/base/string_data.h" #include "mongo/client/authenticate.h" +#include "mongo/client/client_api_version_parameters_gen.h" #include "mongo/client/connection_string.h" #include "mongo/client/dbclient_cursor.h" #include "mongo/client/index_spec.h" @@ -109,11 +110,15 @@ class DBClientBase : public DBClientQueryInterface { DBClientBase& operator=(const DBClientBase&) = delete; public: - DBClientBase() + DBClientBase(const ClientAPIVersionParameters* apiParameters = nullptr) : _logLevel(logv2::LogSeverity::Log()), _connectionId(ConnectionIdSequence.fetchAndAdd(1)), _cachedAvailableOptions((enum QueryOptions)0), - _haveCachedAvailableOptions(false) {} + _haveCachedAvailableOptions(false) { + if (apiParameters) { + _apiParameters = *apiParameters; + } + } virtual ~DBClientBase() {} @@ -770,6 +775,10 @@ public: virtual const SSLConfiguration* getSSLConfiguration() = 0; #endif + const ClientAPIVersionParameters& getApiParameters() const { + return _apiParameters; + } + protected: /** if the result of a command is ok*/ bool isOk(const BSONObj&); @@ -836,6 +845,8 @@ private: // The operationTime associated with the last command handles by the client. // TODO(SERVER-49791): Implement proper tracking of operationTime. Timestamp _lastOperationTime; + + ClientAPIVersionParameters _apiParameters; }; // DBClientBase BSONElement getErrField(const BSONObj& result); diff --git a/src/mongo/client/dbclient_connection.cpp b/src/mongo/client/dbclient_connection.cpp index 7ae8c5ca793..66714d742de 100644 --- a/src/mongo/client/dbclient_connection.cpp +++ b/src/mongo/client/dbclient_connection.cpp @@ -678,8 +678,10 @@ unsigned long long DBClientConnection::query(std::function<void(DBClientCursorBa DBClientConnection::DBClientConnection(bool _autoReconnect, double so_timeout, MongoURI uri, - const HandshakeValidationHook& hook) - : autoReconnect(_autoReconnect), + const HandshakeValidationHook& hook, + const ClientAPIVersionParameters* apiParameters) + : DBClientBase(apiParameters), + autoReconnect(_autoReconnect), _autoReconnectBackoff(Seconds(1), Seconds(2)), _hook(hook), _uri(std::move(uri)) { diff --git a/src/mongo/client/dbclient_connection.h b/src/mongo/client/dbclient_connection.h index db6c0c2a567..f5408f839be 100644 --- a/src/mongo/client/dbclient_connection.h +++ b/src/mongo/client/dbclient_connection.h @@ -95,7 +95,8 @@ public: DBClientConnection(bool _autoReconnect = false, double so_timeout = 0, MongoURI uri = {}, - const HandshakeValidationHook& hook = HandshakeValidationHook()); + const HandshakeValidationHook& hook = HandshakeValidationHook(), + const ClientAPIVersionParameters* apiParameters = nullptr); virtual ~DBClientConnection() { _numConnections.fetchAndAdd(-1); diff --git a/src/mongo/client/dbclient_rs.cpp b/src/mongo/client/dbclient_rs.cpp index 0359251d9fa..b9871482aac 100644 --- a/src/mongo/client/dbclient_rs.cpp +++ b/src/mongo/client/dbclient_rs.cpp @@ -133,8 +133,10 @@ DBClientReplicaSet::DBClientReplicaSet(const string& name, const vector<HostAndPort>& servers, StringData applicationName, double so_timeout, - MongoURI uri) - : _setName(name), + MongoURI uri, + const ClientAPIVersionParameters* apiParameters) + : DBClientBase(apiParameters), + _setName(name), _applicationName(applicationName.toString()), _so_timeout(so_timeout), _uri(std::move(uri)) { diff --git a/src/mongo/client/dbclient_rs.h b/src/mongo/client/dbclient_rs.h index 4710a300c5d..e86cac71ae9 100644 --- a/src/mongo/client/dbclient_rs.h +++ b/src/mongo/client/dbclient_rs.h @@ -67,7 +67,8 @@ public: const std::vector<HostAndPort>& servers, StringData applicationName, double so_timeout = 0, - MongoURI uri = {}); + MongoURI uri = {}, + const ClientAPIVersionParameters* apiParameters = nullptr); /** * Returns false if no member of the set were reachable. This object diff --git a/src/mongo/client/mongo_uri.h b/src/mongo/client/mongo_uri.h index 3d155e1123c..26546ed7af5 100644 --- a/src/mongo/client/mongo_uri.h +++ b/src/mongo/client/mongo_uri.h @@ -46,6 +46,8 @@ namespace mongo { +class ClientAPIVersionParameters; + /** * Encode a string for embedding in a URI. * Replaces reserved bytes with %xx sequences. @@ -149,7 +151,8 @@ public: DBClientBase* connect(StringData applicationName, std::string& errmsg, - boost::optional<double> socketTimeoutSecs = boost::none) const; + boost::optional<double> socketTimeoutSecs = boost::none, + const ClientAPIVersionParameters* apiParameters = nullptr) const; const std::string& getUser() const { return _user; diff --git a/src/mongo/client/mongo_uri_connect.cpp b/src/mongo/client/mongo_uri_connect.cpp index ae556d20aa9..34144bc0b66 100644 --- a/src/mongo/client/mongo_uri_connect.cpp +++ b/src/mongo/client/mongo_uri_connect.cpp @@ -39,7 +39,8 @@ namespace mongo { DBClientBase* MongoURI::connect(StringData applicationName, std::string& errmsg, - boost::optional<double> socketTimeoutSecs) const { + boost::optional<double> socketTimeoutSecs, + const ClientAPIVersionParameters* apiParameters) const { OptionsMap::const_iterator it = _options.find("socketTimeoutMS"); if (it != _options.end() && !socketTimeoutSecs) { try { @@ -50,8 +51,8 @@ DBClientBase* MongoURI::connect(StringData applicationName, } } - auto ret = std::unique_ptr<DBClientBase>( - _connectString.connect(applicationName, errmsg, socketTimeoutSecs.value_or(0.0), this)); + auto ret = std::unique_ptr<DBClientBase>(_connectString.connect( + applicationName, errmsg, socketTimeoutSecs.value_or(0.0), this, apiParameters)); if (!ret) { return nullptr; } diff --git a/src/mongo/db/initialize_api_parameters.cpp b/src/mongo/db/initialize_api_parameters.cpp index a0e0a95220b..84ef1ce568a 100644 --- a/src/mongo/db/initialize_api_parameters.cpp +++ b/src/mongo/db/initialize_api_parameters.cpp @@ -31,14 +31,17 @@ namespace mongo { -const APIParametersFromClient initializeAPIParameters(const BSONObj& requestBody, +const APIParametersFromClient initializeAPIParameters(OperationContext* opCtx, + const BSONObj& requestBody, Command* command) { - auto apiParamsFromClient = APIParametersFromClient::parse("APIParametersFromClient"_sd, requestBody); - if (gRequireApiVersion.load()) { - uassert(498870, "Missing apiVersion parameter", apiParamsFromClient.getApiVersion()); + if (gRequireApiVersion.load() && !opCtx->getClient()->isInDirectClient()) { + uassert( + 498870, + "The apiVersion parameter is required, please configure your MongoClient's API version", + apiParamsFromClient.getApiVersion()); } if (apiParamsFromClient.getApiDeprecationErrors() || apiParamsFromClient.getApiStrict()) { diff --git a/src/mongo/db/initialize_api_parameters.h b/src/mongo/db/initialize_api_parameters.h index 9ca8bec10b7..b255b6800d7 100644 --- a/src/mongo/db/initialize_api_parameters.h +++ b/src/mongo/db/initialize_api_parameters.h @@ -41,7 +41,9 @@ namespace mongo { * This function parses a command's API Version parameters from a request and stores the apiVersion, * apiStrict, and apiDeprecationErrors fields. */ -const APIParametersFromClient initializeAPIParameters(const BSONObj& requestBody, Command* command); +const APIParametersFromClient initializeAPIParameters(OperationContext* opCtx, + const BSONObj& requestBody, + Command* command); /** * Decorates operation context with methods to retrieve apiVersion, apiStrict, and @@ -65,7 +67,7 @@ public: } void setAPIVersion(StringData apiVersion) { - _apiVersion = apiVersion; + _apiVersion = apiVersion.toString(); } const bool getAPIStrict() const { @@ -92,8 +94,10 @@ public: _paramsPassed = paramsPassed; } + BSONObj toBSON() const; + private: - StringData _apiVersion; + std::string _apiVersion; bool _apiStrict; bool _apiDeprecationErrors; bool _paramsPassed; diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index 006f1488f2f..81ab2961805 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -936,7 +936,7 @@ void execCommandDatabase(OperationContext* opCtx, (opCtx->getClient()->session()->getTags() & transport::Session::kInternalClient); try { - const auto apiParamsFromClient = initializeAPIParameters(request.body, command); + const auto apiParamsFromClient = initializeAPIParameters(opCtx, request.body, command); Client* client = opCtx->getClient(); { diff --git a/src/mongo/dbtests/mock/mock_conn_registry.cpp b/src/mongo/dbtests/mock/mock_conn_registry.cpp index 1c9e52b018f..43e4e4b2311 100644 --- a/src/mongo/dbtests/mock/mock_conn_registry.cpp +++ b/src/mongo/dbtests/mock/mock_conn_registry.cpp @@ -89,7 +89,10 @@ MockConnRegistry::MockConnHook::MockConnHook(MockConnRegistry* registry) : _regi MockConnRegistry::MockConnHook::~MockConnHook() {} std::unique_ptr<mongo::DBClientBase> MockConnRegistry::MockConnHook::connect( - const ConnectionString& connString, std::string& errmsg, double socketTimeout) { + const ConnectionString& connString, + std::string& errmsg, + double socketTimeout, + const ClientAPIVersionParameters* apiParameters) { const string hostName(connString.toString()); auto conn = _registry->connect(hostName); diff --git a/src/mongo/dbtests/mock/mock_conn_registry.h b/src/mongo/dbtests/mock/mock_conn_registry.h index feb8eb86517..57bcae8a44f 100644 --- a/src/mongo/dbtests/mock/mock_conn_registry.h +++ b/src/mongo/dbtests/mock/mock_conn_registry.h @@ -99,9 +99,11 @@ private: MockConnHook(MockConnRegistry* registry); ~MockConnHook(); - std::unique_ptr<mongo::DBClientBase> connect(const mongo::ConnectionString& connString, - std::string& errmsg, - double socketTimeout); + std::unique_ptr<mongo::DBClientBase> connect( + const mongo::ConnectionString& connString, + std::string& errmsg, + double socketTimeout, + const ClientAPIVersionParameters* apiParameters = nullptr) override; private: MockConnRegistry* _registry; diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp index ba7220b3839..644c10e6bcb 100644 --- a/src/mongo/s/commands/strategy.cpp +++ b/src/mongo/s/commands/strategy.cpp @@ -372,7 +372,7 @@ void runCommand(OperationContext* opCtx, auto wc = uassertStatusOK(WriteConcernOptions::extractWCFromCommand(request.body)); Client* client = opCtx->getClient(); - auto const apiParamsFromClient = initializeAPIParameters(request.body, command); + auto const apiParamsFromClient = initializeAPIParameters(opCtx, request.body, command); auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx); Status readConcernParseStatus = Status::OK(); diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp index 9d13c551017..6849e636391 100644 --- a/src/mongo/scripting/mozjs/mongo.cpp +++ b/src/mongo/scripting/mozjs/mongo.cpp @@ -34,6 +34,7 @@ #include <memory> #include "mongo/bson/simple_bsonelement_comparator.h" +#include "mongo/client/client_api_version_parameters_gen.h" #include "mongo/client/dbclient_base.h" #include "mongo/client/dbclient_rs.h" #include "mongo/client/global_conn_pool.h" @@ -87,6 +88,7 @@ const JSFunctionSpec MongoBase::methods[] = { MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(getMaxWireVersion, MongoExternalInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(isReplicaSetMember, MongoExternalInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(isMongos, MongoExternalInfo), + MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(getApiParameters, MongoExternalInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(_startSession, MongoExternalInfo), JS_FS_END, }; @@ -815,6 +817,7 @@ void setEncryptedDBClientCallback(EncryptedDBClientCallback* callback) { encryptedDBClientCallback = callback; } +// "new Mongo(uri, encryptedDBClientCallback, {options...})" void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); @@ -826,9 +829,33 @@ void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) { auto cs = uassertStatusOK(MongoURI::parse(host)); + ClientAPIVersionParameters apiParameters; + if (args.length() > 2 && !args.get(2).isUndefined()) { + uassert(4938000, + str::stream() << "the 'options' parameter to Mongo() must be an object", + args.get(2).isObject()); + auto options = ValueWriter(cx, args.get(2)).toBSON(); + if (options.hasField("api")) { + uassert(4938001, + "the 'api' option for Mongo() must be an object", + options["api"].isABSONObj()); + apiParameters = ClientAPIVersionParameters::parse(IDLParserErrorContext("api"_sd), + options["api"].Obj()); + if (apiParameters.getDeprecationErrors().value_or(false) || + apiParameters.getStrict().value_or(false)) { + uassert(4938002, + "the 'api' option for Mongo() must include 'version' if it includes " + "'strict' or " + "'deprecationErrors'", + apiParameters.getVersion()); + } + } + } + boost::optional<std::string> appname = cs.getAppName(); std::string errmsg; - std::unique_ptr<DBClientBase> conn(cs.connect(appname.value_or("MongoDB Shell"), errmsg)); + std::unique_ptr<DBClientBase> conn( + cs.connect(appname.value_or("MongoDB Shell"), errmsg, boost::none, &apiParameters)); if (!conn.get()) { uasserted(ErrorCodes::InternalError, errmsg); @@ -885,6 +912,11 @@ void MongoBase::Functions::isMongos::call(JSContext* cx, JS::CallArgs args) { args.rval().setBoolean(conn->isMongos()); } +void MongoBase::Functions::getApiParameters::call(JSContext* cx, JS::CallArgs args) { + auto conn = getConnection(args); + ValueReader(cx, args.rval()).fromBSON(conn->getApiParameters().toBSON(), nullptr, false); +} + void MongoBase::Functions::_startSession::call(JSContext* cx, JS::CallArgs args) { auto client = static_cast<std::shared_ptr<DBClientBase>*>(JS_GetPrivate(args.thisv().toObjectOrNull())); diff --git a/src/mongo/scripting/mozjs/mongo.h b/src/mongo/scripting/mozjs/mongo.h index 15020b365b5..2d9283fe8af 100644 --- a/src/mongo/scripting/mozjs/mongo.h +++ b/src/mongo/scripting/mozjs/mongo.h @@ -78,10 +78,11 @@ struct MongoBase : public BaseInfo { MONGO_DECLARE_JS_FUNCTION(getMaxWireVersion); MONGO_DECLARE_JS_FUNCTION(isReplicaSetMember); MONGO_DECLARE_JS_FUNCTION(isMongos); + MONGO_DECLARE_JS_FUNCTION(getApiParameters); MONGO_DECLARE_JS_FUNCTION(_startSession); }; - static const JSFunctionSpec methods[27]; + static const JSFunctionSpec methods[28]; static const char* const className; static const unsigned classFlags = JSCLASS_HAS_PRIVATE; diff --git a/src/mongo/shell/db.js b/src/mongo/shell/db.js index dc492dd6ffc..44c9a9b24d9 100644 --- a/src/mongo/shell/db.js +++ b/src/mongo/shell/db.js @@ -197,6 +197,16 @@ DB.prototype.adminCommand = function(obj, extra) { DB.prototype._adminCommand = DB.prototype.adminCommand; // alias old name +DB.prototype._runCommandWithoutApiStrict = function(command) { + let commandWithoutApiStrict = Object.assign({}, command); + if (this.getMongo().getApiParameters().strict) { + // Permit this command invocation, even if it's not in the requested API version. + commandWithoutApiStrict["apiStrict"] = false; + } + + return this.runCommand(commandWithoutApiStrict); +}; + DB.prototype._runAggregate = function(cmdObj, aggregateOptions) { assert(cmdObj.pipeline instanceof Array, "cmdObj must contain a 'pipeline' array"); assert(cmdObj.aggregate !== undefined, "cmdObj must contain 'aggregate' field"); @@ -1104,7 +1114,8 @@ DB.prototype.printSecondaryReplicationInfo = function() { var L = this.getSiblingDB("local"); if (L.system.replset.count() != 0) { - var status = this.adminCommand({'replSetGetStatus': 1}); + const status = + this.getSiblingDB('admin')._runCommandWithoutApiStrict({'replSetGetStatus': 1}); primary = getPrimary(status.members); if (primary) { startOptimeDate = primary.optimeDate; @@ -1126,7 +1137,7 @@ DB.prototype.printSecondaryReplicationInfo = function() { }; DB.prototype.serverBuildInfo = function() { - return this._adminCommand("buildinfo"); + return this.getSiblingDB("admin")._runCommandWithoutApiStrict({buildinfo: 1}); }; // Used to trim entries from the metrics.commands that have never been executed diff --git a/src/mongo/shell/mongo.js b/src/mongo/shell/mongo.js index 6790fcce8d6..71f6c707810 100644 --- a/src/mongo/shell/mongo.js +++ b/src/mongo/shell/mongo.js @@ -308,7 +308,7 @@ Mongo.prototype.getReadConcern = function() { return this._readConcernLevel; }; -connect = function(url, user, pass) { +connect = function(url, user, pass, apiParameters) { if (url instanceof MongoURI) { user = url.user; pass = url.password; @@ -359,7 +359,7 @@ connect = function(url, user, pass) { } chatty("connecting to: " + safeURL); try { - var m = new Mongo(url); + var m = new Mongo(url, undefined /* encryptedDBClientCallback */, apiParameters); } catch (e) { var dest; if (url.indexOf(".query.mongodb.net") != -1) { diff --git a/src/mongo/shell/mongo_main.cpp b/src/mongo/shell/mongo_main.cpp index 3343a61075c..ecdca584ab1 100644 --- a/src/mongo/shell/mongo_main.cpp +++ b/src/mongo/shell/mongo_main.cpp @@ -818,7 +818,8 @@ int mongo_main(int argc, char* argv[]) { ss << "__quiet = true;" << std::endl; } - ss << "db = connect( \"" << parsedURI.canonicalizeURIAsString() << "\");" << std::endl; + ss << "db = connect( \"" << parsedURI.canonicalizeURIAsString() + << "\", null, null, {api: " << getApiParametersJSON() << "});" << std::endl; if (shellGlobalParams.shouldRetryWrites || parsedURI.getRetryWrites()) { // If the --retryWrites cmdline argument or retryWrites URI param was specified, diff --git a/src/mongo/shell/shell_options.cpp b/src/mongo/shell/shell_options.cpp index 307e6301f48..d04885d722d 100644 --- a/src/mongo/shell/shell_options.cpp +++ b/src/mongo/shell/shell_options.cpp @@ -39,6 +39,7 @@ #include "mongo/base/status.h" #include "mongo/bson/util/builder.h" +#include "mongo/client/client_api_version_parameters_gen.h" #include "mongo/client/mongo_uri.h" #include "mongo/config.h" #include "mongo/db/auth/sasl_command_constants.h" @@ -320,6 +321,13 @@ Status storeMongoShellOptions(const moe::Environment& params, } } + // Future API versions may require logic changes in the shell, so ban them for now. + if (!shellGlobalParams.apiVersion.empty() && shellGlobalParams.apiVersion != "1") { + uasserted(4938003, + str::stream() << "Bad value --apiVersion '" << shellGlobalParams.apiVersion + << "', only API Version 1 is supported"); + } + if (params.count("setShellParameter")) { auto ssp = params["setShellParameter"].as<std::map<std::string, std::string>>(); auto map = ServerParameterSet::getGlobal()->getMap(); @@ -348,4 +356,21 @@ Status storeMongoShellOptions(const moe::Environment& params, return Status::OK(); } +std::string getApiParametersJSON() { + BSONObjBuilder bob; + if (!shellGlobalParams.apiVersion.empty()) { + bob.append(ClientAPIVersionParameters::kVersionFieldName, shellGlobalParams.apiVersion); + } + + if (shellGlobalParams.apiStrict) { + bob.append(ClientAPIVersionParameters::kStrictFieldName, true); + } + + if (shellGlobalParams.apiDeprecationErrors) { + bob.append(ClientAPIVersionParameters::kDeprecationErrorsFieldName, true); + } + + return bob.done().jsonString(); +} + } // namespace mongo diff --git a/src/mongo/shell/shell_options.h b/src/mongo/shell/shell_options.h index 45ac4e55455..2d5582e4ffc 100644 --- a/src/mongo/shell/shell_options.h +++ b/src/mongo/shell/shell_options.h @@ -68,6 +68,10 @@ struct ShellGlobalParams { std::string script; + std::string apiVersion; + bool apiStrict; + bool apiDeprecationErrors; + bool autoKillOp = false; bool useWriteCommandsDefault = true; @@ -98,4 +102,6 @@ bool handlePreValidationMongoShellOptions(const moe::Environment& params, Status storeMongoShellOptions(const moe::Environment& params, const std::vector<std::string>& args); void redactPasswordOptions(int argc, char** argv); + +std::string getApiParametersJSON(); } // namespace mongo diff --git a/src/mongo/shell/shell_options.idl b/src/mongo/shell/shell_options.idl index c56a951b2ee..49f402b42e3 100644 --- a/src/mongo/shell/shell_options.idl +++ b/src/mongo/shell/shell_options.idl @@ -78,6 +78,18 @@ configs: arg_vartype: String cpp_varname: shellGlobalParams.script + "apiVersion": + description: "set the MongoDB API version" + arg_vartype: String + cpp_varname: shellGlobalParams.apiVersion + "apiStrict": + description: "disable all features not included in the MongoDB Versioned API" + arg_vartype: Switch + cpp_varname: shellGlobalParams.apiStrict + "apiDeprecationErrors": + description: "disable all features deprecated in the MongoDB Versioned API" + arg_vartype: Switch + cpp_varname: shellGlobalParams.apiDeprecationErrors "objcheck": description: "inspect client data for validity on receipt" arg_vartype: Switch diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp index ae93862b113..4355bf7afd1 100644 --- a/src/mongo/shell/shell_utils.cpp +++ b/src/mongo/shell/shell_utils.cpp @@ -445,6 +445,12 @@ BSONObj shouldUseImplicitSessions(const BSONObj&, void* data) { return BSON("" << shellGlobalParams.shouldUseImplicitSessions); } +BSONObj apiParameters(const BSONObj&, void* data) { + return BSON("" << BSON("apiVersion" << shellGlobalParams.apiVersion << "apiStrict" + << shellGlobalParams.apiStrict << "apiDeprecationErrors" + << shellGlobalParams.apiDeprecationErrors)); +} + BSONObj interpreterVersion(const BSONObj& a, void* data) { uassert(16453, "interpreterVersion accepts no arguments", a.nFields() == 0); return BSON("" << getGlobalScriptEngine()->getInterpreterVersionString()); @@ -496,6 +502,7 @@ void initScope(Scope& scope) { scope.injectNative("_readMode", readMode); scope.injectNative("_shouldRetryWrites", shouldRetryWrites); scope.injectNative("_shouldUseImplicitSessions", shouldUseImplicitSessions); + scope.injectNative("_apiParameters", apiParameters); scope.externalSetup(); mongo::shell_utils::installShellUtils(scope); scope.execSetup(JSFiles::servers); @@ -540,7 +547,15 @@ ConnectionRegistry::ConnectionRegistry() = default; void ConnectionRegistry::registerConnection(DBClientBase& client, StringData uri) { BSONObj info; - if (client.runCommand("admin", BSON("whatsmyuri" << 1), info)) { + BSONObj command; + // If apiStrict is set override it, whatsmyuri is not in the Versioned API. + if (client.getApiParameters().getStrict()) { + command = BSON("whatsmyuri" << 1 << "apiStrict" << false); + } else { + command = BSON("whatsmyuri" << 1); + } + + if (client.runCommand("admin", command, info)) { stdx::lock_guard<Latch> lk(_mutex); _connectionUris[uri.toString()].insert(info["you"].str()); } diff --git a/src/mongo/shell/utils.js b/src/mongo/shell/utils.js index b84250ad598..50f443e82ee 100644 --- a/src/mongo/shell/utils.js +++ b/src/mongo/shell/utils.js @@ -1016,7 +1016,8 @@ shellHelper.show = function(what) { dbDeclared = false; } if (dbDeclared) { - var res = db.adminCommand({getLog: "startupWarnings"}); + var res = + db.getSiblingDB("admin")._runCommandWithoutApiStrict({getLog: "startupWarnings"}); if (res.ok) { if (res.log.length == 0) { return ""; |