diff options
author | David Storch <david.storch@10gen.com> | 2015-06-22 12:25:10 -0400 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2015-06-23 14:16:18 -0400 |
commit | b8f0f273d8f2db1aed1974b94c9b18a63656ad48 (patch) | |
tree | ef86771a005eed7dc7d409857f187aba764a0a02 /src | |
parent | edfeb6a9f7a5f30a951c673374e9e058b1886a10 (diff) | |
download | mongo-b8f0f273d8f2db1aed1974b94c9b18a63656ad48.tar.gz |
SERVER-5811 implement the killCursors command for mongod
Implementing the killCursors command in mongos and the mongo shell is further work.
Diffstat (limited to 'src')
20 files changed, 902 insertions, 70 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 85eecce432a..d3e5ea90e7a 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -532,6 +532,7 @@ serverOnlyFiles = [ "commands/group.cpp", "commands/index_filter_commands.cpp", "commands/kill_op.cpp", + "commands/killcursors_cmd.cpp", "commands/list_collections.cpp", "commands/list_databases.cpp", "commands/list_indexes.cpp", diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp index 22bd1b1f191..a193655b242 100644 --- a/src/mongo/db/auth/authorization_session.cpp +++ b/src/mongo/db/auth/authorization_session.cpp @@ -192,7 +192,7 @@ Status AuthorizationSession::checkAuthForQuery(const NamespaceString& ns, const Status AuthorizationSession::checkAuthForGetMore(const NamespaceString& ns, long long cursorID) { // "ns" can be in one of three formats: "listCollections" format, "listIndexes" format, and // normal format. - if (ns.isListCollectionsGetMore()) { + if (ns.isListCollectionsCursorNS()) { // "ns" is of the form "<db>.$cmd.listCollections". Check if we can perform the // listCollections action on the database resource for "<db>". if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(ns.db()), @@ -201,10 +201,10 @@ Status AuthorizationSession::checkAuthForGetMore(const NamespaceString& ns, long str::stream() << "not authorized for listCollections getMore on " << ns.ns()); } - } else if (ns.isListIndexesGetMore()) { + } else if (ns.isListIndexesCursorNS()) { // "ns" is of the form "<db>.$cmd.listIndexes.<coll>". Check if we can perform the // listIndexes action on the "<db>.<coll>" namespace. - NamespaceString targetNS = ns.getTargetNSForListIndexesGetMore(); + NamespaceString targetNS = ns.getTargetNSForListIndexes(); if (!isAuthorizedForActionsOnNamespace(targetNS, ActionType::listIndexes)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for listIndexes getMore on " << ns.ns()); @@ -275,15 +275,15 @@ Status AuthorizationSession::checkAuthForDelete(const NamespaceString& ns, const Status AuthorizationSession::checkAuthForKillCursors(const NamespaceString& ns, long long cursorID) { // See implementation comments in checkAuthForGetMore(). This method looks very similar. - if (ns.isListCollectionsGetMore()) { + if (ns.isListCollectionsCursorNS()) { if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(ns.db()), ActionType::killCursors)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized to kill listCollections cursor on " << ns.ns()); } - } else if (ns.isListIndexesGetMore()) { - NamespaceString targetNS = ns.getTargetNSForListIndexesGetMore(); + } else if (ns.isListIndexesCursorNS()) { + NamespaceString targetNS = ns.getTargetNSForListIndexes(); if (!isAuthorizedForActionsOnNamespace(targetNS, ActionType::killCursors)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized to kill listIndexes cursor on " diff --git a/src/mongo/db/catalog/cursor_manager.cpp b/src/mongo/db/catalog/cursor_manager.cpp index 97426228ca9..4adae4eb821 100644 --- a/src/mongo/db/catalog/cursor_manager.cpp +++ b/src/mongo/db/catalog/cursor_manager.cpp @@ -213,7 +213,12 @@ bool GlobalCursorIdCache::eraseCursor(OperationContext* txn, CursorId id, bool c // If this cursor is owned by the global cursor manager, ask it to erase the cursor for us. if (globalCursorManager->ownsCursorId(id)) { - return globalCursorManager->eraseCursor(txn, id, checkAuth); + Status eraseStatus = globalCursorManager->eraseCursor(txn, id, checkAuth); + massert(28697, + eraseStatus.reason(), + eraseStatus.code() == ErrorCodes::OK || + eraseStatus.code() == ErrorCodes::CursorNotFound); + return eraseStatus.isOK(); } // If not, then the cursor must be owned by a collection. Erase the cursor under the @@ -225,7 +230,13 @@ bool GlobalCursorIdCache::eraseCursor(OperationContext* txn, CursorId id, bool c audit::logKillCursorsAuthzCheck(txn->getClient(), nss, id, ErrorCodes::CursorNotFound); return false; } - return collection->getCursorManager()->eraseCursor(txn, id, checkAuth); + + Status eraseStatus = collection->getCursorManager()->eraseCursor(txn, id, checkAuth); + massert(16089, + eraseStatus.reason(), + eraseStatus.code() == ErrorCodes::OK || + eraseStatus.code() == ErrorCodes::CursorNotFound); + return eraseStatus.isOK(); } std::size_t GlobalCursorIdCache::timeoutCursors(OperationContext* txn, int millisSinceLastCall) { @@ -495,27 +506,35 @@ void CursorManager::deregisterCursor(ClientCursor* cc) { _deregisterCursor_inlock(cc); } -bool CursorManager::eraseCursor(OperationContext* txn, CursorId id, bool checkAuth) { +Status CursorManager::eraseCursor(OperationContext* txn, CursorId id, bool shouldAudit) { stdx::lock_guard<SimpleMutex> lk(_mutex); CursorMap::iterator it = _cursors.find(id); if (it == _cursors.end()) { - if (checkAuth) + if (shouldAudit) { audit::logKillCursorsAuthzCheck(txn->getClient(), _nss, id, ErrorCodes::CursorNotFound); - return false; + } + return {ErrorCodes::CursorNotFound, str::stream() << "Cursor id not found: " << id}; } ClientCursor* cursor = it->second; - if (checkAuth) - audit::logKillCursorsAuthzCheck(txn->getClient(), _nss, id, ErrorCodes::OK); + if (cursor->isPinned()) { + if (shouldAudit) { + audit::logKillCursorsAuthzCheck( + txn->getClient(), _nss, id, ErrorCodes::OperationFailed); + } + return {ErrorCodes::OperationFailed, str::stream() << "Cannot kill pinned cursor: " << id}; + } - massert(16089, str::stream() << "Cannot kill active cursor " << id, !cursor->isPinned()); + if (shouldAudit) { + audit::logKillCursorsAuthzCheck(txn->getClient(), _nss, id, ErrorCodes::OK); + } cursor->kill(); _deregisterCursor_inlock(cursor); delete cursor; - return true; + return Status::OK(); } void CursorManager::_deregisterCursor_inlock(ClientCursor* cc) { diff --git a/src/mongo/db/catalog/cursor_manager.h b/src/mongo/db/catalog/cursor_manager.h index 81b9dd0107c..54d2a02374a 100644 --- a/src/mongo/db/catalog/cursor_manager.h +++ b/src/mongo/db/catalog/cursor_manager.h @@ -97,7 +97,15 @@ public: CursorId registerCursor(ClientCursor* cc); void deregisterCursor(ClientCursor* cc); - bool eraseCursor(OperationContext* txn, CursorId id, bool checkAuth); + /** + * Returns an OK status if the cursor was successfully erased. + * + * Returns error code CursorNotFound if the cursor id is not owned by this manager. Returns + * error code OperationFailed if attempting to erase a pinned cursor. + * + * If 'shouldAudit' is true, will perform audit logging. + */ + Status eraseCursor(OperationContext* txn, CursorId id, bool shouldAudit); /** * Returns true if the space of cursor ids that cursor manager is responsible for includes diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp index 23805bf1123..c5f32467c00 100644 --- a/src/mongo/db/commands/getmore_cmd.cpp +++ b/src/mongo/db/commands/getmore_cmd.cpp @@ -52,6 +52,7 @@ #include "mongo/db/repl/oplog.h" #include "mongo/db/service_context.h" #include "mongo/db/stats/counters.h" +#include "mongo/stdx/memory.h" #include "mongo/util/log.h" #include "mongo/util/scopeguard.h" @@ -144,7 +145,9 @@ public: // // 1) Normal cursor: we lock with "ctx" and hold it for the whole getMore. // 2) Cursor owned by global cursor manager: we don't lock anything. These cursors - // don't own any collection state. + // don't own any collection state. These cursors are generated either by the + // listCollections or listIndexes commands, as these special cursor-generating commands + // operate over catalog data rather than targeting the data within a collection. // 3) Agg cursor: we lock with "ctx", then release, then relock with "unpinDBLock" and // "unpinCollLock". This is because agg cursors handle locking internally (hence the // release), but the pin and unpin of the cursor must occur under the collection @@ -160,11 +163,10 @@ public: std::unique_ptr<Lock::CollectionLock> unpinCollLock; CursorManager* cursorManager; - CursorManager* globalCursorManager = CursorManager::getGlobalCursorManager(); - if (globalCursorManager->ownsCursorId(request.cursorid)) { - cursorManager = globalCursorManager; + if (request.nss.isListIndexesCursorNS() || request.nss.isListCollectionsCursorNS()) { + cursorManager = CursorManager::getGlobalCursorManager(); } else { - ctx.reset(new AutoGetCollectionForRead(txn, request.nss)); + ctx = stdx::make_unique<AutoGetCollectionForRead>(txn, request.nss); Collection* collection = ctx->getCollection(); if (!collection) { return appendCommandStatus(result, diff --git a/src/mongo/db/commands/killcursors_cmd.cpp b/src/mongo/db/commands/killcursors_cmd.cpp new file mode 100644 index 00000000000..27b978ca15c --- /dev/null +++ b/src/mongo/db/commands/killcursors_cmd.cpp @@ -0,0 +1,171 @@ +/** + * Copyright (C) 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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::kQuery + +#include "mongo/platform/basic.h" + +#include <memory> + +#include "mongo/base/disallow_copying.h" +#include "mongo/db/audit.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/catalog/collection.h" +#include "mongo/db/catalog/cursor_manager.h" +#include "mongo/db/client.h" +#include "mongo/db/clientcursor.h" +#include "mongo/db/commands.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/query/killcursors_request.h" +#include "mongo/db/query/killcursors_response.h" +#include "mongo/stdx/memory.h" + +namespace mongo { + +/** + * Attempt to kill a list of cursor ids. The ClientCursors will be removed from the CursorManager + * and destroyed. + */ +class KillCursorsCmd : public Command { + MONGO_DISALLOW_COPYING(KillCursorsCmd); + +public: + KillCursorsCmd() : Command("killCursors") {} + + bool isWriteCommandForConfigServer() const override { + return false; + } + + bool slaveOk() const override { + return false; + } + + bool slaveOverrideOk() const override { + return true; + } + + bool maintenanceOk() const override { + return false; + } + + bool adminOnly() const override { + return false; + } + + void help(std::stringstream& help) const override { + help << "kill a list of cursor ids"; + } + + bool shouldAffectCommandCounter() const override { + return true; + } + + Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) override { + auto statusWithRequest = KillCursorsRequest::parseFromBSON(dbname, cmdObj); + if (!statusWithRequest.isOK()) { + return statusWithRequest.getStatus(); + } + auto killCursorsRequest = std::move(statusWithRequest.getValue()); + + AuthorizationSession* as = AuthorizationSession::get(client); + + for (CursorId id : killCursorsRequest.cursorIds) { + Status authorizationStatus = as->checkAuthForKillCursors(killCursorsRequest.nss, id); + + if (!authorizationStatus.isOK()) { + audit::logKillCursorsAuthzCheck( + client, killCursorsRequest.nss, id, ErrorCodes::Unauthorized); + return authorizationStatus; + } + } + + return Status::OK(); + } + + bool run(OperationContext* txn, + const std::string& dbname, + BSONObj& cmdObj, + int options, + std::string& errmsg, + BSONObjBuilder& result) override { + auto statusWithRequest = KillCursorsRequest::parseFromBSON(dbname, cmdObj); + if (!statusWithRequest.isOK()) { + return appendCommandStatus(result, statusWithRequest.getStatus()); + } + auto killCursorsRequest = std::move(statusWithRequest.getValue()); + + std::unique_ptr<AutoGetCollectionForRead> ctx; + + CursorManager* cursorManager; + if (killCursorsRequest.nss.isListIndexesCursorNS() || + killCursorsRequest.nss.isListCollectionsCursorNS()) { + // listCollections and listIndexes are special cursor-generating commands whose cursors + // are managed globally, as they operate over catalog data rather than targeting the + // data within a collection. + cursorManager = CursorManager::getGlobalCursorManager(); + } else { + ctx = stdx::make_unique<AutoGetCollectionForRead>(txn, killCursorsRequest.nss); + Collection* collection = ctx->getCollection(); + if (!collection) { + return appendCommandStatus(result, + {ErrorCodes::InvalidNamespace, + str::stream() << "collection does not exist: " + << killCursorsRequest.nss.ns()}); + } + cursorManager = collection->getCursorManager(); + } + invariant(cursorManager); + + std::vector<CursorId> cursorsKilled; + std::vector<CursorId> cursorsNotFound; + std::vector<CursorId> cursorsAlive; + + for (CursorId id : killCursorsRequest.cursorIds) { + Status status = cursorManager->eraseCursor(txn, id, true /*shouldAudit*/); + if (status.isOK()) { + cursorsKilled.push_back(id); + } else if (status.code() == ErrorCodes::CursorNotFound) { + cursorsNotFound.push_back(id); + } else { + cursorsAlive.push_back(id); + } + + audit::logKillCursorsAuthzCheck( + txn->getClient(), killCursorsRequest.nss, id, status.code()); + } + + KillCursorsResponse killCursorsResponse(cursorsKilled, cursorsNotFound, cursorsAlive); + killCursorsResponse.addToBSON(&result); + return true; + } + +} killCursorsCmd; + +} // namespace mongo diff --git a/src/mongo/db/commands/list_collections.cpp b/src/mongo/db/commands/list_collections.cpp index b48e98598ac..ac871b0f078 100644 --- a/src/mongo/db/commands/list_collections.cpp +++ b/src/mongo/db/commands/list_collections.cpp @@ -167,7 +167,7 @@ public: std::string cursorNamespace = str::stream() << dbname << ".$cmd." << name; dassert(NamespaceString(cursorNamespace).isValid()); - dassert(NamespaceString(cursorNamespace).isListCollectionsGetMore()); + dassert(NamespaceString(cursorNamespace).isListCollectionsCursorNS()); PlanExecutor* rawExec; Status makeStatus = PlanExecutor::make(txn, diff --git a/src/mongo/db/commands/list_indexes.cpp b/src/mongo/db/commands/list_indexes.cpp index ce0394156e3..9b84c27d6d8 100644 --- a/src/mongo/db/commands/list_indexes.cpp +++ b/src/mongo/db/commands/list_indexes.cpp @@ -164,8 +164,8 @@ public: std::string cursorNamespace = str::stream() << dbname << ".$cmd." << name << "." << ns.coll(); dassert(NamespaceString(cursorNamespace).isValid()); - dassert(NamespaceString(cursorNamespace).isListIndexesGetMore()); - dassert(ns == NamespaceString(cursorNamespace).getTargetNSForListIndexesGetMore()); + dassert(NamespaceString(cursorNamespace).isListIndexesCursorNS()); + dassert(ns == NamespaceString(cursorNamespace).getTargetNSForListIndexes()); PlanExecutor* rawExec; Status makeStatus = PlanExecutor::make(txn, diff --git a/src/mongo/db/namespace_string.cpp b/src/mongo/db/namespace_string.cpp index f8c0b6751bb..afe30a3a977 100644 --- a/src/mongo/db/namespace_string.cpp +++ b/src/mongo/db/namespace_string.cpp @@ -83,22 +83,22 @@ bool legalClientSystemNS(StringData ns, bool write) { return false; } -bool NamespaceString::isListCollectionsGetMore() const { +bool NamespaceString::isListCollectionsCursorNS() const { return coll() == StringData("$cmd.listCollections", StringData::LiteralTag()); } namespace { -const StringData listIndexesGetMoreNSPrefix("$cmd.listIndexes.", StringData::LiteralTag()); +const StringData listIndexesCursorNSPrefix("$cmd.listIndexes.", StringData::LiteralTag()); } // namespace -bool NamespaceString::isListIndexesGetMore() const { - return coll().size() > listIndexesGetMoreNSPrefix.size() && - coll().startsWith(listIndexesGetMoreNSPrefix); +bool NamespaceString::isListIndexesCursorNS() const { + return coll().size() > listIndexesCursorNSPrefix.size() && + coll().startsWith(listIndexesCursorNSPrefix); } -NamespaceString NamespaceString::getTargetNSForListIndexesGetMore() const { - dassert(isListIndexesGetMore()); - return NamespaceString(db(), coll().substr(listIndexesGetMoreNSPrefix.size())); +NamespaceString NamespaceString::getTargetNSForListIndexes() const { + dassert(isListIndexesCursorNS()); + return NamespaceString(db(), coll().substr(listIndexesCursorNSPrefix.size())); } string NamespaceString::escapeDbName(const StringData dbname) { diff --git a/src/mongo/db/namespace_string.h b/src/mongo/db/namespace_string.h index 881b9e1d468..9a7e54500cf 100644 --- a/src/mongo/db/namespace_string.h +++ b/src/mongo/db/namespace_string.h @@ -139,14 +139,14 @@ public: bool isNormal() const { return normal(_ns); } - bool isListCollectionsGetMore() const; - bool isListIndexesGetMore() const; + bool isListCollectionsCursorNS() const; + bool isListIndexesCursorNS() const; /** - * Given a NamespaceString for which isListIndexesGetMore() returns true, returns the - * NamespaceString for the collection that the "listIndexesGetMore" targets. + * Given a NamespaceString for which isListIndexesCursorNS() returns true, returns the + * NamespaceString for the collection that the "listIndexes" targets. */ - NamespaceString getTargetNSForListIndexesGetMore() const; + NamespaceString getTargetNSForListIndexes() const; /** * @return true if the namespace is valid. Special namespaces for internal use are considered as diff --git a/src/mongo/db/namespace_string_test.cpp b/src/mongo/db/namespace_string_test.cpp index d1b3c7b4420..ba85cc775e3 100644 --- a/src/mongo/db/namespace_string_test.cpp +++ b/src/mongo/db/namespace_string_test.cpp @@ -88,39 +88,39 @@ TEST(NamespaceStringTest, DatabaseValidNames) { ASSERT(NamespaceString::normal("local.oplog.$main")); } -TEST(NamespaceStringTest, ListCollectionsGetMore) { - ASSERT(NamespaceString("test.$cmd.listCollections").isListCollectionsGetMore()); +TEST(NamespaceStringTest, ListCollectionsCursorNS) { + ASSERT(NamespaceString("test.$cmd.listCollections").isListCollectionsCursorNS()); - ASSERT(!NamespaceString("test.foo").isListCollectionsGetMore()); - ASSERT(!NamespaceString("test.foo.$cmd.listCollections").isListCollectionsGetMore()); - ASSERT(!NamespaceString("test.$cmd.").isListCollectionsGetMore()); - ASSERT(!NamespaceString("test.$cmd.foo.").isListCollectionsGetMore()); - ASSERT(!NamespaceString("test.$cmd.listCollections.").isListCollectionsGetMore()); - ASSERT(!NamespaceString("test.$cmd.listIndexes").isListCollectionsGetMore()); - ASSERT(!NamespaceString("test.$cmd.listIndexes.foo").isListCollectionsGetMore()); + ASSERT(!NamespaceString("test.foo").isListCollectionsCursorNS()); + ASSERT(!NamespaceString("test.foo.$cmd.listCollections").isListCollectionsCursorNS()); + ASSERT(!NamespaceString("test.$cmd.").isListCollectionsCursorNS()); + ASSERT(!NamespaceString("test.$cmd.foo.").isListCollectionsCursorNS()); + ASSERT(!NamespaceString("test.$cmd.listCollections.").isListCollectionsCursorNS()); + ASSERT(!NamespaceString("test.$cmd.listIndexes").isListCollectionsCursorNS()); + ASSERT(!NamespaceString("test.$cmd.listIndexes.foo").isListCollectionsCursorNS()); } -TEST(NamespaceStringTest, ListIndexesGetMore) { +TEST(NamespaceStringTest, ListIndexesCursorNS) { NamespaceString ns1("test.$cmd.listIndexes.f"); - ASSERT(ns1.isListIndexesGetMore()); - ASSERT("test.f" == ns1.getTargetNSForListIndexesGetMore().ns()); + ASSERT(ns1.isListIndexesCursorNS()); + ASSERT("test.f" == ns1.getTargetNSForListIndexes().ns()); NamespaceString ns2("test.$cmd.listIndexes.foo"); - ASSERT(ns2.isListIndexesGetMore()); - ASSERT("test.foo" == ns2.getTargetNSForListIndexesGetMore().ns()); + ASSERT(ns2.isListIndexesCursorNS()); + ASSERT("test.foo" == ns2.getTargetNSForListIndexes().ns()); NamespaceString ns3("test.$cmd.listIndexes.foo.bar"); - ASSERT(ns3.isListIndexesGetMore()); - ASSERT("test.foo.bar" == ns3.getTargetNSForListIndexesGetMore().ns()); - - ASSERT(!NamespaceString("test.foo").isListIndexesGetMore()); - ASSERT(!NamespaceString("test.foo.$cmd.listIndexes").isListIndexesGetMore()); - ASSERT(!NamespaceString("test.$cmd.").isListIndexesGetMore()); - ASSERT(!NamespaceString("test.$cmd.foo.").isListIndexesGetMore()); - ASSERT(!NamespaceString("test.$cmd.listIndexes").isListIndexesGetMore()); - ASSERT(!NamespaceString("test.$cmd.listIndexes.").isListIndexesGetMore()); - ASSERT(!NamespaceString("test.$cmd.listCollections").isListIndexesGetMore()); - ASSERT(!NamespaceString("test.$cmd.listCollections.foo").isListIndexesGetMore()); + ASSERT(ns3.isListIndexesCursorNS()); + ASSERT("test.foo.bar" == ns3.getTargetNSForListIndexes().ns()); + + ASSERT(!NamespaceString("test.foo").isListIndexesCursorNS()); + ASSERT(!NamespaceString("test.foo.$cmd.listIndexes").isListIndexesCursorNS()); + ASSERT(!NamespaceString("test.$cmd.").isListIndexesCursorNS()); + ASSERT(!NamespaceString("test.$cmd.foo.").isListIndexesCursorNS()); + ASSERT(!NamespaceString("test.$cmd.listIndexes").isListIndexesCursorNS()); + ASSERT(!NamespaceString("test.$cmd.listIndexes.").isListIndexesCursorNS()); + ASSERT(!NamespaceString("test.$cmd.listCollections").isListIndexesCursorNS()); + ASSERT(!NamespaceString("test.$cmd.listCollections.foo").isListIndexesCursorNS()); } TEST(NamespaceStringTest, CollectionComponentValidNames) { diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript index bb209a8ae47..e899da3961b 100644 --- a/src/mongo/db/query/SConscript +++ b/src/mongo/db/query/SConscript @@ -105,11 +105,14 @@ env.Library( 'cursor_responses.cpp', 'find_and_modify_request.cpp', 'getmore_request.cpp', + 'killcursors_request.cpp', + 'killcursors_response.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/bson/bson', '$BUILD_DIR/mongo/db/common', '$BUILD_DIR/mongo/db/namespace_string', + '$BUILD_DIR/mongo/rpc/command_status', ] ) @@ -119,6 +122,8 @@ env.CppUnitTest( 'count_request_test.cpp', 'find_and_modify_request_test.cpp', 'getmore_request_test.cpp', + 'killcursors_request_test.cpp', + 'killcursors_response_test.cpp', ], LIBDEPS=[ 'command_request_response', diff --git a/src/mongo/db/query/find.cpp b/src/mongo/db/query/find.cpp index 2283bc66e9f..a142377bab7 100644 --- a/src/mongo/db/query/find.cpp +++ b/src/mongo/db/query/find.cpp @@ -32,7 +32,6 @@ #include "mongo/db/query/find.h" - #include "mongo/client/dbclientinterface.h" #include "mongo/db/catalog/collection.h" #include "mongo/db/catalog/database_holder.h" @@ -57,6 +56,7 @@ #include "mongo/s/chunk_version.h" #include "mongo/s/d_state.h" #include "mongo/s/stale_exception.h" +#include "mongo/stdx/memory.h" #include "mongo/util/fail_point_service.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" @@ -266,8 +266,10 @@ QueryResult::View getMore(OperationContext* txn, // or none of the getMore, or part of the getMore. The three cases in detail: // // 1) Normal cursor: we lock with "ctx" and hold it for the whole getMore. - // 2) Cursor owned by global cursor manager: we don't lock anything. These cursors don't - // own any collection state. + // 2) Cursor owned by global cursor manager: we don't lock anything. These cursors don't own + // any collection state. These cursors are generated either by the listCollections or + // listIndexes commands, as these special cursor-generating commands operate over catalog + // data rather than targeting the data within a collection. // 3) Agg cursor: we lock with "ctx", then release, then relock with "unpinDBLock" and // "unpinCollLock". This is because agg cursors handle locking internally (hence the // release), but the pin and unpin of the cursor must occur under the collection lock. @@ -283,11 +285,13 @@ QueryResult::View getMore(OperationContext* txn, unique_ptr<Lock::CollectionLock> unpinCollLock; CursorManager* cursorManager; - CursorManager* globalCursorManager = CursorManager::getGlobalCursorManager(); - if (globalCursorManager->ownsCursorId(cursorid)) { - cursorManager = globalCursorManager; + if (nss.isListIndexesCursorNS() || nss.isListCollectionsCursorNS()) { + // List collections and list indexes are special cursor-generating commands whose + // cursors are managed globally, as they operate over catalog data rather than targeting + // the data within a collection. + cursorManager = CursorManager::getGlobalCursorManager(); } else { - ctx.reset(new AutoGetCollectionForRead(txn, nss)); + ctx = stdx::make_unique<AutoGetCollectionForRead>(txn, nss); Collection* collection = ctx->getCollection(); uassert(17356, "collection dropped between getMore calls", collection); cursorManager = collection->getCursorManager(); diff --git a/src/mongo/db/query/killcursors_request.cpp b/src/mongo/db/query/killcursors_request.cpp new file mode 100644 index 00000000000..6d95311accd --- /dev/null +++ b/src/mongo/db/query/killcursors_request.cpp @@ -0,0 +1,105 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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::kQuery + +#include "mongo/platform/basic.h" + +#include "mongo/db/query/killcursors_request.h" + +namespace mongo { + +namespace { + +const char kCmdName[] = "killCursors"; +const char kCursorsField[] = "cursors"; + +} // namespace + +KillCursorsRequest::KillCursorsRequest(const NamespaceString& nsString, + const std::vector<CursorId>& ids) + : nss(nsString), cursorIds(ids) {} + +StatusWith<KillCursorsRequest> KillCursorsRequest::parseFromBSON(const std::string& dbname, + const BSONObj& cmdObj) { + if (!str::equals(cmdObj.firstElement().fieldName(), kCmdName)) { + return {ErrorCodes::FailedToParse, + str::stream() << "First field name must be '" << kCmdName << "' in: " << cmdObj}; + } + + if (cmdObj.firstElement().type() != BSONType::String) { + return {ErrorCodes::FailedToParse, + str::stream() << "First parameter must be a string in: " << cmdObj}; + } + + std::string collName = cmdObj.firstElement().String(); + const NamespaceString nss(dbname, collName); + if (!nss.isValid()) { + return {ErrorCodes::InvalidNamespace, + str::stream() << "Invalid collection name: " << nss.ns()}; + } + + if (cmdObj[kCursorsField].type() != BSONType::Array) { + return {ErrorCodes::FailedToParse, + str::stream() << "Field '" << kCursorsField + << "' must be of type array in: " << cmdObj}; + } + + std::vector<CursorId> cursorIds; + for (BSONElement cursorEl : cmdObj[kCursorsField].Obj()) { + if (cursorEl.type() != BSONType::NumberLong) { + return {ErrorCodes::FailedToParse, + str::stream() << "Field '" << kCursorsField + << "' contains an element that is not of type long: " + << cursorEl}; + } + cursorIds.push_back(cursorEl.numberLong()); + } + + if (cursorIds.empty()) { + return {ErrorCodes::BadValue, + str::stream() << "Must specify at least one cursor id in: " << cmdObj}; + } + + return KillCursorsRequest(nss, cursorIds); +} + +BSONObj KillCursorsRequest::toBSON() const { + BSONObjBuilder builder; + + builder.append(kCmdName, nss.coll()); + BSONArrayBuilder idsBuilder(builder.subarrayStart(kCursorsField)); + for (CursorId id : cursorIds) { + idsBuilder.append(id); + } + idsBuilder.doneFast(); + + return builder.obj(); +} + +} // namespace mongo diff --git a/src/mongo/db/query/killcursors_request.h b/src/mongo/db/query/killcursors_request.h new file mode 100644 index 00000000000..34c8595eff6 --- /dev/null +++ b/src/mongo/db/query/killcursors_request.h @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#pragma once + +#include <vector> + +#include "mongo/base/status_with.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/db/clientcursor.h" +#include "mongo/db/namespace_string.h" + +namespace mongo { + +struct KillCursorsRequest { + /** + * Constructs from a namespace and a list of cursors ids. + */ + KillCursorsRequest(const NamespaceString& nsString, const std::vector<CursorId>& ids); + + static StatusWith<KillCursorsRequest> parseFromBSON(const std::string& dbname, + const BSONObj& cmdObj); + + BSONObj toBSON() const; + + const NamespaceString nss; + const std::vector<CursorId> cursorIds; +}; + +} // namespace mongo diff --git a/src/mongo/db/query/killcursors_request_test.cpp b/src/mongo/db/query/killcursors_request_test.cpp new file mode 100644 index 00000000000..74ce8bfa31e --- /dev/null +++ b/src/mongo/db/query/killcursors_request_test.cpp @@ -0,0 +1,138 @@ +/** + * Copyright 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/query/killcursors_request.h" + +#include "mongo/db/clientcursor.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { + +namespace { + +TEST(KillCursorsRequestTest, parseFromBSONSuccess) { + StatusWith<KillCursorsRequest> result = + KillCursorsRequest::parseFromBSON("db", + BSON("killCursors" + << "coll" + << "cursors" + << BSON_ARRAY(CursorId(123) << CursorId(456)))); + ASSERT_OK(result.getStatus()); + KillCursorsRequest request = result.getValue(); + ASSERT_EQ(request.nss.ns(), "db.coll"); + ASSERT_EQ(request.cursorIds.size(), 2U); + ASSERT_EQ(request.cursorIds[0], CursorId(123)); + ASSERT_EQ(request.cursorIds[1], CursorId(456)); +} + +TEST(KillCursorsRequestTest, parseFromBSONFirstFieldNotKillCursors) { + StatusWith<KillCursorsRequest> result = + KillCursorsRequest::parseFromBSON("db", + BSON("foobar" + << "coll" + << "cursors" + << BSON_ARRAY(CursorId(123) << CursorId(456)))); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse); +} + +TEST(KillCursorsRequestTest, parseFromBSONFirstFieldNotString) { + StatusWith<KillCursorsRequest> result = KillCursorsRequest::parseFromBSON( + "db", BSON("killCursors" << 99 << "cursors" << BSON_ARRAY(CursorId(123) << CursorId(456)))); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse); +} + +TEST(KillCursorsRequestTest, parseFromBSONInvalidNamespace) { + StatusWith<KillCursorsRequest> result = + KillCursorsRequest::parseFromBSON("", + BSON("killCursors" + << "coll" + << "cursors" + << BSON_ARRAY(CursorId(123) << CursorId(456)))); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::InvalidNamespace); +} + +TEST(KillCursorsRequestTest, parseFromBSONCursorFieldMissing) { + StatusWith<KillCursorsRequest> result = KillCursorsRequest::parseFromBSON("db", + BSON("killCursors" + << "coll")); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse); +} + +TEST(KillCursorsRequestTest, parseFromBSONCursorFieldNotArray) { + StatusWith<KillCursorsRequest> result = + KillCursorsRequest::parseFromBSON("db", + BSON("killCursors" + << "coll" + << "cursors" << CursorId(123))); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse); +} + +TEST(KillCursorsRequestTest, parseFromBSONCursorFieldEmptyArray) { + StatusWith<KillCursorsRequest> result = + KillCursorsRequest::parseFromBSON("db", + BSON("killCursors" + << "coll" + << "cursors" << BSONArrayBuilder().arr())); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::BadValue); +} + + +TEST(KillCursorsRequestTest, parseFromBSONCursorFieldContainsEltOfWrongType) { + StatusWith<KillCursorsRequest> result = + KillCursorsRequest::parseFromBSON("db", + BSON("killCursors" + << "coll" + << "cursors" + << BSON_ARRAY(CursorId(123) << "foo" + << CursorId(456)))); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse); +} + +TEST(KillCursorsRequestTest, toBSON) { + const NamespaceString nss("db.coll"); + std::vector<CursorId> cursorIds = {CursorId(123), CursorId(456)}; + KillCursorsRequest request(nss, cursorIds); + BSONObj requestObj = request.toBSON(); + BSONObj expectedObj = BSON("killCursors" + << "coll" + << "cursors" << BSON_ARRAY(CursorId(123) << CursorId(456))); + ASSERT_EQ(requestObj, expectedObj); +} + +} // namespace + +} // namespace mongo diff --git a/src/mongo/db/query/killcursors_response.cpp b/src/mongo/db/query/killcursors_response.cpp new file mode 100644 index 00000000000..4117e4dafe0 --- /dev/null +++ b/src/mongo/db/query/killcursors_response.cpp @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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::kQuery + +#include "mongo/platform/basic.h" + +#include "mongo/db/query/killcursors_response.h" + +#include "mongo/rpc/get_status_from_command_result.h" + +namespace mongo { + +namespace { + +const char kKilledField[] = "cursorsKilled"; +const char kNotFoundField[] = "cursorsNotFound"; +const char kAliveField[] = "cursorsAlive"; + +Status fillOutCursorArray(const BSONObj& cmdResponse, + StringData fieldName, + std::vector<CursorId>* cursorIds) { + BSONElement elt = cmdResponse[fieldName]; + if (elt.eoo()) { + return Status::OK(); + } + + if (elt.type() != BSONType::Array) { + return {ErrorCodes::FailedToParse, + str::stream() << "Field '" << fieldName + << "' must be of type array in: " << cmdResponse}; + } + + for (BSONElement cursorElt : elt.Obj()) { + if (cursorElt.type() != BSONType::NumberLong) { + return {ErrorCodes::FailedToParse, + str::stream() << "Field '" << fieldName + << "' contains an element that is not of type long: " + << cursorElt}; + } + cursorIds->push_back(cursorElt.numberLong()); + } + + if (cursorIds->empty()) { + return {ErrorCodes::BadValue, + str::stream() << "Must specify at least one cursor id for field '" << fieldName + << "' in: " << cmdResponse}; + } + + return Status::OK(); +} + +void addCursorArrayToBSON(const std::vector<CursorId>& cursorIds, + StringData fieldName, + BSONObjBuilder* builder) { + BSONArrayBuilder idsBuilder(builder->subarrayStart(fieldName)); + for (CursorId id : cursorIds) { + idsBuilder.append(id); + } + idsBuilder.doneFast(); +} + +} // namespace + +KillCursorsResponse::KillCursorsResponse() {} + +KillCursorsResponse::KillCursorsResponse(const std::vector<CursorId>& killed, + const std::vector<CursorId>& notFound, + const std::vector<CursorId>& alive) + : cursorsKilled(killed), cursorsNotFound(notFound), cursorsAlive(alive) {} + +StatusWith<KillCursorsResponse> KillCursorsResponse::parseFromBSON(const BSONObj& cmdResponse) { + Status cmdStatus = getStatusFromCommandResult(cmdResponse); + if (!cmdStatus.isOK()) { + return cmdStatus; + } + + std::vector<CursorId> cursorsKilled; + Status killedStatus = fillOutCursorArray(cmdResponse, kKilledField, &cursorsKilled); + if (!killedStatus.isOK()) { + return killedStatus; + } + + std::vector<CursorId> cursorsNotFound; + Status notFoundStatus = fillOutCursorArray(cmdResponse, kNotFoundField, &cursorsNotFound); + if (!notFoundStatus.isOK()) { + return notFoundStatus; + } + + std::vector<CursorId> cursorsAlive; + Status aliveStatus = fillOutCursorArray(cmdResponse, kAliveField, &cursorsAlive); + if (!aliveStatus.isOK()) { + return aliveStatus; + } + + return KillCursorsResponse(cursorsKilled, cursorsNotFound, cursorsAlive); +} + +BSONObj KillCursorsResponse::toBSON() const { + BSONObjBuilder builder; + addToBSON(&builder); + builder.append("ok", 1.0); + return builder.obj(); +} + +void KillCursorsResponse::addToBSON(BSONObjBuilder* builder) const { + addCursorArrayToBSON(cursorsKilled, kKilledField, builder); + addCursorArrayToBSON(cursorsNotFound, kNotFoundField, builder); + addCursorArrayToBSON(cursorsAlive, kAliveField, builder); +} + +} // namespace mongo diff --git a/src/mongo/db/query/killcursors_response.h b/src/mongo/db/query/killcursors_response.h new file mode 100644 index 00000000000..4f0d7838dfc --- /dev/null +++ b/src/mongo/db/query/killcursors_response.h @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#pragma once + +#include <vector> + +#include "mongo/base/status_with.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/db/clientcursor.h" + +namespace mongo { + +struct KillCursorsResponse { + /** + * Constructs an empty response. + */ + KillCursorsResponse(); + + /** + * Constructs a response from known lists of cursor ids. + */ + KillCursorsResponse(const std::vector<CursorId>& killed, + const std::vector<CursorId>& notFound, + const std::vector<CursorId>& alive); + + static StatusWith<KillCursorsResponse> parseFromBSON(const BSONObj& cmdResponse); + + BSONObj toBSON() const; + + void addToBSON(BSONObjBuilder* builder) const; + + const std::vector<CursorId> cursorsKilled; + const std::vector<CursorId> cursorsNotFound; + const std::vector<CursorId> cursorsAlive; +}; + +} // namespace mongo diff --git a/src/mongo/db/query/killcursors_response_test.cpp b/src/mongo/db/query/killcursors_response_test.cpp new file mode 100644 index 00000000000..fc7e029fe86 --- /dev/null +++ b/src/mongo/db/query/killcursors_response_test.cpp @@ -0,0 +1,124 @@ +/** + * Copyright 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/query/killcursors_response.h" + +#include "mongo/db/clientcursor.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { + +namespace { + +TEST(KillCursorsResponseTest, parseFromBSONSuccess) { + StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON(BSON( + "cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound" + << BSON_ARRAY(CursorId(456) << CursorId(6)) << "cursorsAlive" + << BSON_ARRAY(CursorId(7) << CursorId(8) << CursorId(9)) << "ok" << 1.0)); + ASSERT_OK(result.getStatus()); + KillCursorsResponse response = result.getValue(); + ASSERT_EQ(response.cursorsKilled.size(), 1U); + ASSERT_EQ(response.cursorsKilled[0], CursorId(123)); + ASSERT_EQ(response.cursorsNotFound.size(), 2U); + ASSERT_EQ(response.cursorsNotFound[0], CursorId(456)); + ASSERT_EQ(response.cursorsNotFound[1], CursorId(6)); + ASSERT_EQ(response.cursorsAlive.size(), 3U); + ASSERT_EQ(response.cursorsAlive[0], CursorId(7)); + ASSERT_EQ(response.cursorsAlive[1], CursorId(8)); + ASSERT_EQ(response.cursorsAlive[2], CursorId(9)); +} + +TEST(KillCursorsResponseTest, parseFromBSONSuccessOmitCursorsAlive) { + StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON( + BSON("cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound" + << BSON_ARRAY(CursorId(456) << CursorId(6)) << "ok" << 1.0)); + ASSERT_OK(result.getStatus()); + KillCursorsResponse response = result.getValue(); + ASSERT_EQ(response.cursorsKilled.size(), 1U); + ASSERT_EQ(response.cursorsKilled[0], CursorId(123)); + ASSERT_EQ(response.cursorsNotFound.size(), 2U); + ASSERT_EQ(response.cursorsNotFound[0], CursorId(456)); + ASSERT_EQ(response.cursorsNotFound[1], CursorId(6)); + ASSERT(response.cursorsAlive.empty()); +} + +TEST(KillCursorsResponseTest, parseFromBSONCommandNotOk) { + StatusWith<KillCursorsResponse> result = + KillCursorsResponse::parseFromBSON(BSON("ok" << 0.0 << "code" << 123 << "errmsg" + << "does not work")); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), 123); + ASSERT_EQ(result.getStatus().reason(), "does not work"); +} + +TEST(KillCursorsResponseTest, parseFromBSONFieldNotArray) { + StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON(BSON( + "cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound" + << "foobar" + << "cursorsAlive" << BSON_ARRAY(CursorId(7) << CursorId(8) << CursorId(9)) + << "ok" << 1.0)); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse); +} + +TEST(KillCursorsResponseTest, parseFromBSONArrayContainsInvalidElement) { + StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON( + BSON("cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound" + << BSON_ARRAY(CursorId(456) << CursorId(6)) << "cursorsAlive" + << BSON_ARRAY(CursorId(7) << "foobar" << CursorId(9)) << "ok" << 1.0)); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse); +} + +TEST(KillCursorsResponseTest, parseFromBSONArrayEmpty) { + StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON( + BSON("cursorsKilled" << BSONArrayBuilder().arr() << "cursorsNotFound" + << BSON_ARRAY(CursorId(456) << CursorId(6)) << "cursorsAlive" + << BSON_ARRAY(CursorId(7) << "foobar" << CursorId(9)) << "ok" << 1.0)); + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(result.getStatus().code(), ErrorCodes::BadValue); +} + +TEST(KillCursorsResponseTest, toBSON) { + std::vector<CursorId> killed = {CursorId(123)}; + std::vector<CursorId> notFound = {CursorId(456), CursorId(6)}; + std::vector<CursorId> alive = {CursorId(7), CursorId(8), CursorId(9)}; + KillCursorsResponse response(killed, notFound, alive); + BSONObj responseObj = response.toBSON(); + BSONObj expectedResponse = BSON( + "cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound" + << BSON_ARRAY(CursorId(456) << CursorId(6)) << "cursorsAlive" + << BSON_ARRAY(CursorId(7) << CursorId(8) << CursorId(9)) << "ok" << 1.0); + ASSERT_EQ(responseObj, expectedResponse); +} + +} // namespace + +} // namespace mongo diff --git a/src/mongo/dbtests/querytests.cpp b/src/mongo/dbtests/querytests.cpp index d9c77b80f2c..4912aa1b3a5 100644 --- a/src/mongo/dbtests/querytests.cpp +++ b/src/mongo/dbtests/querytests.cpp @@ -1669,7 +1669,7 @@ public: { OldClientWriteContext ctx(&_txn, ns()); ClientCursorPin pinCursor(ctx.db()->getCollection(ns())->getCursorManager(), cursorId); - string expectedAssertion = str::stream() << "Cannot kill active cursor " << cursorId; + string expectedAssertion = str::stream() << "Cannot kill pinned cursor: " << cursorId; ASSERT_THROWS_WHAT(CursorManager::eraseCursorGlobal(&_txn, cursorId), MsgAssertionException, expectedAssertion); |