summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorJason Carey <jcarey@argv.me>2017-08-01 11:29:51 -0400
committerJason Carey <jcarey@argv.me>2017-08-17 12:16:40 -0400
commitcb20cab73393fbf725627d5f7b1af5e797866870 (patch)
treed2282b9e7490f7e73ead3cf35a746d0f126b42fd /src/mongo
parent427647f7cea35a782f3532c02d3e16323c4aea99 (diff)
downloadmongo-cb20cab73393fbf725627d5f7b1af5e797866870.tar.gz
SERVER-28338 KillSessions Support
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/crypto/sha_block.h14
-rw-r--r--src/mongo/db/SConscript28
-rw-r--r--src/mongo/db/auth/action_types.txt1
-rw-r--r--src/mongo/db/auth/authorization_session.cpp17
-rw-r--r--src/mongo/db/auth/authorization_session.h21
-rw-r--r--src/mongo/db/auth/role_graph_builtin_roles.cpp1
-rw-r--r--src/mongo/db/commands.cpp16
-rw-r--r--src/mongo/db/commands.h31
-rw-r--r--src/mongo/db/commands/SConscript4
-rw-r--r--src/mongo/db/commands/kill_all_sessions_by_pattern_command.cpp119
-rw-r--r--src/mongo/db/commands/kill_all_sessions_command.cpp107
-rw-r--r--src/mongo/db/commands/kill_sessions_command.cpp134
-rw-r--r--src/mongo/db/cursor_manager.cpp27
-rw-r--r--src/mongo/db/cursor_manager.h8
-rw-r--r--src/mongo/db/db.cpp6
-rw-r--r--src/mongo/db/kill_sessions.cpp131
-rw-r--r--src/mongo/db/kill_sessions.h102
-rw-r--r--src/mongo/db/kill_sessions.idl81
-rw-r--r--src/mongo/db/kill_sessions_common.cpp91
-rw-r--r--src/mongo/db/kill_sessions_common.h141
-rw-r--r--src/mongo/db/kill_sessions_local.cpp63
-rw-r--r--src/mongo/db/kill_sessions_local.h44
-rw-r--r--src/mongo/db/logical_session_id.h3
-rw-r--r--src/mongo/db/logical_session_id_test.cpp35
-rw-r--r--src/mongo/db/pipeline/pipeline_d.cpp5
-rw-r--r--src/mongo/db/s/sharding_task_executor.cpp29
-rw-r--r--src/mongo/db/service_entry_point_mongod.cpp4
-rw-r--r--src/mongo/db/session_killer.cpp200
-rw-r--r--src/mongo/db/session_killer.h145
-rw-r--r--src/mongo/executor/SConscript11
-rw-r--r--src/mongo/executor/async_multicaster.cpp104
-rw-r--r--src/mongo/executor/async_multicaster.h79
-rw-r--r--src/mongo/s/commands/SConscript4
-rw-r--r--src/mongo/s/commands/cluster_multicast.cpp158
-rw-r--r--src/mongo/s/commands/cluster_multicast.idl37
-rw-r--r--src/mongo/s/commands/kill_sessions_remote.cpp137
-rw-r--r--src/mongo/s/commands/kill_sessions_remote.h44
-rw-r--r--src/mongo/s/commands/strategy.cpp4
-rw-r--r--src/mongo/s/query/SConscript2
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.cpp12
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.h5
-rw-r--r--src/mongo/s/server.cpp7
43 files changed, 2180 insertions, 33 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 66fdcbc1909..96c41128916 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -290,6 +290,7 @@ env.Library(
'db/index_d',
'db/initialize_snmp',
'db/keys_collection_manager_direct',
+ 'db/kill_sessions_local',
'db/logical_session_cache_factory_mongod',
'db/mongod_options',
'db/mongodandmongos',
diff --git a/src/mongo/crypto/sha_block.h b/src/mongo/crypto/sha_block.h
index 443ef36ebf4..da61684976e 100644
--- a/src/mongo/crypto/sha_block.h
+++ b/src/mongo/crypto/sha_block.h
@@ -31,6 +31,7 @@
#include <array>
#include <cstddef>
#include <string>
+#include <third_party/murmurhash3/MurmurHash3.h>
#include <vector>
#include "mongo/base/data_range.h"
@@ -192,6 +193,19 @@ public:
return !(*this == other);
}
+ /**
+ * Custom hasher so SHABlocks can be used in unordered data structures.
+ *
+ * ex: std::unordered_set<SHABlock, SHABlock::Hash> shaSet;
+ */
+ struct Hash {
+ std::size_t operator()(const SHABlock& shaBlock) const {
+ uint32_t hash;
+ MurmurHash3_x86_32(shaBlock.data(), SHABlock::kHashLength, 0, &hash);
+ return hash;
+ }
+ };
+
private:
// The backing array of bytes for the sha block
HashType _hash;
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index 0d42768d960..e0b2a253e61 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -1081,6 +1081,34 @@ env.Library(
)
env.Library(
+ target='kill_sessions',
+ source=[
+ 'kill_sessions.cpp',
+ 'kill_sessions_common.cpp',
+ 'session_killer.cpp',
+ env.Idlc('kill_sessions.idl')[0],
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/auth/authcore',
+ '$BUILD_DIR/mongo/idl/idl_parser',
+ ],
+)
+
+env.Library(
+ target='kill_sessions_local',
+ source=[
+ 'kill_sessions_local.cpp',
+ ],
+ LIBDEPS=[
+ 'clientcursor',
+ 'kill_sessions',
+ ],
+)
+
+env.Library(
target='signed_logical_time',
source=[
'signed_logical_time.cpp',
diff --git a/src/mongo/db/auth/action_types.txt b/src/mongo/db/auth/action_types.txt
index dd9e6a9c8cc..d3b2745d319 100644
--- a/src/mongo/db/auth/action_types.txt
+++ b/src/mongo/db/auth/action_types.txt
@@ -64,6 +64,7 @@
"insert",
"internal", # Special action type that represents internal actions
"invalidateUserCache",
+"killAnySession",
"killCursors",
"killop",
"listCollections",
diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp
index 4c72c4677e3..201acb11b00 100644
--- a/src/mongo/db/auth/authorization_session.cpp
+++ b/src/mongo/db/auth/authorization_session.cpp
@@ -117,6 +117,23 @@ using UserHolder = std::unique_ptr<User, UserReleaser>;
} // namespace
+AuthorizationSession::ScopedImpersonate::ScopedImpersonate(AuthorizationSession* authSession,
+ std::vector<UserName>* users,
+ std::vector<RoleName>* roles)
+ : _authSession(*authSession), _users(*users), _roles(*roles) {
+ swap();
+}
+
+AuthorizationSession::ScopedImpersonate::~ScopedImpersonate() {
+ swap();
+}
+
+void AuthorizationSession::ScopedImpersonate::swap() {
+ using std::swap;
+ swap(_authSession._impersonatedUserNames, _users);
+ swap(_authSession._impersonatedRoleNames, _roles);
+}
+
AuthorizationSession::AuthorizationSession(std::unique_ptr<AuthzSessionExternalState> externalState)
: _externalState(std::move(externalState)), _impersonationFlag(false) {}
diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h
index 7da7d21c9a1..edb838c1959 100644
--- a/src/mongo/db/auth/authorization_session.h
+++ b/src/mongo/db/auth/authorization_session.h
@@ -70,6 +70,27 @@ class AuthorizationSession {
public:
/**
+ * Provides a way to swap out impersonate data for the duration of the ScopedImpersonate's
+ * lifetime.
+ */
+ class ScopedImpersonate {
+ public:
+ ScopedImpersonate(AuthorizationSession* authSession,
+ std::vector<UserName>* users,
+ std::vector<RoleName>* roles);
+ ~ScopedImpersonate();
+
+ private:
+ void swap();
+
+ AuthorizationSession& _authSession;
+ std::vector<UserName>& _users;
+ std::vector<RoleName>& _roles;
+ };
+
+ friend class ScopedImpersonate;
+
+ /**
* Gets the AuthorizationSession associated with the given "client", or nullptr.
*
* The "client" object continues to own the returned AuthorizationSession.
diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp
index a9ee381c277..36cc6b4c15a 100644
--- a/src/mongo/db/auth/role_graph_builtin_roles.cpp
+++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp
@@ -216,6 +216,7 @@ MONGO_INITIALIZER(AuthorizationBuiltinRoles)(InitializerContext* context) {
<< ActionType::flushRouterConfig // clusterManager gets this also
<< ActionType::fsync
<< ActionType::invalidateUserCache // userAdminAnyDatabase gets this also
+ << ActionType::killAnySession
<< ActionType::killop
<< ActionType::replSetResizeOplog
<< ActionType::resync; // clusterManager gets this also
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp
index 5f5982d76f4..c8f99250182 100644
--- a/src/mongo/db/commands.cpp
+++ b/src/mongo/db/commands.cpp
@@ -189,7 +189,7 @@ BSONObj Command::runCommandDirectly(OperationContext* opCtx, const OpMsgRequest&
BSONObjBuilder out;
try {
- bool ok = command->enhancedRun(opCtx, request, out);
+ bool ok = command->publicRun(opCtx, request, out);
appendCommandStatus(out, ok);
} catch (const StaleConfigException& ex) {
// These exceptions are intended to be handled at a higher level and cannot losslessly
@@ -321,6 +321,20 @@ Status Command::checkAuthorization(Command* c,
return status;
}
+bool Command::publicRun(OperationContext* opCtx,
+ const OpMsgRequest& request,
+ BSONObjBuilder& result) {
+ try {
+ return enhancedRun(opCtx, request, result);
+ } catch (const DBException& e) {
+ if (e.code() == ErrorCodes::Unauthorized) {
+ audit::logCommandAuthzCheck(
+ opCtx->getClient(), request, this, ErrorCodes::Unauthorized);
+ }
+ throw;
+ }
+}
+
bool Command::isHelpRequest(const BSONElement& helpElem) {
return !helpElem.eoo() && helpElem.trueValue();
}
diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h
index dfcda118277..79641088af1 100644
--- a/src/mongo/db/commands.h
+++ b/src/mongo/db/commands.h
@@ -92,18 +92,6 @@ public:
virtual std::size_t reserveBytesForReply() const = 0;
/**
- * Runs the command.
- *
- * The default implementation verifies that request has no document sections then forwards to
- * BasicCommand::run().
- *
- * For now commands should only implement if they need access to OP_MSG-specific functionality.
- */
- virtual bool enhancedRun(OperationContext* opCtx,
- const OpMsgRequest& request,
- BSONObjBuilder& result) = 0;
-
- /**
* supportsWriteConcern returns true if this command should be parsed for a writeConcern
* field and wait for that write concern to be satisfied after the command runs.
*
@@ -349,6 +337,13 @@ public:
_commandsFailed.increment();
}
+ /**
+ * Runs the command.
+ *
+ * Forwards to enhancedRun, but additionally runs audit checks if run throws unauthorized.
+ */
+ bool publicRun(OperationContext* opCtx, const OpMsgRequest& request, BSONObjBuilder& result);
+
static const CommandMap& allCommands() {
return *_commands;
}
@@ -514,6 +509,18 @@ private:
static CommandMap* _commands;
static CommandMap* _commandsByBestName;
+ /**
+ * Runs the command.
+ *
+ * The default implementation verifies that request has no document sections then forwards to
+ * BasicCommand::run().
+ *
+ * For now commands should only implement if they need access to OP_MSG-specific functionality.
+ */
+ virtual bool enhancedRun(OperationContext* opCtx,
+ const OpMsgRequest& request,
+ BSONObjBuilder& result) = 0;
+
// Counters for how many times this command has been executed and failed
Counter64 _commandsExecuted;
Counter64 _commandsFailed;
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index 3ba7f61d588..9ea7617f4d7 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -63,6 +63,9 @@ env.Library(
"generic.cpp",
"hashcmd.cpp",
"isself.cpp",
+ "kill_all_sessions_by_pattern_command.cpp",
+ "kill_all_sessions_command.cpp",
+ "kill_sessions_command.cpp",
"mr_common.cpp",
"parameters.cpp",
"refresh_logical_session_cache_now.cpp",
@@ -85,6 +88,7 @@ env.Library(
'$BUILD_DIR/mongo/db/exec/working_set',
'$BUILD_DIR/mongo/db/index/key_generator',
'$BUILD_DIR/mongo/db/index_names',
+ '$BUILD_DIR/mongo/db/kill_sessions',
'$BUILD_DIR/mongo/db/lasterror',
'$BUILD_DIR/mongo/db/log_process_details',
'$BUILD_DIR/mongo/db/logical_session_cache',
diff --git a/src/mongo/db/commands/kill_all_sessions_by_pattern_command.cpp b/src/mongo/db/commands/kill_all_sessions_by_pattern_command.cpp
new file mode 100644
index 00000000000..48387e69e88
--- /dev/null
+++ b/src/mongo/db/commands/kill_all_sessions_by_pattern_command.cpp
@@ -0,0 +1,119 @@
+/**
+ * Copyright (C) 2017 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::kCommand
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/base/init.h"
+#include "mongo/db/auth/action_set.h"
+#include "mongo/db/auth/action_type.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/auth/privilege.h"
+#include "mongo/db/client.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/kill_sessions.h"
+#include "mongo/db/kill_sessions_common.h"
+#include "mongo/db/kill_sessions_local.h"
+#include "mongo/db/logical_session_cache.h"
+#include "mongo/db/logical_session_id.h"
+#include "mongo/db/logical_session_id_helpers.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/stats/top.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+class KillAllSessionsByPatternCommand final : public BasicCommand {
+ MONGO_DISALLOW_COPYING(KillAllSessionsByPatternCommand);
+
+public:
+ KillAllSessionsByPatternCommand() : BasicCommand("killAllSessionsByPattern") {}
+
+ bool slaveOk() const override {
+ return true;
+ }
+ bool adminOnly() const override {
+ return false;
+ }
+ bool supportsWriteConcern(const BSONObj& cmd) const override {
+ return false;
+ }
+ void help(std::stringstream& help) const override {
+ help << "kill logical sessions by pattern";
+ }
+ Status checkAuthForOperation(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj) override {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+ if (!authSession->isAuthorizedForPrivilege(
+ Privilege{ResourcePattern::forClusterResource(), ActionType::killAnySession})) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ return Status::OK();
+ }
+
+ virtual bool run(OperationContext* opCtx,
+ const std::string& db,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) override {
+ IDLParserErrorContext ctx("KillAllSessionsByPatternCmd");
+ auto ksc = KillAllSessionsByPatternCmd::parse(ctx, cmdObj);
+
+ // The empty command kills all
+ if (ksc.getKillAllSessionsByPattern().empty()) {
+ ksc.setKillAllSessionsByPattern({makeKillAllSessionsByPattern(opCtx)});
+ } else {
+ // If a pattern is passed, you may only pass impersonate data if you have the
+ // impersonate privilege.
+ auto authSession = AuthorizationSession::get(opCtx->getClient());
+
+ if (!authSession->isAuthorizedForPrivilege(
+ Privilege(ResourcePattern::forClusterResource(), ActionType::impersonate))) {
+
+ for (const auto& pattern : ksc.getKillAllSessionsByPattern()) {
+ if (pattern.getUsers() || pattern.getRoles()) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::Unauthorized,
+ "Not authorized to impersonate in killAllSessionsByPattern"));
+ }
+ }
+ }
+ }
+
+ KillAllSessionsByPatternSet patterns{ksc.getKillAllSessionsByPattern().begin(),
+ ksc.getKillAllSessionsByPattern().end()};
+
+ return appendCommandStatus(result, killSessionsCmdHelper(opCtx, result, patterns));
+ }
+} killAllSessionsByPatternCommand;
+
+} // namespace mongo
diff --git a/src/mongo/db/commands/kill_all_sessions_command.cpp b/src/mongo/db/commands/kill_all_sessions_command.cpp
new file mode 100644
index 00000000000..ba200c4e443
--- /dev/null
+++ b/src/mongo/db/commands/kill_all_sessions_command.cpp
@@ -0,0 +1,107 @@
+/**
+ * Copyright (C) 2017 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::kCommand
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/base/init.h"
+#include "mongo/db/auth/action_set.h"
+#include "mongo/db/auth/action_type.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/auth/privilege.h"
+#include "mongo/db/client.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/kill_sessions.h"
+#include "mongo/db/kill_sessions_common.h"
+#include "mongo/db/kill_sessions_local.h"
+#include "mongo/db/logical_session_cache.h"
+#include "mongo/db/logical_session_id.h"
+#include "mongo/db/logical_session_id_helpers.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/stats/top.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+class KillAllSessionsCommand final : public BasicCommand {
+ MONGO_DISALLOW_COPYING(KillAllSessionsCommand);
+
+public:
+ KillAllSessionsCommand() : BasicCommand("killAllSessions") {}
+
+ bool slaveOk() const override {
+ return true;
+ }
+ bool adminOnly() const override {
+ return false;
+ }
+ bool supportsWriteConcern(const BSONObj& cmd) const override {
+ return false;
+ }
+ void help(std::stringstream& help) const override {
+ help << "kill all logical sessions, for a user, and their operations";
+ }
+ Status checkAuthForOperation(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj) override {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+ if (!authSession->isAuthorizedForPrivilege(
+ Privilege{ResourcePattern::forClusterResource(), ActionType::killAnySession})) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ return Status::OK();
+ }
+
+ virtual bool run(OperationContext* opCtx,
+ const std::string& db,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) override {
+ IDLParserErrorContext ctx("KillAllSessionsCmd");
+ auto ksc = KillAllSessionsCmd::parse(ctx, cmdObj);
+
+ KillAllSessionsByPatternSet patterns;
+
+ // The empty command kills all
+ if (ksc.getKillAllSessions().empty()) {
+ patterns.emplace(makeKillAllSessionsByPattern(opCtx));
+ } else {
+ patterns.reserve(ksc.getKillAllSessions().size());
+
+ for (const auto& user : ksc.getKillAllSessions()) {
+ patterns.emplace(makeKillAllSessionsByPattern(opCtx, user));
+ }
+ }
+
+ return appendCommandStatus(result, killSessionsCmdHelper(opCtx, result, patterns));
+ }
+} killAllSessionsCommand;
+
+} // namespace mongo
diff --git a/src/mongo/db/commands/kill_sessions_command.cpp b/src/mongo/db/commands/kill_sessions_command.cpp
new file mode 100644
index 00000000000..6460863d599
--- /dev/null
+++ b/src/mongo/db/commands/kill_sessions_command.cpp
@@ -0,0 +1,134 @@
+/**
+ * Copyright (C) 2017 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::kCommand
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/base/init.h"
+#include "mongo/db/auth/action_set.h"
+#include "mongo/db/auth/action_type.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/auth/privilege.h"
+#include "mongo/db/client.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/kill_sessions.h"
+#include "mongo/db/kill_sessions_common.h"
+#include "mongo/db/kill_sessions_local.h"
+#include "mongo/db/logical_session_cache.h"
+#include "mongo/db/logical_session_id.h"
+#include "mongo/db/logical_session_id_helpers.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/stats/top.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+namespace {
+
+KillAllSessionsByPatternSet patternsForLoggedInUser(OperationContext* opCtx) {
+ auto client = opCtx->getClient();
+ ServiceContext* serviceContext = client->getServiceContext();
+
+ KillAllSessionsByPatternSet patterns;
+
+ if (AuthorizationManager::get(serviceContext)->isAuthEnabled()) {
+ auto authzSession = AuthorizationSession::get(client);
+ for (auto iter = authzSession->getAuthenticatedUserNames(); iter.more(); iter.next()) {
+ User* user = authzSession->lookupUser(*iter);
+ invariant(user);
+
+ auto pattern = makeKillAllSessionsByPattern(opCtx);
+ pattern.setUid(user->getDigest());
+ patterns.emplace(std::move(pattern));
+ }
+ } else {
+ patterns.emplace(makeKillAllSessionsByPattern(opCtx));
+ }
+
+ return patterns;
+}
+
+} // namespace
+
+class KillSessionsCommand final : public BasicCommand {
+ MONGO_DISALLOW_COPYING(KillSessionsCommand);
+
+public:
+ KillSessionsCommand() : BasicCommand("killSessions") {}
+
+ bool slaveOk() const override {
+ return true;
+ }
+ bool adminOnly() const override {
+ return false;
+ }
+ bool supportsWriteConcern(const BSONObj& cmd) const override {
+ return false;
+ }
+ void help(std::stringstream& help) const override {
+ help << "kill a logical session and its operations";
+ }
+
+ // Any user can kill their own sessions
+ Status checkAuthForOperation(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj) override {
+ return Status::OK();
+ }
+
+ virtual bool run(OperationContext* opCtx,
+ const std::string& db,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) override {
+ IDLParserErrorContext ctx("KillSessionsCmd");
+ auto ksc = KillSessionsCmdFromClient::parse(ctx, cmdObj);
+
+ KillAllSessionsByPatternSet patterns;
+
+ if (ksc.getKillSessions().empty()) {
+ patterns = patternsForLoggedInUser(opCtx);
+ } else {
+ auto lsids = makeLogicalSessionIds(
+ ksc.getKillSessions(),
+ opCtx,
+ {Privilege{ResourcePattern::forClusterResource(), ActionType::killAnySession}});
+
+ patterns.reserve(lsids.size());
+ for (const auto& lsid : lsids) {
+ patterns.emplace(makeKillAllSessionsByPattern(opCtx, lsid));
+ }
+ }
+
+ return appendCommandStatus(result, killSessionsCmdHelper(opCtx, result, patterns));
+ }
+} killSessionsCommand;
+
+} // namespace mongo
diff --git a/src/mongo/db/cursor_manager.cpp b/src/mongo/db/cursor_manager.cpp
index 2406f7422bc..d2013c14f0f 100644
--- a/src/mongo/db/cursor_manager.cpp
+++ b/src/mongo/db/cursor_manager.cpp
@@ -40,6 +40,7 @@
#include "mongo/db/catalog/database_holder.h"
#include "mongo/db/client.h"
#include "mongo/db/db_raii.h"
+#include "mongo/db/kill_sessions_common.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/query/plan_executor.h"
@@ -103,7 +104,8 @@ public:
std::size_t timeoutCursors(OperationContext* opCtx, Date_t now);
- void appendActiveSessions(OperationContext* opCtx, LogicalSessionIdSet* lsids);
+ template <typename Visitor>
+ void visitAllCursorManagers(OperationContext* opCtx, Visitor* visitor);
int64_t nextSeed();
@@ -271,10 +273,9 @@ std::size_t GlobalCursorIdCache::timeoutCursors(OperationContext* opCtx, Date_t
}
} // namespace
-void GlobalCursorIdCache::appendActiveSessions(OperationContext* opCtx,
- LogicalSessionIdSet* lsids) {
- // Get active session ids from the global cursor manager
- globalCursorManager->appendActiveSessions(lsids);
+template <typename Visitor>
+void GlobalCursorIdCache::visitAllCursorManagers(OperationContext* opCtx, Visitor* visitor) {
+ (*visitor)(*globalCursorManager);
// Compute the set of collection names that we have to get sessions for
vector<NamespaceString> namespaces;
@@ -298,7 +299,7 @@ void GlobalCursorIdCache::appendActiveSessions(OperationContext* opCtx,
continue;
}
- collection->getCursorManager()->appendActiveSessions(lsids);
+ (*visitor)(*(collection->getCursorManager()));
}
}
@@ -309,7 +310,19 @@ CursorManager* CursorManager::getGlobalCursorManager() {
}
void CursorManager::appendAllActiveSessions(OperationContext* opCtx, LogicalSessionIdSet* lsids) {
- globalCursorIdCache->appendActiveSessions(opCtx, lsids);
+ auto visitor = [&](CursorManager& mgr) { mgr.appendActiveSessions(lsids); };
+ globalCursorIdCache->visitAllCursorManagers(opCtx, &visitor);
+}
+
+Status CursorManager::killCursorsWithMatchingSessions(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher) {
+ auto eraser = [&](CursorManager& mgr, CursorId id) {
+ uassertStatusOK(mgr.eraseCursor(opCtx, id, true));
+ };
+
+ auto visitor = makeKillSessionsCursorManagerVisitor(opCtx, matcher, std::move(eraser));
+ globalCursorIdCache->visitAllCursorManagers(opCtx, &visitor);
+ return visitor.getStatus();
}
std::size_t CursorManager::timeoutCursorsGlobal(OperationContext* opCtx, Date_t now) {
diff --git a/src/mongo/db/cursor_manager.h b/src/mongo/db/cursor_manager.h
index 5a2aed6edc0..c2fdc12e93c 100644
--- a/src/mongo/db/cursor_manager.h
+++ b/src/mongo/db/cursor_manager.h
@@ -32,8 +32,10 @@
#include "mongo/db/clientcursor.h"
#include "mongo/db/cursor_id.h"
#include "mongo/db/invalidation_type.h"
+#include "mongo/db/kill_sessions.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/record_id.h"
+#include "mongo/db/session_killer.h"
#include "mongo/platform/unordered_map.h"
#include "mongo/platform/unordered_set.h"
#include "mongo/stdx/unordered_set.h"
@@ -85,6 +87,12 @@ public:
*/
static void appendAllActiveSessions(OperationContext* opCtx, LogicalSessionIdSet* lsids);
+ /**
+ * Kills cursors with matching logical sessions.
+ */
+ static Status killCursorsWithMatchingSessions(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher);
+
CursorManager(NamespaceString nss);
/**
diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp
index ef48122ce6f..9defa4f328f 100644
--- a/src/mongo/db/db.cpp
+++ b/src/mongo/db/db.cpp
@@ -75,6 +75,8 @@
#include "mongo/db/introspect.h"
#include "mongo/db/json.h"
#include "mongo/db/keys_collection_manager.h"
+#include "mongo/db/kill_sessions.h"
+#include "mongo/db/kill_sessions_local.h"
#include "mongo/db/log_process_details.h"
#include "mongo/db/logical_clock.h"
#include "mongo/db/logical_session_cache.h"
@@ -109,6 +111,7 @@
#include "mongo/db/service_context_d.h"
#include "mongo/db/service_entry_point_mongod.h"
#include "mongo/db/session_catalog.h"
+#include "mongo/db/session_killer.h"
#include "mongo/db/startup_warnings_mongod.h"
#include "mongo/db/stats/counters.h"
#include "mongo/db/storage/encryption_hooks.h"
@@ -762,6 +765,9 @@ ExitCode _initAndListen(int listenPort) {
runner->startup().transitional_ignore();
globalServiceContext->setPeriodicRunner(std::move(runner));
+ SessionKiller::set(globalServiceContext,
+ std::make_shared<SessionKiller>(globalServiceContext, killSessionsLocal));
+
// Set up the logical session cache
LogicalSessionCacheServer kind = LogicalSessionCacheServer::kStandalone;
if (shardingInitialized) {
diff --git a/src/mongo/db/kill_sessions.cpp b/src/mongo/db/kill_sessions.cpp
new file mode 100644
index 00000000000..f16202c343e
--- /dev/null
+++ b/src/mongo/db/kill_sessions.cpp
@@ -0,0 +1,131 @@
+/**
+ * Copyright (C) 2017 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/kill_sessions.h"
+
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/client.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/service_context.h"
+
+namespace mongo {
+
+namespace {
+
+std::vector<KillAllSessionsUser> getKillAllSessionsImpersonateUsers(OperationContext* opCtx) {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+
+ std::vector<KillAllSessionsUser> out;
+
+ for (auto iter = authSession->getAuthenticatedUserNames(); iter.more(); iter.next()) {
+ out.emplace_back();
+ out.back().setUser(iter->getUser());
+ out.back().setDb(iter->getDB());
+ }
+
+ return out;
+}
+
+std::vector<KillAllSessionsRole> getKillAllSessionsImpersonateRoles(OperationContext* opCtx) {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+
+ std::vector<KillAllSessionsRole> out;
+
+ for (auto iter = authSession->getAuthenticatedRoleNames(); iter.more(); iter.next()) {
+ out.emplace_back();
+ out.back().setRole(iter->getRole());
+ out.back().setDb(iter->getDB());
+ }
+
+ return out;
+}
+
+} // namespace
+
+std::tuple<std::vector<UserName>, std::vector<RoleName>> getKillAllSessionsByPatternImpersonateData(
+ const KillAllSessionsByPattern& pattern) {
+ std::tuple<std::vector<UserName>, std::vector<RoleName>> out;
+
+ auto& users = std::get<0>(out);
+ auto& roles = std::get<1>(out);
+
+ if (pattern.getUsers()) {
+ users.reserve(pattern.getUsers()->size());
+
+ for (auto&& user : pattern.getUsers().get()) {
+ users.emplace_back(user.getUser(), user.getDb());
+ }
+ }
+
+ if (pattern.getRoles()) {
+ roles.reserve(pattern.getUsers()->size());
+
+ for (auto&& user : pattern.getUsers().get()) {
+ roles.emplace_back(user.getUser(), user.getDb());
+ }
+ }
+
+ return out;
+}
+
+KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx) {
+ KillAllSessionsByPattern kasbp;
+
+ kasbp.setUsers(getKillAllSessionsImpersonateUsers(opCtx));
+ kasbp.setRoles(getKillAllSessionsImpersonateRoles(opCtx));
+
+ return kasbp;
+}
+
+KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx,
+ const KillAllSessionsUser& kasu) {
+ KillAllSessionsByPattern kasbp = makeKillAllSessionsByPattern(opCtx);
+
+ auto authMgr = AuthorizationManager::get(opCtx->getServiceContext());
+
+ User* user;
+ UserName un(kasu.getUser(), kasu.getDb());
+
+ uassertStatusOK(authMgr->acquireUser(opCtx, un, &user));
+ kasbp.setUid(user->getDigest());
+ authMgr->releaseUser(user);
+
+ return kasbp;
+}
+
+KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx,
+ const LogicalSessionId& lsid) {
+ KillAllSessionsByPattern kasbp = makeKillAllSessionsByPattern(opCtx);
+ kasbp.setLsid(lsid);
+
+ return kasbp;
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/kill_sessions.h b/src/mongo/db/kill_sessions.h
new file mode 100644
index 00000000000..547c2d038a3
--- /dev/null
+++ b/src/mongo/db/kill_sessions.h
@@ -0,0 +1,102 @@
+/**
+ * Copyright (C) 2017 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 <tuple>
+
+#include "mongo/db/auth/role_name.h"
+#include "mongo/db/auth/user_name.h"
+#include "mongo/db/kill_sessions_gen.h"
+#include "mongo/db/logical_session_id.h"
+
+namespace mongo {
+
+class OperationContext;
+class ServiceContext;
+
+struct KillAllSessionsByPatternHash {
+ std::size_t operator()(const KillAllSessionsByPattern& pattern) const {
+ if (pattern.getLsid()) {
+ return lsidHasher(*pattern.getLsid());
+ } else if (pattern.getUid()) {
+ return uidHasher(*pattern.getUid());
+ }
+
+ // fall through for killAll
+ return 0;
+ }
+
+ LogicalSessionIdHash lsidHasher;
+ SHA256Block::Hash uidHasher;
+};
+
+/**
+ * Patterns are specifically equal if they differ only by impersonate data.
+ */
+inline bool operator==(const KillAllSessionsByPattern& lhs, const KillAllSessionsByPattern& rhs) {
+ auto makeEqualityLens = [](const auto& pattern) {
+ return std::tie(pattern.getLsid(), pattern.getUid());
+ };
+
+ return makeEqualityLens(lhs) == makeEqualityLens(rhs);
+}
+
+inline bool operator!=(const KillAllSessionsByPattern& lhs, const KillAllSessionsByPattern& rhs) {
+ return !(lhs == rhs);
+}
+
+using KillAllSessionsByPatternSet =
+ stdx::unordered_set<KillAllSessionsByPattern, KillAllSessionsByPatternHash>;
+
+std::tuple<std::vector<UserName>, std::vector<RoleName>> getKillAllSessionsByPatternImpersonateData(
+ const KillAllSessionsByPattern& pattern);
+
+/**
+ * Note: All three of the below makeKillAllSessionsByPattern helpers take opCtx to inline the
+ * required impersonate data
+ */
+
+/**
+ * Constructs a kill sessions pattern which kills all sessions
+ */
+KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx);
+
+/**
+ * Constructs a kill sessions pattern for a particular user
+ */
+KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx,
+ const KillAllSessionsUser& user);
+
+/**
+ * Constructs a kill sessions pattern for a particular logical session
+ */
+KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx,
+ const LogicalSessionId& lsid);
+
+} // namespace mongo
diff --git a/src/mongo/db/kill_sessions.idl b/src/mongo/db/kill_sessions.idl
new file mode 100644
index 00000000000..becc2c8ef9a
--- /dev/null
+++ b/src/mongo/db/kill_sessions.idl
@@ -0,0 +1,81 @@
+# Copyright (C) 2017 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/>.
+#
+
+global:
+ cpp_namespace: "mongo"
+
+imports:
+ - "mongo/crypto/sha256_block.idl"
+ - "mongo/db/logical_session_id.idl"
+ - "mongo/idl/basic_types.idl"
+
+structs:
+
+ KillSessionsCmdFromClient:
+ description: "A struct representing a killSessions command from a client"
+ strict: false
+ fields:
+ killSessions: array<LogicalSessionFromClient>
+
+ KillAllSessionsUser:
+ description: "A struct representing a killAllSessions User"
+ strict: true
+ fields:
+ user: string
+ db: string
+
+ KillAllSessionsRole:
+ description: "A struct representing a killAllSessions Role"
+ strict: true
+ fields:
+ role: string
+ db: string
+
+ KillAllSessionsCmd:
+ description: "A struct representing a killAllSessions command"
+ strict: false
+ fields:
+ killAllSessions: array<KillAllSessionsUser>
+
+ KillAllSessionsByPattern:
+ description: "A struct representing a killAllSessionsByPatternCmd kill pattern"
+ strict: true
+ fields:
+ lsid:
+ type: LogicalSessionId
+ optional: true
+ uid:
+ type: sha256Block
+ optional: true
+ users:
+ description: "logged in users for impersonate"
+ type: array<KillAllSessionsUser>
+ optional: true
+ roles:
+ description: "logged in roles for impersonate"
+ type: array<KillAllSessionsRole>
+ optional: true
+
+ KillAllSessionsByPatternCmd:
+ description: "A struct representing a killAllSessionsByPattern command"
+ strict: false
+ fields:
+ killAllSessionsByPattern: array<KillAllSessionsByPattern>
+
+ KillSessionsCmdToServer:
+ description: "A struct representing a killSessions command to a server"
+ strict: true
+ fields:
+ killSessions: array<LogicalSessionId>
diff --git a/src/mongo/db/kill_sessions_common.cpp b/src/mongo/db/kill_sessions_common.cpp
new file mode 100644
index 00000000000..084e5eeec44
--- /dev/null
+++ b/src/mongo/db/kill_sessions_common.cpp
@@ -0,0 +1,91 @@
+/**
+ * Copyright (C) 2017 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::kCommand
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/kill_sessions_common.h"
+
+#include "mongo/db/client.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/service_context.h"
+#include "mongo/db/session_killer.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+SessionKiller::Result killSessionsLocalKillOps(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher) {
+ for (ServiceContext::LockedClientsCursor cursor(opCtx->getClient()->getServiceContext());
+ Client* client = cursor.next();) {
+ invariant(client);
+ stdx::unique_lock<Client> lk(*client);
+
+ OperationContext* opCtxToKill = client->getOperationContext();
+ if (opCtxToKill) {
+ const auto& lsid = opCtxToKill->getLogicalSessionId();
+
+ if (lsid) {
+ if (const KillAllSessionsByPattern* pattern = matcher.match(*lsid)) {
+ ScopedKillAllSessionsByPatternImpersonator impersonator(opCtx, *pattern);
+
+ log() << "killing op: " << opCtxToKill->getOpID()
+ << " as part of killing session: " << lsid->toBSON();
+
+ opCtx->getServiceContext()->killOperation(opCtxToKill);
+ }
+ }
+ }
+ }
+
+ return {std::vector<HostAndPort>{}};
+}
+
+Status killSessionsCmdHelper(OperationContext* opCtx,
+ BSONObjBuilder& result,
+ const KillAllSessionsByPatternSet& patterns) {
+ auto killResult = SessionKiller::get(opCtx)->kill(opCtx, patterns);
+
+ if (!killResult->isOK()) {
+ return killResult->getStatus();
+ }
+
+ if (!killResult->getValue().empty()) {
+ BSONArrayBuilder bab(result.subarrayStart("failedHosts"));
+ for (const auto& host : killResult->getValue()) {
+ bab.append(host.toString());
+ }
+
+ return Status(ErrorCodes::HostUnreachable, "Failed to kill on some hosts");
+ }
+
+ return Status::OK();
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/kill_sessions_common.h b/src/mongo/db/kill_sessions_common.h
new file mode 100644
index 00000000000..4b6556db7a8
--- /dev/null
+++ b/src/mongo/db/kill_sessions_common.h
@@ -0,0 +1,141 @@
+/**
+ * Copyright (C) 2017 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 "mongo/db/kill_sessions.h"
+
+#include <vector>
+
+#include "mongo/base/status.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/session_killer.h"
+#include "mongo/stdx/unordered_set.h"
+#include "mongo/util/stringutils.h"
+
+namespace mongo {
+
+/**
+ * Local killing involves looping over all local operations, checking to see if they have matching
+ * logical session ids, and killing if they do.
+ */
+SessionKiller::Result killSessionsLocalKillOps(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher);
+
+/**
+ * Helper for executing a pattern set from a kill sessions style command.
+ */
+Status killSessionsCmdHelper(OperationContext* opCtx,
+ BSONObjBuilder& result,
+ const KillAllSessionsByPatternSet& patterns);
+
+class ScopedKillAllSessionsByPatternImpersonator {
+public:
+ ScopedKillAllSessionsByPatternImpersonator(OperationContext* opCtx,
+ const KillAllSessionsByPattern& pattern) {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+
+ if (pattern.getUsers() && pattern.getRoles()) {
+ std::tie(_names, _roles) = getKillAllSessionsByPatternImpersonateData(pattern);
+ _raii.emplace(authSession, &_names, &_roles);
+ }
+ }
+
+private:
+ std::vector<UserName> _names;
+ std::vector<RoleName> _roles;
+ boost::optional<AuthorizationSession::ScopedImpersonate> _raii;
+};
+
+/**
+ * This elaborate bit of artiface helps us to adapt the shape of a cursor manager that we know from
+ * logical sessions with the different ways to cancel cursors in mongos versus mongod. I.e. the
+ * two types share no code, but do share enough shape to re-use some boilerplate.
+ */
+template <typename Eraser>
+class KillSessionsCursorManagerVisitor {
+public:
+ KillSessionsCursorManagerVisitor(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher,
+ Eraser&& eraser)
+ : _opCtx(opCtx), _matcher(matcher), _eraser(eraser) {}
+
+ template <typename Mgr>
+ void operator()(Mgr& mgr) {
+ LogicalSessionIdSet activeSessions;
+ mgr.appendActiveSessions(&activeSessions);
+
+ for (const auto& session : activeSessions) {
+ if (const KillAllSessionsByPattern* pattern = _matcher.match(session)) {
+ ScopedKillAllSessionsByPatternImpersonator impersonator(_opCtx, *pattern);
+
+ auto cursors = mgr.getCursorsForSession(session);
+ for (const auto& id : cursors) {
+ try {
+ _eraser(mgr, id);
+ } catch (...) {
+ _failures.push_back(exceptionToStatus());
+ }
+ }
+ }
+ }
+ }
+
+ Status getStatus() const {
+ if (_failures.empty()) {
+ return Status::OK();
+ }
+
+ if (_failures.size() == 1) {
+ return _failures.back();
+ }
+
+ return Status(_failures.back().code(),
+ str::stream() << "Encountered " << _failures.size()
+ << " errors while killing cursors, "
+ "showing most recent error: "
+ << _failures.back().reason());
+ }
+
+private:
+ OperationContext* _opCtx;
+ const SessionKiller::Matcher& _matcher;
+ std::vector<Status> _failures;
+ Eraser _eraser;
+};
+
+template <typename Eraser>
+auto makeKillSessionsCursorManagerVisitor(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher,
+ Eraser&& eraser) {
+ return KillSessionsCursorManagerVisitor<std::decay_t<Eraser>>{
+ opCtx, matcher, std::forward<Eraser>(eraser)};
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/kill_sessions_local.cpp b/src/mongo/db/kill_sessions_local.cpp
new file mode 100644
index 00000000000..981cab7d85c
--- /dev/null
+++ b/src/mongo/db/kill_sessions_local.cpp
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2017 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::kCommand
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/kill_sessions_local.h"
+
+#include "mongo/db/client.h"
+#include "mongo/db/cursor_manager.h"
+#include "mongo/db/kill_sessions_common.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/service_context.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+SessionKiller::Result killSessionsLocalKillCursors(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher) {
+
+ auto status = CursorManager::killCursorsWithMatchingSessions(opCtx, matcher);
+
+ if (status.isOK()) {
+ return std::vector<HostAndPort>{};
+ } else {
+ return status;
+ }
+}
+
+SessionKiller::Result killSessionsLocal(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher,
+ SessionKiller::UniformRandomBitGenerator* urbg) {
+ uassertStatusOK(killSessionsLocalKillCursors(opCtx, matcher));
+ return uassertStatusOK(killSessionsLocalKillOps(opCtx, matcher));
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/kill_sessions_local.h b/src/mongo/db/kill_sessions_local.h
new file mode 100644
index 00000000000..0785eb9d6a1
--- /dev/null
+++ b/src/mongo/db/kill_sessions_local.h
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2017 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 "mongo/db/kill_sessions.h"
+
+#include "mongo/db/session_killer.h"
+
+namespace mongo {
+
+SessionKiller::Result killSessionsLocal(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher,
+ SessionKiller::UniformRandomBitGenerator* urbg);
+
+SessionKiller::Result killSessionsLocalKillCursors(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher);
+
+} // namespace mongo
diff --git a/src/mongo/db/logical_session_id.h b/src/mongo/db/logical_session_id.h
index 37937d79a56..24629bdd050 100644
--- a/src/mongo/db/logical_session_id.h
+++ b/src/mongo/db/logical_session_id.h
@@ -112,4 +112,7 @@ inline StringBuilder& operator<<(StringBuilder& s, const LogicalSessionFromClien
using LogicalSessionIdSet = stdx::unordered_set<LogicalSessionId, LogicalSessionIdHash>;
using LogicalSessionRecordSet = stdx::unordered_set<LogicalSessionRecord, LogicalSessionRecordHash>;
+template <typename T>
+using LogicalSessionIdMap = stdx::unordered_map<LogicalSessionId, T, LogicalSessionIdHash>;
+
} // namespace mongo
diff --git a/src/mongo/db/logical_session_id_test.cpp b/src/mongo/db/logical_session_id_test.cpp
index 48b25dc4148..c10ce634cfc 100644
--- a/src/mongo/db/logical_session_id_test.cpp
+++ b/src/mongo/db/logical_session_id_test.cpp
@@ -157,6 +157,24 @@ TEST_F(LogicalSessionIdTest, ConstructorFromClientWithPassedUidWithPermissions)
ASSERT_EQ(lsid.getUid(), uid);
}
+TEST_F(LogicalSessionIdTest, ConstructorFromClientWithPassedUidWithNonImpersonatePermissions) {
+ auto id = UUID::gen();
+ auto uid = SHA256Block{};
+ addSimpleUser(UserName("simple", "test"));
+
+ LogicalSessionFromClient req;
+ req.setId(id);
+ req.setUid(uid);
+
+ LogicalSessionId lsid = makeLogicalSessionId(
+ req,
+ _opCtx.get(),
+ {Privilege{ResourcePattern::forClusterResource(), ActionType::startSession}});
+
+ ASSERT_EQ(lsid.getId(), id);
+ ASSERT_EQ(lsid.getUid(), uid);
+}
+
TEST_F(LogicalSessionIdTest, ConstructorFromClientWithPassedUidWithoutAuthedUser) {
auto id = UUID::gen();
auto uid = SHA256Block{};
@@ -168,7 +186,7 @@ TEST_F(LogicalSessionIdTest, ConstructorFromClientWithPassedUidWithoutAuthedUser
ASSERT_THROWS(makeLogicalSessionId(req, _opCtx.get()), AssertionException);
}
-TEST_F(LogicalSessionIdTest, ConstructorFromClientWithPassedUidWithoutPermissions) {
+TEST_F(LogicalSessionIdTest, ConstructorFromClientWithPassedNonMatchingUidWithoutPermissions) {
auto id = UUID::gen();
auto uid = SHA256Block{};
addSimpleUser(UserName("simple", "test"));
@@ -180,6 +198,21 @@ TEST_F(LogicalSessionIdTest, ConstructorFromClientWithPassedUidWithoutPermission
ASSERT_THROWS(makeLogicalSessionId(req, _opCtx.get()), AssertionException);
}
+TEST_F(LogicalSessionIdTest, ConstructorFromClientWithPassedMatchingUidWithoutPermissions) {
+ auto id = UUID::gen();
+ User* user = addSimpleUser(UserName("simple", "test"));
+ auto uid = user->getDigest();
+
+ LogicalSessionFromClient req;
+ req.setId(id);
+ req.setUid(uid);
+
+ LogicalSessionId lsid = makeLogicalSessionId(req, _opCtx.get());
+
+ ASSERT_EQ(lsid.getId(), id);
+ ASSERT_EQ(lsid.getUid(), uid);
+}
+
TEST_F(LogicalSessionIdTest, GenWithUser) {
User* user = addSimpleUser(UserName("simple", "test"));
auto lsid = makeLogicalSessionId(_opCtx.get());
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp
index 0405d78e1b8..f796cd1a9fc 100644
--- a/src/mongo/db/pipeline/pipeline_d.cpp
+++ b/src/mongo/db/pipeline/pipeline_d.cpp
@@ -280,6 +280,11 @@ public:
infoBuilder.append("killPending", true);
}
+ if (clientOpCtx->getLogicalSessionId()) {
+ BSONObjBuilder bob(infoBuilder.subobjStart("lsid"));
+ clientOpCtx->getLogicalSessionId()->serialize(&bob);
+ }
+
CurOp::get(clientOpCtx)
->reportState(&infoBuilder,
(truncateMode == CurrentOpTruncateMode::kTruncateOps));
diff --git a/src/mongo/db/s/sharding_task_executor.cpp b/src/mongo/db/s/sharding_task_executor.cpp
index 967dbe713bb..08362e55d66 100644
--- a/src/mongo/db/s/sharding_task_executor.cpp
+++ b/src/mongo/db/s/sharding_task_executor.cpp
@@ -111,30 +111,43 @@ StatusWith<TaskExecutor::CallbackHandle> ShardingTaskExecutor::scheduleRemoteCom
return _executor->scheduleRemoteCommand(request, cb);
}
+ boost::optional<RemoteCommandRequest> newRequest;
+
+ if (request.opCtx->getLogicalSessionId() && !request.cmdObj.hasField("lsid")) {
+ newRequest.emplace(request);
+ BSONObjBuilder bob(std::move(newRequest->cmdObj));
+ {
+ BSONObjBuilder subbob(bob.subobjStart("lsid"));
+ request.opCtx->getLogicalSessionId()->serialize(&subbob);
+ }
+
+ newRequest->cmdObj = bob.obj();
+ }
+
std::shared_ptr<OperationTimeTracker> timeTracker = OperationTimeTracker::get(request.opCtx);
auto clusterGLE = ClusterLastErrorInfo::get(request.opCtx->getClient());
- auto shardingCb = [timeTracker, clusterGLE, request, cb](
+ auto shardingCb = [timeTracker, clusterGLE, cb](
const TaskExecutor::RemoteCommandCallbackArgs& args) {
ON_BLOCK_EXIT([&cb, &args]() { cb(args); });
// Update replica set monitor info.
- auto shard = grid.shardRegistry()->getShardForHostNoReload(request.target);
+ auto shard = grid.shardRegistry()->getShardForHostNoReload(args.request.target);
if (!shard) {
- LOG(1) << "Could not find shard containing host: " << request.target.toString();
+ LOG(1) << "Could not find shard containing host: " << args.request.target.toString();
}
if (!args.response.isOK()) {
if (shard) {
- shard->updateReplSetMonitor(request.target, args.response.status);
+ shard->updateReplSetMonitor(args.request.target, args.response.status);
}
LOG(1) << "Error processing the remote request, not updating operationTime or gLE";
return;
}
if (shard) {
- shard->updateReplSetMonitor(request.target,
+ shard->updateReplSetMonitor(args.request.target,
getStatusFromCommandResult(args.response.data));
}
@@ -153,9 +166,9 @@ StatusWith<TaskExecutor::CallbackHandle> ShardingTaskExecutor::scheduleRemoteCom
if (swShardingMetadata.isOK()) {
auto shardingMetadata = std::move(swShardingMetadata.getValue());
- auto shardConn = ConnectionString::parse(request.target.toString());
+ auto shardConn = ConnectionString::parse(args.request.target.toString());
if (!shardConn.isOK()) {
- severe() << "got bad host string in saveGLEStats: " << request.target;
+ severe() << "got bad host string in saveGLEStats: " << args.request.target;
}
clusterGLE->addHostOpTime(shardConn.getValue(),
@@ -169,7 +182,7 @@ StatusWith<TaskExecutor::CallbackHandle> ShardingTaskExecutor::scheduleRemoteCom
}
};
- return _executor->scheduleRemoteCommand(request, shardingCb);
+ return _executor->scheduleRemoteCommand(newRequest ? *newRequest : request, shardingCb);
}
void ShardingTaskExecutor::cancel(const CallbackHandle& cbHandle) {
diff --git a/src/mongo/db/service_entry_point_mongod.cpp b/src/mongo/db/service_entry_point_mongod.cpp
index 491a61dc9ea..64ab9ab7271 100644
--- a/src/mongo/db/service_entry_point_mongod.cpp
+++ b/src/mongo/db/service_entry_point_mongod.cpp
@@ -450,7 +450,7 @@ bool runCommandImpl(OperationContext* opCtx,
return result;
}
- result = command->enhancedRun(opCtx, request, inPlaceReplyBob);
+ result = command->publicRun(opCtx, request, inPlaceReplyBob);
} else {
auto wcResult = extractWriteConcern(opCtx, cmd, db);
if (!wcResult.isOK()) {
@@ -471,7 +471,7 @@ bool runCommandImpl(OperationContext* opCtx,
opCtx, command->getName(), lastOpBeforeRun, &inPlaceReplyBob);
});
- result = command->enhancedRun(opCtx, request, inPlaceReplyBob);
+ result = command->publicRun(opCtx, request, inPlaceReplyBob);
// Nothing in run() should change the writeConcern.
dassert(SimpleBSONObjComparator::kInstance.evaluate(opCtx->getWriteConcern().toBSON() ==
diff --git a/src/mongo/db/session_killer.cpp b/src/mongo/db/session_killer.cpp
new file mode 100644
index 00000000000..034cc93038b
--- /dev/null
+++ b/src/mongo/db/session_killer.cpp
@@ -0,0 +1,200 @@
+/**
+ * Copyright (C) 2017 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/session_killer.h"
+
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/client.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/service_context.h"
+#include "mongo/util/destructor_guard.h"
+#include "mongo/util/scopeguard.h"
+
+namespace mongo {
+
+namespace {
+const auto getSessionKiller = ServiceContext::declareDecoration<std::shared_ptr<SessionKiller>>();
+} // namespace
+
+SessionKiller::SessionKiller(ServiceContext* sc, KillFunc killer)
+ : _killFunc(std::move(killer)), _urbg(std::random_device{}()), _reapResults() {
+ _thread = stdx::thread([this, sc] {
+ // This is the background killing thread
+
+ Client::setCurrent(sc->makeClient("SessionKiller"));
+
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+
+ // While we're not in shutdown
+ while (!_inShutdown) {
+ // Wait until we're woken up, and should either shutdown, or have something new to reap.
+ _killerCV.wait(lk, [&] { return _inShutdown || _nextToReap.size(); });
+
+ // If we're in shutdown we're done
+ if (_inShutdown) {
+ return;
+ }
+
+ // Otherwise make an opctx and head into kill
+ auto opCtx = cc().makeOperationContext();
+ _periodicKill(opCtx.get(), lk);
+ }
+ });
+}
+
+SessionKiller::~SessionKiller() {
+ DESTRUCTOR_GUARD([&] {
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _inShutdown = true;
+ }
+ _killerCV.notify_one();
+ _callerCV.notify_all();
+ _thread.join();
+ }());
+}
+
+SessionKiller::ReapResult::ReapResult() : result(std::make_shared<boost::optional<Result>>()) {}
+
+SessionKiller::Matcher::Matcher(KillAllSessionsByPatternSet&& patterns)
+ : _patterns(std::move(patterns)) {
+ for (const auto& pattern : _patterns) {
+ if (pattern.getUid()) {
+ _uids.emplace(pattern.getUid().get(), &pattern);
+ } else if (pattern.getLsid()) {
+ _lsids.emplace(pattern.getLsid().get(), &pattern);
+ } else {
+ // If we're killing everything, it's the only pattern we care about.
+ decltype(_patterns) onlyKillAll{{pattern}};
+ using std::swap;
+ swap(_patterns, onlyKillAll);
+
+ _killAll = &(*_patterns.begin());
+ break;
+ }
+ }
+}
+
+const KillAllSessionsByPatternSet& SessionKiller::Matcher::getPatterns() const {
+ return _patterns;
+}
+
+const KillAllSessionsByPattern* SessionKiller::Matcher::match(const LogicalSessionId& lsid) const {
+ if (_killAll) {
+ return _killAll;
+ }
+
+ {
+ auto iter = _lsids.find(lsid);
+ if (iter != _lsids.end()) {
+ return iter->second;
+ }
+ }
+
+ {
+ auto iter = _uids.find(lsid.getUid());
+ if (iter != _uids.end()) {
+ return iter->second;
+ }
+ }
+
+ return nullptr;
+}
+
+SessionKiller* SessionKiller::get(ServiceContext* service) {
+ return getSessionKiller(service).get();
+}
+
+SessionKiller* SessionKiller::get(OperationContext* ctx) {
+ return get(ctx->getServiceContext());
+}
+
+std::shared_ptr<SessionKiller::Result> SessionKiller::kill(
+ OperationContext* opCtx, const KillAllSessionsByPatternSet& toKill) {
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+
+ // Save a shared_ptr to the current reapResults (I.e. the next thing to get killed).
+ auto reapResults = _reapResults;
+
+ // Dump all your lsids in.
+ for (const auto& item : toKill) {
+ _nextToReap.emplace(item);
+ }
+
+ // Wake up the killer.
+ _killerCV.notify_one();
+
+ // Wait until our results are there, or the killer is shutting down.
+ opCtx->waitForConditionOrInterrupt(
+ _callerCV, lk, [&] { return reapResults.result->is_initialized() || _inShutdown; });
+
+ // If the killer is shutting down, throw.
+ uassert(ErrorCodes::ShutdownInProgress, "SessionKiller shutting down", !_inShutdown);
+
+ // Otherwise, alias (via the aliasing ctor of shared_ptr) a shared_ptr to the actual results
+ // (inside the optional) to keep our contract. That ctor form returns a shared_ptr which
+ // returns one type, while keeping a refcount on a control block from a different type.
+ return {reapResults.result, reapResults.result->get_ptr()};
+}
+
+void SessionKiller::_periodicKill(OperationContext* opCtx, stdx::unique_lock<stdx::mutex>& lk) {
+ // Pull our current workload onto the stack. Swap it for empties.
+ decltype(_nextToReap) nextToReap;
+ decltype(_reapResults) reapResults;
+ {
+ using std::swap;
+ swap(nextToReap, _nextToReap);
+ swap(reapResults, _reapResults);
+ }
+
+ // Drop the lock and run the killer.
+ lk.unlock();
+
+ Matcher matcher(std::move(nextToReap));
+ boost::optional<Result> results;
+ try {
+ results.emplace(_killFunc(opCtx, matcher, &_urbg));
+ } catch (...) {
+ results.emplace(exceptionToStatus());
+ }
+ lk.lock();
+
+ invariant(results);
+
+ // Expose the results and notify callers
+ *(reapResults.result) = std::move(results);
+ _callerCV.notify_all();
+};
+
+void SessionKiller::set(ServiceContext* sc, std::shared_ptr<SessionKiller> sk) {
+ getSessionKiller(sc) = sk;
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/session_killer.h b/src/mongo/db/session_killer.h
new file mode 100644
index 00000000000..2e13df038d8
--- /dev/null
+++ b/src/mongo/db/session_killer.h
@@ -0,0 +1,145 @@
+/**
+ * Copyright (C) 2017 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 <boost/optional.hpp>
+#include <memory>
+#include <random>
+#include <vector>
+
+#include "mongo/base/status_with.h"
+#include "mongo/db/kill_sessions.h"
+#include "mongo/stdx/condition_variable.h"
+#include "mongo/stdx/functional.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/stdx/thread.h"
+#include "mongo/stdx/unordered_set.h"
+#include "mongo/util/net/hostandport.h"
+
+namespace mongo {
+
+/**
+ * The SessionKiller enforces a single thread for session killing for any given ServiceContext.
+ *
+ * The killer owns a background thread which actually does the work, and callers batch their kills
+ * together each round, before the killer starts its dispatching, after which they batch up for the
+ * next round.
+ *
+ * The KillFunc's kill function is passed in to its constructor, and parameterizes its behavior
+ * depending on context (mongod/mongos).
+ */
+class SessionKiller {
+public:
+ /**
+ * The Result of a call is either:
+ *
+ * Status::OK(), empty vector - we killed everything
+ * Status::OK(), filled vector - we killed something. HostAndPort is filled with nodes we
+ * failed to kill on.
+ *
+ * !Status::OK() - The kill function itself failed. I.e. we may have killed nothing.
+ *
+ * This contract has a helper in kill_sessions_common.h which adapts results for command
+ * implementations. (killSessionsCmdHelper)
+ */
+ using Result = StatusWith<std::vector<HostAndPort>>;
+ using UniformRandomBitGenerator = std::minstd_rand;
+
+ class Matcher {
+ public:
+ Matcher(KillAllSessionsByPatternSet&& patterns);
+
+ const KillAllSessionsByPatternSet& getPatterns() const;
+
+ const KillAllSessionsByPattern* match(const LogicalSessionId& lsid) const;
+
+ private:
+ KillAllSessionsByPatternSet _patterns;
+ LogicalSessionIdMap<const KillAllSessionsByPattern*> _lsids;
+ stdx::unordered_map<SHA256Block, const KillAllSessionsByPattern*, SHA256Block::Hash> _uids;
+ const KillAllSessionsByPattern* _killAll = nullptr;
+ };
+
+ /**
+ * A process specific kill function (we have a different impl in mongos versus mongod).
+ */
+ using KillFunc =
+ stdx::function<Result(OperationContext*, const Matcher&, UniformRandomBitGenerator* urbg)>;
+
+ /**
+ * The killer lives as a decoration on the service context.
+ */
+ static SessionKiller* get(ServiceContext* service);
+ static SessionKiller* get(OperationContext* opCtx);
+
+ /**
+ * This method binds the SessionKiller to the ServiceContext.
+ */
+ static void set(ServiceContext* ctx, std::shared_ptr<SessionKiller> sk);
+
+ explicit SessionKiller(ServiceContext* sc, KillFunc killer);
+ ~SessionKiller();
+
+ /**
+ * This is the api for killSessions commands to invoke the killer. It blocks until the kill is
+ * finished, or until it fails (times out on all nodes in mongos).
+ */
+ std::shared_ptr<Result> kill(OperationContext* opCtx,
+ const KillAllSessionsByPatternSet& toKill);
+
+private:
+ /**
+ * This struct is a helper to default fill in the result, which is otherwise a little error
+ * prone.
+ */
+ struct ReapResult {
+ ReapResult();
+
+ std::shared_ptr<boost::optional<Result>> result;
+ };
+
+ void _periodicKill(OperationContext* opCtx, stdx::unique_lock<stdx::mutex>& lk);
+
+ KillFunc _killFunc;
+
+ stdx::thread _thread;
+
+ stdx::mutex _mutex;
+ stdx::condition_variable _callerCV;
+ stdx::condition_variable _killerCV;
+
+ UniformRandomBitGenerator _urbg;
+
+ ReapResult _reapResults;
+ KillAllSessionsByPatternSet _nextToReap;
+
+ bool _inShutdown = false;
+};
+
+} // namespace mongo
diff --git a/src/mongo/executor/SConscript b/src/mongo/executor/SConscript
index 3c8bcf9c60a..b1ed48063bb 100644
--- a/src/mongo/executor/SConscript
+++ b/src/mongo/executor/SConscript
@@ -26,6 +26,17 @@ env.Library(target='remote_command',
'$BUILD_DIR/mongo/util/net/network',
])
+env.Library(target='async_multicaster',
+ source=[
+ 'async_multicaster.cpp',
+ ],
+ LIBDEPS=[
+ 'remote_command',
+ 'task_executor_interface',
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/util/net/network',
+ ])
+
env.Library(target='task_executor_interface',
source=[
'task_executor.cpp',
diff --git a/src/mongo/executor/async_multicaster.cpp b/src/mongo/executor/async_multicaster.cpp
new file mode 100644
index 00000000000..0be2b05f47b
--- /dev/null
+++ b/src/mongo/executor/async_multicaster.cpp
@@ -0,0 +1,104 @@
+/**
+ * Copyright (C) 2017 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/executor/async_multicaster.h"
+
+#include <memory>
+
+#include "mongo/base/status.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/stdx/condition_variable.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+namespace executor {
+
+AsyncMulticaster::AsyncMulticaster(executor::TaskExecutor* executor, Options options)
+ : _options(options), _executor(executor) {}
+
+std::vector<AsyncMulticaster::Reply> AsyncMulticaster::multicast(
+ const std::vector<HostAndPort> servers,
+ const std::string& theDbName,
+ const BSONObj& theCmdObj,
+ OperationContext* opCtx,
+ Milliseconds timeoutMillis) {
+
+ // Everything goes into a state struct because we can get cancelled, and then our callback would
+ // be invoked later.
+ struct State {
+ State(size_t leftToDo) : leftToDo(leftToDo) {}
+
+ stdx::mutex mutex;
+ stdx::condition_variable cv;
+ size_t leftToDo;
+ size_t running = 0;
+
+ // To indicate which hosts fail.
+ std::vector<Reply> out;
+ };
+
+ auto state = std::make_shared<State>(servers.size());
+ for (const auto& server : servers) {
+ stdx::unique_lock<stdx::mutex> lk(state->mutex);
+ // spin up no more than maxConcurrency tasks at once
+ opCtx->waitForConditionOrInterrupt(
+ state->cv, lk, [&] { return state->running < _options.maxConcurrency; });
+ ++state->running;
+
+ uassertStatusOK(_executor->scheduleRemoteCommand(
+ RemoteCommandRequest{server, theDbName, theCmdObj, opCtx, timeoutMillis},
+ [state](const TaskExecutor::RemoteCommandCallbackArgs& cbData) {
+ stdx::lock_guard<stdx::mutex> lk(state->mutex);
+
+ state->out.emplace_back(
+ std::forward_as_tuple(cbData.request.target, cbData.response));
+
+ // If we were the last job, flush the done flag and release via notify.
+ if (!--(state->leftToDo)) {
+ state->cv.notify_one();
+ }
+
+ if (--(state->running) < kMaxConcurrency) {
+ state->cv.notify_one();
+ }
+ }));
+ }
+
+ stdx::unique_lock<stdx::mutex> lk(state->mutex);
+ opCtx->waitForConditionOrInterrupt(state->cv, lk, [&] { return state->leftToDo == 0; });
+
+ return std::move(state->out);
+}
+
+} // namespace executor
+} // namespace mongo
diff --git a/src/mongo/executor/async_multicaster.h b/src/mongo/executor/async_multicaster.h
new file mode 100644
index 00000000000..239fbf0978f
--- /dev/null
+++ b/src/mongo/executor/async_multicaster.h
@@ -0,0 +1,79 @@
+/**
+ * Copyright (C) 2017 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/executor/remote_command_response.h"
+#include "mongo/executor/task_executor.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/util/net/hostandport.h"
+
+namespace mongo {
+namespace executor {
+
+/**
+ * An async harness for scatter/gathering a command across an arbitrary number of specific hosts
+ */
+class AsyncMulticaster {
+public:
+ using Reply = std::tuple<HostAndPort, executor::RemoteCommandResponse>;
+ static constexpr size_t kMaxConcurrency = 100;
+
+ struct Options {
+ // maxConcurrency controls the maximum number of inflight operations. I.e. limiting it
+ // prevents the fan out from overwhelming the host, if the number of servers to multicast
+ // to is very high.
+ size_t maxConcurrency = kMaxConcurrency;
+ };
+
+ AsyncMulticaster(executor::TaskExecutor* executor, Options options);
+
+ /**
+ * Sends the cmd out to all passed servers (via the executor), observing the multicaster's
+ * maxConcurrency.
+ *
+ * The timeout value on multicast is per operation. The overall timeout will be:
+ * timeoutMillis - if max concurrency is greater than servers.size()
+ * or
+ * timeoutMillis * (servers.size() / maxConcurrency) - if not
+ */
+ std::vector<Reply> multicast(const std::vector<HostAndPort> servers,
+ const std::string& theDbName,
+ const BSONObj& theCmdObj,
+ OperationContext* opCtx,
+ Milliseconds timeoutMillis);
+
+private:
+ Options _options;
+ executor::TaskExecutor* _executor;
+};
+
+} // namespace executor
+} // namespace mongo
diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript
index 75db97fa257..75fb56c876b 100644
--- a/src/mongo/s/commands/SConscript
+++ b/src/mongo/s/commands/SConscript
@@ -61,6 +61,7 @@ env.Library(
'cluster_merge_chunks_cmd.cpp',
'cluster_move_chunk_cmd.cpp',
'cluster_move_primary_cmd.cpp',
+ 'cluster_multicast.cpp',
'cluster_netstat_cmd.cpp',
'cluster_pipeline_cmd.cpp',
'cluster_plan_cache_cmd.cpp',
@@ -79,7 +80,9 @@ env.Library(
'cluster_write.cpp',
'cluster_write_cmd.cpp',
'commands_public.cpp',
+ 'kill_sessions_remote.cpp',
'strategy.cpp',
+ env.Idlc('cluster_multicast.idl')[0],
],
LIBDEPS=[
'$BUILD_DIR/mongo/db/auth/authmongos',
@@ -88,6 +91,7 @@ env.Library(
'$BUILD_DIR/mongo/db/commands/write_commands_common',
'$BUILD_DIR/mongo/db/pipeline/aggregation',
'$BUILD_DIR/mongo/db/views/views',
+ '$BUILD_DIR/mongo/executor/async_multicaster',
'$BUILD_DIR/mongo/rpc/client_metadata',
'$BUILD_DIR/mongo/s/async_requests_sender',
'$BUILD_DIR/mongo/s/client/parallel',
diff --git a/src/mongo/s/commands/cluster_multicast.cpp b/src/mongo/s/commands/cluster_multicast.cpp
new file mode 100644
index 00000000000..56af56e255a
--- /dev/null
+++ b/src/mongo/s/commands/cluster_multicast.cpp
@@ -0,0 +1,158 @@
+/**
+ * Copyright (C) 2017 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 <vector>
+
+#include "mongo/base/init.h"
+#include "mongo/db/commands.h"
+#include "mongo/executor/async_multicaster.h"
+#include "mongo/executor/task_executor_pool.h"
+#include "mongo/s/catalog/sharding_catalog_client.h"
+#include "mongo/s/catalog/type_shard.h"
+#include "mongo/s/client/shard_registry.h"
+#include "mongo/s/commands/cluster_multicast_gen.h"
+#include "mongo/s/grid.h"
+
+namespace mongo {
+namespace {
+
+std::vector<HostAndPort> getAllClusterHosts(OperationContext* opCtx) {
+ auto registry = Grid::get(opCtx)->shardRegistry();
+
+ std::vector<ShardId> shardIds;
+ registry->getAllShardIds(&shardIds);
+
+ std::vector<HostAndPort> servers;
+ for (const auto& shardId : shardIds) {
+ auto shard = uassertStatusOK(registry->getShard(opCtx, shardId));
+
+ auto cs = shard->getConnString();
+ for (auto&& host : cs.getServers()) {
+ servers.emplace_back(host);
+ }
+ }
+
+ return servers;
+}
+
+class MulticastCmd : public BasicCommand {
+public:
+ MulticastCmd() : BasicCommand("multicast") {}
+
+ bool slaveOk() const override {
+ return true;
+ }
+
+ bool adminOnly() const override {
+ return true;
+ }
+
+
+ bool supportsWriteConcern(const BSONObj& cmd) const override {
+ return false;
+ }
+
+ void help(std::stringstream& help) const override {
+ help << "multicasts a command to hosts in a system";
+ }
+
+ // no privs because it's a test command
+ void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) override {}
+
+ bool run(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) override {
+ IDLParserErrorContext ctx("ClusterMulticastArgs");
+ auto args = ClusterMulticastArgs::parse(ctx, cmdObj);
+
+ // Grab an arbitrary executor.
+ auto executor = Grid::get(opCtx)->getExecutorPool()->getArbitraryExecutor();
+
+ // Grab all hosts in the cluster.
+ auto servers = getAllClusterHosts(opCtx);
+
+ executor::AsyncMulticaster::Options options;
+ if (args.getConcurrency()) {
+ options.maxConcurrency = *args.getConcurrency();
+ }
+
+ auto results =
+ executor::AsyncMulticaster(executor, options)
+ .multicast(servers,
+ args.getDb().toString(),
+ args.getMulticast(),
+ opCtx,
+ (args.getTimeout() ? Milliseconds(*args.getTimeout())
+ : executor::RemoteCommandRequest::kNoTimeout));
+
+ bool success = true;
+
+ {
+ BSONObjBuilder bob(result.subobjStart("hosts"));
+
+ for (const auto& r : results) {
+ HostAndPort host;
+ executor::RemoteCommandResponse response;
+ std::tie(host, response) = r;
+
+ if (!response.isOK() || !response.data["ok"].trueValue()) {
+ success = false;
+ }
+
+ {
+ BSONObjBuilder subbob(bob.subobjStart(host.toString()));
+
+ if (appendCommandStatus(subbob, response.status)) {
+ subbob.append("data", response.data);
+ subbob.append("metadata", response.metadata);
+ if (response.elapsedMillis) {
+ subbob.append("elapsedMillis", response.elapsedMillis->count());
+ }
+ }
+ }
+ }
+ }
+
+ return success;
+ }
+};
+
+MONGO_INITIALIZER(RegisterMulticast)(InitializerContext* context) {
+ if (Command::testCommandsEnabled) {
+ new MulticastCmd();
+ }
+ return Status::OK();
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_multicast.idl b/src/mongo/s/commands/cluster_multicast.idl
new file mode 100644
index 00000000000..cbed46fd512
--- /dev/null
+++ b/src/mongo/s/commands/cluster_multicast.idl
@@ -0,0 +1,37 @@
+# Copyright (C) 2017 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/>.
+#
+
+global:
+ cpp_namespace: "mongo"
+
+imports:
+ - "mongo/idl/basic_types.idl"
+
+structs:
+
+ ClusterMulticastArgs:
+ description: "A struct representing cluster multicast args"
+ strict: false
+ fields:
+ multicast: object
+ $db:
+ type: string
+ cpp_name: db
+ concurrency:
+ type: int
+ optional: true
+ timeout:
+ type: int
+ optional: true
diff --git a/src/mongo/s/commands/kill_sessions_remote.cpp b/src/mongo/s/commands/kill_sessions_remote.cpp
new file mode 100644
index 00000000000..a89e407eb45
--- /dev/null
+++ b/src/mongo/s/commands/kill_sessions_remote.cpp
@@ -0,0 +1,137 @@
+/**
+ * Copyright (C) 2017 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::kCommand
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/s/commands/kill_sessions_remote.h"
+
+#include "mongo/db/client.h"
+#include "mongo/db/kill_sessions_common.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/server_parameters.h"
+#include "mongo/db/service_context.h"
+#include "mongo/executor/async_multicaster.h"
+#include "mongo/executor/task_executor_pool.h"
+#include "mongo/s/client/shard.h"
+#include "mongo/s/client/shard_registry.h"
+#include "mongo/s/grid.h"
+#include "mongo/s/query/cluster_cursor_manager.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+namespace {
+
+constexpr size_t kMaxConcurrency = 100;
+constexpr Milliseconds kTimeout = Milliseconds(60000);
+
+MONGO_EXPORT_STARTUP_SERVER_PARAMETER(KillSessionsMaxConcurrency, int, kMaxConcurrency);
+MONGO_EXPORT_STARTUP_SERVER_PARAMETER(KillSessionsPerHostTimeoutMS, int, kTimeout.count());
+
+/**
+ * Get all hosts in the cluster.
+ */
+std::vector<HostAndPort> getAllClusterHosts(OperationContext* opCtx) {
+ auto registry = Grid::get(opCtx)->shardRegistry();
+
+ std::vector<ShardId> shardIds;
+ registry->getAllShardIds(&shardIds);
+
+ std::vector<HostAndPort> servers;
+ for (const auto& shardId : shardIds) {
+ auto shard = uassertStatusOK(registry->getShard(opCtx, shardId));
+
+ auto cs = shard->getConnString();
+ for (auto&& host : cs.getServers()) {
+ servers.emplace_back(host);
+ }
+ }
+
+ return servers;
+}
+
+/**
+ * A function for running an arbitrary command on all shards. Only returns which hosts failed.
+ */
+SessionKiller::Result parallelExec(OperationContext* opCtx,
+ const BSONObj& cmd,
+ SessionKiller::UniformRandomBitGenerator* urbg) {
+ // Grab an arbitrary executor.
+ auto executor = Grid::get(opCtx)->getExecutorPool()->getArbitraryExecutor();
+
+ // Grab all hosts in the cluster.
+ auto servers = getAllClusterHosts(opCtx);
+ std::shuffle(servers.begin(), servers.end(), *urbg);
+
+ // To indicate which hosts fail.
+ std::vector<HostAndPort> failed;
+
+ executor::AsyncMulticaster::Options options;
+ options.maxConcurrency = KillSessionsMaxConcurrency;
+ auto results =
+ executor::AsyncMulticaster(executor, options)
+ .multicast(servers, "admin", cmd, opCtx, Milliseconds(KillSessionsPerHostTimeoutMS));
+
+ for (const auto& result : results) {
+ if (!std::get<1>(result).isOK()) {
+ failed.push_back(std::get<0>(result));
+ }
+ }
+
+ return failed;
+}
+
+Status killSessionsRemoteKillCursor(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher) {
+ return Grid::get(opCtx)->getCursorManager()->killCursorsWithMatchingSessions(opCtx, matcher);
+}
+
+} // namespace
+
+/**
+ * This kill function (meant for mongos), kills matching local ops first, then fans out to all other
+ * nodes in the cluster to kill them as well.
+ */
+SessionKiller::Result killSessionsRemote(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher,
+ SessionKiller::UniformRandomBitGenerator* urbg) {
+ // First kill local sessions.
+ uassertStatusOK(killSessionsRemoteKillCursor(opCtx, matcher));
+ uassertStatusOK(killSessionsLocalKillOps(opCtx, matcher));
+
+ // Generate the kill command.
+ KillAllSessionsByPatternCmd cmd;
+ cmd.setKillAllSessionsByPattern(std::vector<KillAllSessionsByPattern>{
+ matcher.getPatterns().begin(), matcher.getPatterns().end()});
+
+ return parallelExec(opCtx, cmd.toBSON(), urbg);
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/commands/kill_sessions_remote.h b/src/mongo/s/commands/kill_sessions_remote.h
new file mode 100644
index 00000000000..0be3cc7189e
--- /dev/null
+++ b/src/mongo/s/commands/kill_sessions_remote.h
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2017 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 "mongo/db/kill_sessions.h"
+
+#include "mongo/db/session_killer.h"
+
+namespace mongo {
+
+/**
+ * The killSessions killer for running on mongos
+ */
+SessionKiller::Result killSessionsRemote(OperationContext* opCtx,
+ const SessionKiller::Matcher& patterns,
+ SessionKiller::UniformRandomBitGenerator* urbg);
+
+} // namespace mongo
diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp
index a17e73199d4..9d6aa9e72f5 100644
--- a/src/mongo/s/commands/strategy.cpp
+++ b/src/mongo/s/commands/strategy.cpp
@@ -217,14 +217,14 @@ void execCommandClient(OperationContext* opCtx,
try {
bool ok = false;
if (!supportsWriteConcern) {
- ok = c->enhancedRun(opCtx, request, result);
+ ok = c->publicRun(opCtx, request, result);
} else {
// Change the write concern while running the command.
const auto oldWC = opCtx->getWriteConcern();
ON_BLOCK_EXIT([&] { opCtx->setWriteConcern(oldWC); });
opCtx->setWriteConcern(wcResult.getValue());
- ok = c->enhancedRun(opCtx, request, result);
+ ok = c->publicRun(opCtx, request, result);
}
if (!ok) {
c->incrementCommandsFailed();
diff --git a/src/mongo/s/query/SConscript b/src/mongo/s/query/SConscript
index 2977050be19..259e2d28244 100644
--- a/src/mongo/s/query/SConscript
+++ b/src/mongo/s/query/SConscript
@@ -133,6 +133,8 @@ env.Library(
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/auth/authcore',
+ '$BUILD_DIR/mongo/db/kill_sessions',
'$BUILD_DIR/mongo/db/logical_session_id',
],
)
diff --git a/src/mongo/s/query/cluster_cursor_manager.cpp b/src/mongo/s/query/cluster_cursor_manager.cpp
index 99e7b2afa82..25e63a50d54 100644
--- a/src/mongo/s/query/cluster_cursor_manager.cpp
+++ b/src/mongo/s/query/cluster_cursor_manager.cpp
@@ -34,6 +34,7 @@
#include <set>
+#include "mongo/db/kill_sessions_common.h"
#include "mongo/util/clock_source.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
@@ -471,6 +472,17 @@ void ClusterCursorManager::appendActiveSessions(LogicalSessionIdSet* lsids) cons
}
}
+Status ClusterCursorManager::killCursorsWithMatchingSessions(
+ OperationContext* opCtx, const SessionKiller::Matcher& matcher) {
+ auto eraser = [&](ClusterCursorManager& mgr, CursorId id) {
+ uassertStatusOK(mgr.killCursor(getNamespaceForCursorId(id).get(), id));
+ };
+
+ auto visitor = makeKillSessionsCursorManagerVisitor(opCtx, matcher, std::move(eraser));
+ visitor(*this);
+ return visitor.getStatus();
+}
+
stdx::unordered_set<CursorId> ClusterCursorManager::getCursorsForSession(
LogicalSessionId lsid) const {
stdx::lock_guard<stdx::mutex> lk(_mutex);
diff --git a/src/mongo/s/query/cluster_cursor_manager.h b/src/mongo/s/query/cluster_cursor_manager.h
index f6da640bb1e..d6eb8f47abf 100644
--- a/src/mongo/s/query/cluster_cursor_manager.h
+++ b/src/mongo/s/query/cluster_cursor_manager.h
@@ -32,7 +32,9 @@
#include <vector>
#include "mongo/db/cursor_id.h"
+#include "mongo/db/kill_sessions.h"
#include "mongo/db/namespace_string.h"
+#include "mongo/db/session_killer.h"
#include "mongo/platform/random.h"
#include "mongo/s/query/cluster_client_cursor.h"
#include "mongo/stdx/mutex.h"
@@ -353,6 +355,9 @@ public:
*/
void appendActiveSessions(LogicalSessionIdSet* lsids) const;
+ Status killCursorsWithMatchingSessions(OperationContext* opCtx,
+ const SessionKiller::Matcher& matcher);
+
/**
* Returns a list of all open cursors for the given session.
*/
diff --git a/src/mongo/s/server.cpp b/src/mongo/s/server.cpp
index 521702e56b8..27de3a7994f 100644
--- a/src/mongo/s/server.cpp
+++ b/src/mongo/s/server.cpp
@@ -52,6 +52,7 @@
#include "mongo/db/client.h"
#include "mongo/db/ftdc/ftdc_mongos.h"
#include "mongo/db/initialize_server_global_state.h"
+#include "mongo/db/kill_sessions.h"
#include "mongo/db/lasterror.h"
#include "mongo/db/log_process_details.h"
#include "mongo/db/logical_clock.h"
@@ -62,6 +63,7 @@
#include "mongo/db/server_options.h"
#include "mongo/db/service_context.h"
#include "mongo/db/service_context_noop.h"
+#include "mongo/db/session_killer.h"
#include "mongo/db/startup_warnings_common.h"
#include "mongo/db/wire_version.h"
#include "mongo/executor/task_executor_pool.h"
@@ -75,6 +77,7 @@
#include "mongo/s/client/shard_registry.h"
#include "mongo/s/client/shard_remote.h"
#include "mongo/s/client/sharding_connection_hook.h"
+#include "mongo/s/commands/kill_sessions_remote.h"
#include "mongo/s/config_server_catalog_cache_loader.h"
#include "mongo/s/grid.h"
#include "mongo/s/is_mongos.h"
@@ -395,6 +398,10 @@ static ExitCode runMongosServer() {
runner->startup().transitional_ignore();
getGlobalServiceContext()->setPeriodicRunner(std::move(runner));
+ SessionKiller::set(
+ getGlobalServiceContext(),
+ std::make_shared<SessionKiller>(getGlobalServiceContext(), killSessionsRemote));
+
// Set up the logical session cache
LogicalSessionCache::set(getGlobalServiceContext(), makeLogicalSessionCacheS());