Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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

… up / opening a database.
  • Loading branch information...
commit cb015fbf94cacf909ce4374ec90be1d2a055fee3 1 parent ec846a5
Andy Schwerin authored December 20, 2012
145  jstests/multiVersion/auth_index_upgrade_downgrade.js
... ...
@@ -0,0 +1,145 @@
  1
+//
  2
+// Upgrade and downgrade a MongoD node with existing system.users collections.
  3
+//
  4
+// Between 2.2 and 2.4, the schema of system.users documents expanded, and the uniqueness
  5
+// constraints changed.  As a result, the indexes on system.users collections must be replaced.
  6
+//
  7
+// Theory of operation:
  8
+//
  9
+// Running version 2.2:
  10
+//   * Construct a database, "old", and insert elements into old.system.users.
  11
+//   * Construct { user: 1 } unique index on old.system.users.
  12
+// Restart the node running version "latest":
  13
+//   * Construct a database, "new", and insert elements into new.system.users.
  14
+//   * Verify the presence of the { user: 1, userSource: 1 } unique index on new.system.users.
  15
+//   * Verify the presence of the { user: 1, userSource: 1 } unique index on old.system.users.
  16
+//   * Verify the absence of the { user: 1 } unique index on old.system.users.
  17
+//   * Verify the absence of the { user: 1 } unique index on new.system.users.
  18
+//   * Verify can insert privilege documents that would have conflicted in 2.2 into the database.
  19
+//   * Verify that the authenticate command works.
  20
+//   * Remove the conflicting entries.
  21
+// Restart the node running version 2.2:
  22
+//   * Verify that it didn't crash.
  23
+//   * Verify that the authenticate command works.
  24
+// Restart the node running version "latest":
  25
+//   * Verify that it didn't crash.
  26
+//   * Verify that the authenticate command works.
  27
+//   * Verify that the desired indexes are present.
  28
+//   * Verify can insert privilege documents that would have conflicted in 2.2 into the database.
  29
+
  30
+var oldVersion = "2.2.2";
  31
+var newVersion = "latest";
  32
+
  33
+// Raises an exception if "status" is not a GetLastError object indicating success.
  34
+function assertGLEOK(status) {
  35
+    assert(status.ok && status.err === null,
  36
+           "Expected OK status object; found " + tojson(status));
  37
+}
  38
+
  39
+// Raises an exception if "status" is not a GetLastError object indicating failure.
  40
+function assertGLENotOK(status) {
  41
+    assert(status.ok && status.err !== null,
  42
+           "Expected not-OK status object; found " + tojson(status));
  43
+}
  44
+
  45
+// Finds and returns a cursor over all indexes on "collectionName" in database object "db" with
  46
+// the given "keyPattern".
  47
+function findIndex(db, collectionName, keyPattern) {
  48
+    return db.system.indexes.find({ key: keyPattern, ns: db.getName() + '.' + collectionName });
  49
+}
  50
+
  51
+// Asserts that an index matching "keyPattern" is present for "collectionName" in "db".
  52
+function assertIndexExists(db, collectionName, keyPattern) {
  53
+    assert.eq(1, findIndex(db, collectionName, keyPattern).itcount());
  54
+}
  55
+
  56
+// Asserts that an index matching "keyPattern" is absent for "collectionName" in "db".
  57
+function assertIndexDoesNotExist(db, collectionName, keyPattern) {
  58
+    assert.eq(0, findIndex(db, collectionName, keyPattern).itcount());
  59
+}
  60
+
  61
+// Asserts that inserting "obj" into "collection" succeeds.
  62
+function assertInsertSucceeds(collection, obj) {
  63
+    collection.insert(obj);
  64
+    assertGLEOK(collection.getDB().getLastErrorObj());
  65
+}
  66
+
  67
+// Asserts that inserting "obj" into "collection" fails.
  68
+function assertInsertFails(collection, obj) {
  69
+    collection.insert(obj);
  70
+    assertGLENotOK(collection.getDB().getLastErrorObj());
  71
+}
  72
+
  73
+// Runs the function "action" with database objects for every database named in "dbNames", using
  74
+// "conn" as the connection object.
  75
+function withDbs(conn, dbNames, action) {
  76
+    var dbs = [];
  77
+    var i;
  78
+    for (i = 0; i < dbNames.length; ++i) {
  79
+        dbs.push(conn.getDB(dbNames[i]));
  80
+    }
  81
+    action.apply(null, dbs);
  82
+}
  83
+
  84
+//
  85
+// With oldVersion
  86
+//
  87
+var conn = MongoRunner.runMongod({ remember: true, binVersion: oldVersion, smallfiles: "" });
  88
