summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/auth/lib/commands_lib.js23
-rw-r--r--jstests/replsets/oplog_note_cmd.js21
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/base/error_codes.err1
-rw-r--r--src/mongo/db/auth/action_types.txt1
-rw-r--r--src/mongo/db/auth/role_graph_builtin_roles.cpp7
-rw-r--r--src/mongo/db/commands/oplog_note.cpp82
7 files changed, 134 insertions, 2 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 1dca80e1ac2..a5bd6a47b4f 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -308,6 +308,29 @@ var authCommandsLib = {
]
},
{
+ testname: "appendOplogNote",
+ command: {appendOplogNote: 1, data: {a: 1}},
+ skipSharded: true,
+ testcases: [
+ {
+ runOnDb: adminDbName,
+ roles: {
+ backup: 1,
+ clusterManager: 1,
+ clusterAdmin: 1,
+ root: 1,
+ __system: 1
+ },
+ privileges: [
+ { resource: {cluster: true}, actions: ["appendOplogNote"] }
+ ],
+ expectFail: true, // because no replication enabled
+ },
+ { runOnDb: firstDbName, roles: {} },
+ { runOnDb: secondDbName, roles: {} }
+ ]
+ },
+ {
testname: "buildInfo",
command: {buildInfo: 1},
testcases: [
diff --git a/jstests/replsets/oplog_note_cmd.js b/jstests/replsets/oplog_note_cmd.js
new file mode 100644
index 00000000000..2db0c2ae0b1
--- /dev/null
+++ b/jstests/replsets/oplog_note_cmd.js
@@ -0,0 +1,21 @@
+// Test that the "appendOplogNote" command works properly
+
+var rs = new ReplSetTest({name: "oplogNoteTest", nodes: 1});
+rs.startSet();
+rs.initiate();
+
+var primary = rs.getPrimary();
+var db = primary.getDB('admin');
+db.foo.insert({a:1});
+
+// Make sure "optime" field gets updated
+var statusBefore = db.runCommand({replSetGetStatus: 1});
+assert.commandWorked(db.runCommand({appendOplogNote: 1, data: {a: 1}}));
+var statusAfter = db.runCommand({replSetGetStatus: 1});
+assert.lt(statusBefore.members[0].optime, statusAfter.members[0].optime);
+
+// Make sure note written successfully
+var op = db.getSiblingDB('local').oplog.rs.find().sort({$natural: -1}).limit(1).next();
+assert.eq(1, op.o.a);
+
+rs.stopSet(); \ No newline at end of file
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 0316d177ddc..fc6519e4a83 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -633,6 +633,7 @@ serverOnlyFiles = [ "db/curop.cpp",
"db/commands/group.cpp",
"db/commands/index_stats.cpp",
"db/commands/mr.cpp",
+ "db/commands/oplog_note.cpp",
"db/commands/pipeline_command.cpp",
"db/commands/plan_cache_commands.cpp",
"db/commands/rename_collection.cpp",
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index 936d77a33fc..c29612eafa3 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -75,6 +75,7 @@ error_code("InvalidOptions", 72)
error_code("InvalidNamespace", 73)
error_code("NodeNotFound", 74)
error_code("WriteConcernLegacyOK", 75)
+error_code("NoReplicationEnabled", 76)
# Non-sequential error codes (for compatibility only)
error_code("DuplicateKey", 11000)
diff --git a/src/mongo/db/auth/action_types.txt b/src/mongo/db/auth/action_types.txt
index 7b0f8022f19..fed412281b3 100644
--- a/src/mongo/db/auth/action_types.txt
+++ b/src/mongo/db/auth/action_types.txt
@@ -6,6 +6,7 @@
# also may change between versions.
["addShard",
"anyAction", # Special ActionType that represents *all* actions
+"appendOplogNote",
"applicationMessage", # Not used for permissions checks, but to id the event in logs.
"auditLogRotate", # Not used for permissions checks, but to id the event in logs.
"authCheck", # Not used for permissions checks, but to id the authorization-checking event in logs.
diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp
index 85f8b33f058..d4c3a9060e1 100644
--- a/src/mongo/db/auth/role_graph_builtin_roles.cpp
+++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp
@@ -218,6 +218,7 @@ namespace {
// clusterManager role actions that target the cluster resource
clusterManagerRoleClusterActions
+ << ActionType::appendOplogNote // backup gets this also
<< ActionType::applicationMessage // hostManager gets this also
<< ActionType::replSetConfigure
<< ActionType::replSetGetStatus // clusterMonitor gets this also
@@ -464,9 +465,11 @@ namespace {
privileges,
Privilege(ResourcePattern::forAnyNormalResource(), ActionType::find));
+ ActionSet clusterActions;
+ clusterActions << ActionType::listDatabases
+ << ActionType::appendOplogNote;
Privilege::addPrivilegeToPrivilegeVector(
- privileges,
- Privilege(ResourcePattern::forClusterResource(), ActionType::listDatabases));
+ privileges, Privilege(ResourcePattern::forClusterResource(), clusterActions));
Privilege::addPrivilegeToPrivilegeVector(
privileges,
diff --git a/src/mongo/db/commands/oplog_note.cpp b/src/mongo/db/commands/oplog_note.cpp
new file mode 100644
index 00000000000..a1d280b3755
--- /dev/null
+++ b/src/mongo/db/commands/oplog_note.cpp
@@ -0,0 +1,82 @@
+/**
+ * Copyright (C) 2013 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 <string>
+
+#include "mongo/bson/util/bson_extract.h"
+#include "mongo/db/auth/action_type.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/auth/resource_pattern.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/repl/oplog.h"
+#include "mongo/db/repl/replication_server_status.h"
+
+namespace mongo {
+ class AppendOplogNoteCmd : public Command {
+ public:
+ AppendOplogNoteCmd() : Command( "appendOplogNote" ) {}
+ virtual bool slaveOk() const { return false; }
+ virtual bool adminOnly() const { return true; }
+ virtual LockType locktype() const { return NONE; }
+ virtual void help( stringstream &help ) const {
+ help << "Adds a no-op entry to the oplog";
+ }
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!client->getAuthorizationSession()->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::appendOplogNote)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ return Status::OK();
+ }
+ virtual bool run(const string& dbname,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result,
+ bool fromRepl) {
+ if (!replSettings.master) {
+ return appendCommandStatus(result, Status(
+ ErrorCodes::NoReplicationEnabled,
+ "Must have replication set up to run \"appendOplogNote\""));
+ }
+ BSONElement dataElement;
+ Status status = bsonExtractTypedField(cmdObj, "data", Object, &dataElement);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
+
+ logOpComment(dataElement.Obj());
+ return true;
+ }
+
+ } appendOplogNoteCmd;
+
+} // namespace mongo