Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

SERVER-7982 Upgrade indexes on system.users collections when starting…

… up / opening a database.
  • Loading branch information...
commit cb015fbf94cacf909ce4374ec90be1d2a055fee3 1 parent ec846a5
@andy10gen andy10gen authored
View
145 jstests/multiVersion/auth_index_upgrade_downgrade.js
@@ -0,0 +1,145 @@
+//
+// Upgrade and downgrade a MongoD node with existing system.users collections.
+//
+// Between 2.2 and 2.4, the schema of system.users documents expanded, and the uniqueness
+// constraints changed. As a result, the indexes on system.users collections must be replaced.
+//
+// Theory of operation:
+//
+// Running version 2.2:
+// * Construct a database, "old", and insert elements into old.system.users.
+// * Construct { user: 1 } unique index on old.system.users.
+// Restart the node running version "latest":
+// * Construct a database, "new", and insert elements into new.system.users.
+// * Verify the presence of the { user: 1, userSource: 1 } unique index on new.system.users.
+// * Verify the presence of the { user: 1, userSource: 1 } unique index on old.system.users.
+// * Verify the absence of the { user: 1 } unique index on old.system.users.
+// * Verify the absence of the { user: 1 } unique index on new.system.users.
+// * Verify can insert privilege documents that would have conflicted in 2.2 into the database.
+// * Verify that the authenticate command works.
+// * Remove the conflicting entries.
+// Restart the node running version 2.2:
+// * Verify that it didn't crash.
+// * Verify that the authenticate command works.
+// Restart the node running version "latest":
+// * Verify that it didn't crash.
+// * Verify that the authenticate command works.
+// * Verify that the desired indexes are present.
+// * Verify can insert privilege documents that would have conflicted in 2.2 into the database.
+
+var oldVersion = "2.2.2";
+var newVersion = "latest";
+
+// Raises an exception if "status" is not a GetLastError object indicating success.
+function assertGLEOK(status) {
+ assert(status.ok && status.err === null,
+ "Expected OK status object; found " + tojson(status));
+}
+
+// Raises an exception if "status" is not a GetLastError object indicating failure.
+function assertGLENotOK(status) {
+ assert(status.ok && status.err !== null,
+ "Expected not-OK status object; found " + tojson(status));
+}
+
+// Finds and returns a cursor over all indexes on "collectionName" in database object "db" with
+// the given "keyPattern".
+function findIndex(db, collectionName, keyPattern) {
+ return db.system.indexes.find({ key: keyPattern, ns: db.getName() + '.' + collectionName });
+}
+
+// Asserts that an index matching "keyPattern" is present for "collectionName" in "db".
+function assertIndexExists(db, collectionName, keyPattern) {
+ assert.eq(1, findIndex(db, collectionName, keyPattern).itcount());
+}
+
+// Asserts that an index matching "keyPattern" is absent for "collectionName" in "db".
+function assertIndexDoesNotExist(db, collectionName, keyPattern) {
+ assert.eq(0, findIndex(db, collectionName, keyPattern).itcount());
+}
+
+// Asserts that inserting "obj" into "collection" succeeds.
+function assertInsertSucceeds(collection, obj) {
+ collection.insert(obj);
+ assertGLEOK(collection.getDB().getLastErrorObj());
+}
+
+// Asserts that inserting "obj" into "collection" fails.
+function assertInsertFails(collection, obj) {
+ collection.insert(obj);
+ assertGLENotOK(collection.getDB().getLastErrorObj());
+}
+
+// Runs the function "action" with database objects for every database named in "dbNames", using
+// "conn" as the connection object.
+function withDbs(conn, dbNames, action) {
+ var dbs = [];
+ var i;
+ for (i = 0; i < dbNames.length; ++i) {
+ dbs.push(conn.getDB(dbNames[i]));
+ }
+ action.apply(null, dbs);
+}
+
+//
+// With oldVersion
+//
+var conn = MongoRunner.runMongod({ remember: true, binVersion: oldVersion, smallfiles: "" });
+
+withDbs(conn, ["old"], function (dbOld) {
+ dbOld.system.users.ensureIndex({ user: 1 }, { unique: 1 });
+ assertGLEOK(dbOld.getLastErrorObj());
+
+ assertInsertSucceeds(dbOld.system.users, {user: 'andy', pwd: hex_md5('andy:mongo:a')});
+ assertInsertSucceeds(dbOld.system.users, {user: 'spencer', pwd: hex_md5('spencer:mongo:a')});
+ assertInsertFails(dbOld.system.users, {user: 'spencer', pwd: hex_md5('spencer:mongo:b')});
+ assert(dbOld.auth('andy', 'a'));
+});
+
+//
+// With newVersion
+//
+MongoRunner.stopMongod(conn);
+conn = MongoRunner.runMongod({ restart: conn, binVersion: newVersion });
+withDbs(conn, ["old", "new"], function (dbOld, dbNew) {
+
+ assertInsertSucceeds(dbNew.system.users, {user: 'andy', pwd: hex_md5('andy:mongo:a')});
+ assertInsertSucceeds(dbNew.system.users, {user: 'andy', userSource: 'old', roles: ["read"]});
+
+ assertIndexExists(dbOld, 'system.users', { user: 1, userSource: 1 });
+ assertIndexExists(dbNew, 'system.users', { user: 1, userSource: 1 });
+ assertIndexDoesNotExist(dbOld, 'system.users', { user: 1 });
+ assertIndexDoesNotExist(dbNew, 'system.users', { user: 1 });
+
+ dbNew.system.users.remove({user: 'andy', userSource: 'old'});
+ assert(dbNew.auth('andy', 'a'));
+ assert(dbOld.auth('andy', 'a'));
+});
+
+//
+// Again with oldVersion
+//
+MongoRunner.stopMongod(conn);
+conn = MongoRunner.runMongod({ restart: conn, binVersion: oldVersion });
+withDbs(conn, ["old", "new"], function (dbOld, dbNew) {
+ assert.eq(1, dbNew.system.users.find({user: 'andy'}).itcount());
+ assert.eq(1, dbOld.system.users.find({user: 'andy'}).itcount());
+ assert(dbNew.auth('andy', 'a'));
+ assert(dbOld.auth('andy', 'a'));
+});
+
+
+//
+// Again with newVersion
+//
+MongoRunner.stopMongod(conn);
+conn = MongoRunner.runMongod({ restart: conn, binVersion: newVersion });
+withDbs(conn, ["old", "new"], function (dbOld, dbNew) {
+ assert(dbNew.auth('andy', 'a'));
+ assert(dbOld.auth('andy', 'a'));
+ assertIndexExists(dbOld, 'system.users', { user: 1, userSource: 1 });
+ assertIndexExists(dbNew, 'system.users', { user: 1, userSource: 1 });
+ assertIndexDoesNotExist(dbOld, 'system.users', { user: 1 });
+ assertIndexDoesNotExist(dbNew, 'system.users', { user: 1 });
+ assertInsertSucceeds(dbNew.system.users, {user: 'andy', userSource: 'old', roles: ["read"]});
+});
View
3  src/mongo/db/auth/SConscript
@@ -25,7 +25,8 @@ env.StaticLibrary('authservercommon',
LIBDEPS=['authcore'])
env.StaticLibrary('authmongod',
- ['auth_external_state_d.cpp'],
+ ['auth_external_state_d.cpp',
+ 'auth_index_d.cpp'],
LIBDEPS=['authservercommon'])
env.StaticLibrary('authmongos',
View
101 src/mongo/db/auth/auth_index_d.cpp
@@ -0,0 +1,101 @@
+/**
+* Copyright (C) 2012 10gen 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/>.
+*/
+
+#include "mongo/db/auth/auth_index_d.h"
+
+#include "mongo/base/init.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/client.h"
+#include "mongo/db/dbhelpers.h"
+#include "mongo/db/index_update.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/namespace_details.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+namespace authindex {
+
+namespace {
+ BSONObj oldSystemUsersKeyPattern;
+ BSONObj extendedSystemUsersKeyPattern;
+ std::string extendedSystemUsersIndexName;
+
+ MONGO_INITIALIZER(AuthIndexKeyPatterns)(InitializerContext*) {
+ oldSystemUsersKeyPattern = BSON(AuthorizationManager::USER_NAME_FIELD_NAME << 1);
+ extendedSystemUsersKeyPattern = BSON(AuthorizationManager::USER_NAME_FIELD_NAME << 1 <<
+ AuthorizationManager::USER_SOURCE_FIELD_NAME << 1);
+ extendedSystemUsersIndexName = std::string(str::stream() <<
+ AuthorizationManager::USER_NAME_FIELD_NAME <<
+ "_1_" <<
+ AuthorizationManager::USER_SOURCE_FIELD_NAME <<
+ "_1");
+ return Status::OK();
+ }
+
+ void configureSystemUsersIndexes(const StringData& dbname) {
+ std::string systemUsers = dbname.toString() + ".system.users";
+ Client::WriteContext wctx(systemUsers);
+
+ createSystemIndexes(systemUsers);
+
+ NamespaceDetails* nsd = nsdetails(systemUsers.c_str());
+ if (nsd == NULL)
+ return;
+
+ NamespaceDetails::IndexIterator indexIter = nsd->ii();
+ std::vector<std::string> namedIndexesToDrop;
+
+ while (indexIter.more()) {
+ IndexDetails& idetails = indexIter.next();
+ if (idetails.keyPattern() == oldSystemUsersKeyPattern)
+ namedIndexesToDrop.push_back(idetails.indexName());
+ }
+ for (size_t i = 0; i < namedIndexesToDrop.size(); ++i) {
+ std::string errmsg;
+ BSONObjBuilder infoBuilder;
+
+ if (dropIndexes(nsd,
+ systemUsers.c_str(),
+ namedIndexesToDrop[i].c_str(),
+ errmsg,
+ infoBuilder,
+ false)) {
+ log() << "Dropped index " << namedIndexesToDrop[i] << " with key pattern " <<
+ oldSystemUsersKeyPattern << " from " << systemUsers <<
+ " because it is incompatible with extended form privilege documents." << endl;
+ }
+ else {
+ // Only reason should be orphaned index, which dropIndexes logged.
+ }
+ }
+ }
+} // namespace
+
+ void configureSystemIndexes(const StringData& dbname) {
+ configureSystemUsersIndexes(dbname);
+ }
+
+ void createSystemIndexes(const NamespaceString& ns) {
+ if (ns.coll == "system.users") {
+ Helpers::ensureIndex(ns.ns().c_str(),
+ extendedSystemUsersKeyPattern,
+ true, // unique
+ extendedSystemUsersIndexName.c_str());
+ }
+ }
+
+} // namespace authindex
+} // namespace mongo
View
42 src/mongo/db/auth/auth_index_d.h
@@ -0,0 +1,42 @@
+/**
+* Copyright (C) 2012 10gen 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/>.
+*/
+
+#pragma once
+
+#include "mongo/base/string_data.h"
+#include "mongo/db/namespacestring.h"
+
+namespace mongo {
+namespace authindex {
+
+ /**
+ * Ensures that exactly the appropriate indexes are present on system collections supporting
+ * authentication and authorization in database "dbname".
+ *
+ * It is appropriate to call this function on new or existing databases, though it is primarily
+ * intended for use on existing databases. Under no circumstances may it be called on databases
+ * with running operations.
+ */
+ void configureSystemIndexes(const StringData& dbname);
+
+ /**
+ * Creates the appropriate indexes on _new_ system collections supporting authentication and
+ * authorization.
+ */
+ void createSystemIndexes(const NamespaceString& ns);
+
+} // namespace authindex
+} // namespace mongo
View
19 src/mongo/db/database.cpp
@@ -16,16 +16,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "pch.h"
-#include "pdfile.h"
-#include "database.h"
-#include "instance.h"
-#include "introspect.h"
-#include "clientcursor.h"
-#include "databaseholder.h"
+#include "mongo/pch.h"
+
+#include "mongo/db/database.h"
#include <boost/filesystem/operations.hpp>
+#include "mongo/db/auth/auth_index_d.h"
+#include "mongo/db/clientcursor.h"
+#include "mongo/db/databaseholder.h"
+#include "mongo/db/instance.h"
+#include "mongo/db/introspect.h"
+#include "mongo/db/pdfile.h"
+
namespace mongo {
bool Database::_openAllFiles = true;
@@ -445,6 +448,8 @@ namespace mongo {
_size++;
}
+ authindex::configureSystemIndexes(dbname);
+
return db;
}
View
2  src/mongo/db/namespacestring.h
@@ -20,6 +20,8 @@
#include <string>
+#include "mongo/util/assert_util.h"
+
namespace mongo {
using std::string;
View
13 src/mongo/db/pdfile.cpp
@@ -32,6 +32,7 @@ _ disallow system* manipulations from the database.
#include <list>
#include "mongo/base/counter.h"
+#include "mongo/db/auth/auth_index_d.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/pdfile_private.h"
#include "mongo/db/background.h"
@@ -164,6 +165,13 @@ namespace mongo {
}
}
+ static void _ensureSystemIndexes(const char* ns) {
+ NamespaceString nsstring(ns);
+ if (StringData(nsstring.coll).substr(0, 7) == "system.") {
+ authindex::createSystemIndexes(nsstring);
+ }
+ }
+
string getDbContext() {
stringstream ss;
Client * c = currentClient.get();
@@ -329,7 +337,9 @@ namespace mongo {
else
ensureIdIndexForNewNs( ns );
}
-
+
+ _ensureSystemIndexes(ns);
+
if ( mx > 0 )
d->setMaxCappedDocs( mx );
@@ -1402,6 +1412,7 @@ namespace mongo {
NamespaceDetails *d = nsdetails(ns);
if ( !god )
ensureIdIndexForNewNs(ns);
+ _ensureSystemIndexes(ns);
addNewNamespaceToCatalog(ns);
return d;
}
Please sign in to comment.
Something went wrong with that request. Please try again.