+
  89
+withDbs(conn, ["old"], function (dbOld) {
  90
+    dbOld.system.users.ensureIndex({ user: 1 }, { unique: 1 });
  91
+    assertGLEOK(dbOld.getLastErrorObj());
  92
+
  93
+    assertInsertSucceeds(dbOld.system.users, {user: 'andy', pwd: hex_md5('andy:mongo:a')});
  94
+    assertInsertSucceeds(dbOld.system.users, {user: 'spencer', pwd: hex_md5('spencer:mongo:a')});
  95
+    assertInsertFails(dbOld.system.users, {user: 'spencer', pwd: hex_md5('spencer:mongo:b')});
  96
+    assert(dbOld.auth('andy', 'a'));
  97
+});
  98
+
  99
+//
  100
+// With newVersion
  101
+//
  102
+MongoRunner.stopMongod(conn);
  103
+conn = MongoRunner.runMongod({ restart: conn, binVersion: newVersion });
  104
+withDbs(conn, ["old", "new"], function (dbOld, dbNew) {
  105
+
  106
+    assertInsertSucceeds(dbNew.system.users, {user: 'andy', pwd: hex_md5('andy:mongo:a')});
  107
+    assertInsertSucceeds(dbNew.system.users, {user: 'andy', userSource: 'old', roles: ["read"]});
  108
+
  109
+    assertIndexExists(dbOld, 'system.users', { user: 1, userSource: 1 });
  110
+    assertIndexExists(dbNew, 'system.users', { user: 1, userSource: 1 });
  111
+    assertIndexDoesNotExist(dbOld, 'system.users', { user: 1 });
  112
+    assertIndexDoesNotExist(dbNew, 'system.users', { user: 1 });
  113
+
  114
+    dbNew.system.users.remove({user: 'andy', userSource: 'old'});
  115
+    assert(dbNew.auth('andy', 'a'));
  116
+    assert(dbOld.auth('andy', 'a'));
  117
+});
  118
+
  119
+//
  120
+// Again with oldVersion
  121
+//
  122
+MongoRunner.stopMongod(conn);
  123
+conn = MongoRunner.runMongod({ restart: conn, binVersion: oldVersion });
  124
+withDbs(conn, ["old", "new"], function (dbOld, dbNew) {
  125
+    assert.eq(1, dbNew.system.users.find({user: 'andy'}).itcount());
  126
+    assert.eq(1, dbOld.system.users.find({user: 'andy'}).itcount());
  127
+    assert(dbNew.auth('andy', 'a'));
  128
+    assert(dbOld.auth('andy', 'a'));
  129
+});
  130
+
  131
+
  132
+//
  133
+// Again with newVersion
  134
+//
  135
+MongoRunner.stopMongod(conn);
  136
+conn = MongoRunner.runMongod({ restart: conn, binVersion: newVersion });
  137
+withDbs(conn, ["old", "new"], function (dbOld, dbNew) {
  138
+    assert(dbNew.auth('andy', 'a'));
  139
+    assert(dbOld.auth('andy', 'a'));
  140
+    assertIndexExists(dbOld, 'system.users', { user: 1, userSource: 1 });
  141
+    assertIndexExists(dbNew, 'system.users', { user: 1, userSource: 1 });
  142
+    assertIndexDoesNotExist(dbOld, 'system.users', { user: 1 });
  143
+    assertIndexDoesNotExist(dbNew, 'system.users', { user: 1 });
  144
+    assertInsertSucceeds(dbNew.system.users, {user: 'andy', userSource: 'old', roles: ["read"]});
  145
+});
3  src/mongo/db/auth/SConscript
@@ -25,7 +25,8 @@ env.StaticLibrary('authservercommon',
25 25
                   LIBDEPS=['authcore'])
26 26
 
27 27
 env.StaticLibrary('authmongod',
28  
-                  ['auth_external_state_d.cpp'],
  28
+                  ['auth_external_state_d.cpp',
  29
+                   'auth_index_d.cpp'],
29 30
                   LIBDEPS=['authservercommon'])
30 31
 
