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/mongo/db | |
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/mongo/db')
-rw-r--r-- | src/mongo/db/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/catalog/cursor_manager.cpp | 37 | ||||
-rw-r--r-- | src/mongo/db/catalog/cursor_manager.h | 10 | ||||
-rw-r--r-- | src/mongo/db/commands/getmore_cmd.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/commands/killcursors_cmd.cpp | 171 | ||||
-rw-r--r-- | src/mongo/db/commands/list_collections.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/commands/list_indexes.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/namespace_string.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/namespace_string.h | 10 | ||||
-rw-r--r-- | src/mongo/db/namespace_string_test.cpp | 50 | ||||
-rw-r--r-- | src/mongo/db/query/SConscript | 5 | ||||
-rw-r--r-- | src/mongo/db/query/find.cpp | 18 | ||||
-rw-r--r-- | src/mongo/db/query/killcursors_request.cpp | 105 | ||||
-rw-r--r-- | src/mongo/db/query/killcursors_request.h | 55 | ||||
-rw-r--r-- | src/mongo/db/query/killcursors_request_test.cpp | 138 | ||||
-rw-r--r-- | src/mongo/db/query/killcursors_response.cpp | 137 | ||||
-rw-r--r-- | src/mongo/db/query/killcursors_response.h | 63 | ||||
-rw-r--r-- | src/mongo/db/query/killcursors_response_test.cpp | 124 |
19 files changed, 901 insertions, 69 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 |