31 32
 env.StaticLibrary('authmongos',
101  src/mongo/db/auth/auth_index_d.cpp
... ...
@@ -0,0 +1,101 @@
  1
+/**
  2
+*    Copyright (C) 2012 10gen Inc.
  3
+*
  4
+*    This program is free software: you can redistribute it and/or  modify
  5
+*    it under the terms of the GNU Affero General Public License, version 3,
  6
+*    as published by the Free Software Foundation.
  7
+*
  8
+*    This program is distributed in the hope that it will be useful,
  9
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of
  10
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11
+*    GNU Affero General Public License for more details.
  12
+*
  13
+*    You should have received a copy of the GNU Affero General Public License
  14
+*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15
+*/
  16
+
  17
+#include "mongo/db/auth/auth_index_d.h"
  18
+
  19
+#include "mongo/base/init.h"
  20
+#include "mongo/db/auth/authorization_manager.h"
  21
+#include "mongo/db/client.h"
  22
+#include "mongo/db/dbhelpers.h"
  23
+#include "mongo/db/index_update.h"
  24
+#include "mongo/db/jsobj.h"
  25
+#include "mongo/db/namespace_details.h"
  26
+#include "mongo/util/log.h"
  27
+
  28
+namespace mongo {
  29
+namespace authindex {
  30
+
  31
+namespace {
  32
+    BSONObj oldSystemUsersKeyPattern;
  33
+    BSONObj extendedSystemUsersKeyPattern;
  34
+    std::string extendedSystemUsersIndexName;
  35
+
  36
+    MONGO_INITIALIZER(AuthIndexKeyPatterns)(InitializerContext*) {
  37
+        oldSystemUsersKeyPattern = BSON(AuthorizationManager::USER_NAME_FIELD_NAME << 1);
  38
+        extendedSystemUsersKeyPattern = BSON(AuthorizationManager::USER_NAME_FIELD_NAME << 1 <<
  39
+                                             AuthorizationManager::USER_SOURCE_FIELD_NAME << 1);
  40
+        extendedSystemUsersIndexName = std::string(str::stream() <<
  41
+                                                   AuthorizationManager::USER_NAME_FIELD_NAME <<
  42
+                                                   "_1_" <<
  43
+                                                   AuthorizationManager::USER_SOURCE_FIELD_NAME <<
  44
+                                                   "_1");
  45
+        return Status::OK();
  46
+    }
  47
+
  48
+    void configureSystemUsersIndexes(const StringData& dbname) {
  49
+        std::string systemUsers = dbname.toString() + ".system.users";
  50
+        Client::WriteContext wctx(systemUsers);
  51
+
  52
+        createSystemIndexes(systemUsers);
  53
+
  54
+        NamespaceDetails* nsd = nsdetails(systemUsers.c_str());
  55
+        if (nsd == NULL)
  56
+            return;
  57
+
  58
+        NamespaceDetails::IndexIterator indexIter = nsd->ii();
  59
+        std::vector<std::string> namedIndexesToDrop;
  60
+
  61
+        while (indexIter.more()) {
  62
+            IndexDetails& idetails = indexIter.next();
  63
+            if (idetails.keyPattern() == oldSystemUsersKeyPattern)
  64
+                namedIndexesToDrop.push_back(idetails.indexName());
  65
+        }
  66
+        for (size_t i = 0; i < namedIndexesToDrop.size(); ++i) {
  67
+            std::string errmsg;
  68
+            BSONObjBuilder infoBuilder;
  69
+
  70
+            if (dropIndexes(nsd,
  71
+                            systemUsers.c_str(),
  72
+                            namedIndexesToDrop[i].c_str(),
  73
+                            errmsg,
  74
+                            infoBuilder,
  75
+                            false)) {
  76
+                log() << "Dropped index " << namedIndexesToDrop[i] << " with key pattern " <<
  77
+                    oldSystemUsersKeyPattern << " from " << systemUsers <<
  78
+                    " because it is incompatible with extended form privilege documents." << endl;
  79
+            }
  80
+            else {
  81
+                // Only reason should be orphaned index, which dropIndexes logged.
  82
+            }
  83
+        }
  84
+    }
  85
+}  // namespace
  86
+
  87
+    void configureSystemIndexes(const StringData& dbname) {
  88
+        configureSystemUsersIndexes(dbname);
  89
+    }
  90
+
  91
+    void createSystemIndexes(const NamespaceString& ns) {
  92
+        if (ns.coll == "system.users") {
  93
+            Helpers::ensureIndex(ns.ns().c_str(),
  94
+                                 extendedSystemUsersKeyPattern,
  95
+                                 true,  // unique
  96
+                                 extendedSystemUsersIndexName.c_str());
  97
+        }
  98
+    }
  99
+
  100
+}  // namespace authindex
  101
+}  // namespace mongo
42  src/mongo/db/auth/auth_index_d.h
... ...
@@ -0,0 +1,42 @@
  1
+/**
  2
+*    Copyright (C) 2012 10gen Inc.
  3
+*
  4
+*    This program is free software: you can redistribute it and/or  modify
  5
+*    it under the terms of the GNU Affero General Public License, version 3,
  6
+*    as published by the Free Software Foundation.
  7
+*
  8
+*    This program is distributed in the hope that it will be useful,
  9
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of
  10
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11
+*    GNU Affero General Public License for more details.
  12
+*
  13
+*    You should have received a copy of the GNU Affero General Public License
  14
+*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15
+*/
  16
+
  17
+#pragma once
  18
+
  19
+#include "mongo/base/string_data.h"
  20
+#include "mongo/db/namespacestring.h"
  21
+
  22
+namespace mongo {
  23
+namespace authindex {
  24
+
  25
+    /**
  26
+     * Ensures that exactly the appropriate indexes are present on system collections supporting
  27
+     * authentication and authorization in database "dbname".
  28
+     *
  29
+     * It is appropriate to call this function on new or existing databases, though it is primarily
  30
+     * intended for use on existing databases.  Under no circumstances may it be called on databases
  31
+     * with running operations.
  32
+     */
  33
+    void configureSystemIndexes(const StringData& dbname);
  34
+
  35
+    /**
  36
+     * Creates the appropriate indexes on _new_ system collections supporting authentication and
  37
+     * authorization.
  38
+     */
  39
+    void createSystemIndexes(const NamespaceString& ns);
  40
+
  41
+}  // namespace authindex
  42
+}  // namespace mongo
19  src/mongo/db/database.cpp
@@ -16,16 +16,19 @@
16 16
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 17
 */
18 18
 
19  
-#include "pch.h"
20  
-#include "pdfile.h"
21  
-#include "database.h"
22  
-#include "instance.h"
23  
-#include "introspect.h"
24  
-#include "clientcursor.h"
25  
-#include "databaseholder.h"
  19
+#include "mongo/pch.h"
  20
+
  21
+#include "mongo/db/database.h"
26 22
 
27 23
 #include <boost/filesystem/operations.hpp>
28 24
 
  25
+#include "mongo/db/auth/auth_index_d.h"
  26
+#include "mongo/db/clientcursor.h"
  27
+#include "mongo/db/databaseholder.h"
  28
+#include "mongo/db/instance.h"
  29
+#include "mongo/db/introspect.h"
  30
+#include "mongo/db/pdfile.h"
  31
+
29 32
 namespace mongo {
30 33
 
31 34
     bool Database::_openAllFiles = true;
@@ -445,6 +448,8 @@ namespace mongo {
445 448
             _size++;
446 449
         }
447 450
 
  451
+        authindex::configureSystemIndexes(dbname);
  452
+
448 453
         return db;
449 454
     }
450 455
 
2  src/mongo/db/namespacestring.h
@@ -20,6 +20,8 @@
20 20
 
21 21
 #include <string>
22 22
 
  23
+#include "mongo/util/assert_util.h"
  24
+
23 25
 namespace mongo {
24 26
 
25 27
     using std::string;
13  src/mongo/db/pdfile.cpp
@@ -32,6 +32,7 @@ _ disallow system* manipulations from the database.
32 32
 #include <list>
33 33
 
34 34
 #include "mongo/base/counter.h"
  35
+#include "mongo/db/auth/auth_index_d.h"
35 36
 #include "mongo/db/auth/authorization_manager.h"
36 37
 #include "mongo/db/pdfile_private.h"
37 38
 #include "mongo/db/background.h"
@@ -164,6 +165,13 @@ namespace mongo {
164 165
         }
165 166
     }
166 167
 
  168
+    static void _ensureSystemIndexes(const char* ns) {
  169
+        NamespaceString nsstring(ns);
  170
+        if (StringData(nsstring.coll).substr(0, 7) == "system.") {
  171
+            authindex::createSystemIndexes(nsstring);
  172
+        }
  173
+    }
  174
+
167 175
     string getDbContext() {
168 176
         stringstream ss;
169 177
         Client * c = currentClient.get();
@@ -329,7 +337,9 @@ namespace mongo {
329 337
             else
330 338
                 ensureIdIndexForNewNs( ns );
331 339
         }
332  
-        
  340
+
  341
+        _ensureSystemIndexes(ns);
  342
+
333 343
         if ( mx > 0 )
334 344
             d->setMaxCappedDocs( mx );
335 345
 
@@ -1402,6 +1412,7 @@ namespace mongo {
1402 1412
         NamespaceDetails *d = nsdetails(ns);
1403 1413
         if ( !god )
1404 1414
             ensureIdIndexForNewNs(ns);
  1415
+        _ensureSystemIndexes(ns);
1405 1416
         addNewNamespaceToCatalog(ns);
1406 1417
         return d;
1407 1418
     }

0 notes on commit cb015fb

Please sign in to comment.
Something went wrong with that request. Please try again.