diff options
author | Tess Avitabile <tess.avitabile@mongodb.com> | 2016-08-10 14:18:33 -0400 |
---|---|---|
committer | Tess Avitabile <tess.avitabile@mongodb.com> | 2016-08-19 17:57:33 -0400 |
commit | dc4a0797603401f7560199f15bd9dd29bd17b3a0 (patch) | |
tree | 6442bee3f873585b3bc46bbcdeee6806d34ddb7c | |
parent | 9efbffec2c4be8c597a6d39ba3474f5d4a110454 (diff) | |
download | mongo-dc4a0797603401f7560199f15bd9dd29bd17b3a0.tar.gz |
SERVER-25155 Create setFeatureCompatibilityVersion command
35 files changed, 1231 insertions, 50 deletions
diff --git a/buildscripts/resmokeconfig/suites/auth.yml b/buildscripts/resmokeconfig/suites/auth.yml index c485a3ae198..2dec0319aa8 100644 --- a/buildscripts/resmokeconfig/suites/auth.yml +++ b/buildscripts/resmokeconfig/suites/auth.yml @@ -2,6 +2,9 @@ selector: js_test: roots: - jstests/auth/*.js + exclude_files: + # Skip until SERVER-25618 is resolved. + - jstests/auth/repl.js # Auth tests start their own mongod's. executor: diff --git a/buildscripts/resmokeconfig/suites/auth_audit.yml b/buildscripts/resmokeconfig/suites/auth_audit.yml index 58c7ba5e4af..2cc841760ac 100644 --- a/buildscripts/resmokeconfig/suites/auth_audit.yml +++ b/buildscripts/resmokeconfig/suites/auth_audit.yml @@ -2,6 +2,9 @@ selector: js_test: roots: - jstests/auth/*.js + exclude_files: + # Skip until SERVER-25618 is resolved. + - jstests/auth/repl.js # Auth tests start their own mongod's. executor: diff --git a/buildscripts/resmokeconfig/suites/replication.yml b/buildscripts/resmokeconfig/suites/replication.yml index d89a4532e5e..c4cc646fb34 100644 --- a/buildscripts/resmokeconfig/suites/replication.yml +++ b/buildscripts/resmokeconfig/suites/replication.yml @@ -2,6 +2,9 @@ selector: js_test: roots: - jstests/repl/*.js + exclude_files: + # Skip until SERVER-25618 is resolved. + - jstests/repl/block1.js executor: js_test: diff --git a/buildscripts/resmokeconfig/suites/replication_auth.yml b/buildscripts/resmokeconfig/suites/replication_auth.yml index 18a6a970239..5185ef347db 100644 --- a/buildscripts/resmokeconfig/suites/replication_auth.yml +++ b/buildscripts/resmokeconfig/suites/replication_auth.yml @@ -10,6 +10,8 @@ selector: exclude_files: # Skip any tests that run with auth explicitly. - jstests/repl/*[aA]uth*.js + # Skip until SERVER-25618 is resolved. + - jstests/repl/block1.js executor: js_test: diff --git a/buildscripts/resmokeconfig/suites/tool.yml b/buildscripts/resmokeconfig/suites/tool.yml index 6b756b62083..f8b341278db 100644 --- a/buildscripts/resmokeconfig/suites/tool.yml +++ b/buildscripts/resmokeconfig/suites/tool.yml @@ -2,6 +2,11 @@ selector: js_test: roots: - jstests/tool/*.js + exclude_files: + # Skip until TOOLS-1371 is resolved. + - jstests/tool/dumprestore_auth.js + - jstests/tool/dumprestore_auth2.js + - jstests/tool/dumprestore_auth3.js # Tool tests start their own mongod's. executor: diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js index 35c43c3889b..7a6b52c125d 100644 --- a/jstests/auth/lib/commands_lib.js +++ b/jstests/auth/lib/commands_lib.js @@ -2733,6 +2733,23 @@ var authCommandsLib = { ] }, { + testname: "setFeatureCompatibilityVersion", + command: {setFeatureCompatibilityVersion: "x"}, + testcases: [ + { + runOnDb: adminDbName, + roles: Object.extend({readWriteAnyDatabase: 1}, roles_clusterManager), + privileges: [{ + resource: {db: '$setFeatureCompatibilityVersion', collection: 'version'}, + actions: ['update'] + }], + expectFail: true + }, + {runOnDb: firstDbName, roles: {}}, + {runOnDb: secondDbName, roles: {}} + ] + }, + { testname: "setParameter", command: {setParameter: 1, quiet: 1}, testcases: [ diff --git a/jstests/disk/newcollection.js b/jstests/disk/newcollection.js index aab959b44df..c1b3803f35c 100644 --- a/jstests/disk/newcollection.js +++ b/jstests/disk/newcollection.js @@ -5,20 +5,20 @@ var m = MongoRunner.runMongod({noprealloc: "", smallfiles: ""}); db = m.getDB("test"); var t = db[baseName]; -var getTotalNonLocalSize = function() { - var totalNonLocalDBSize = 0; +var getTotalNonLocalNonAdminSize = function() { + var totalNonLocalNonAdminDBSize = 0; m.getDBs().databases.forEach(function(dbStats) { - // We accept the local database's space overhead. - if (dbStats.name == "local") + // We accept the local database's and admin database's space overhead. + if (dbStats.name == "local" || dbStats.name == "admin") return; // Databases with "sizeOnDisk=1" and "empty=true" dont' actually take up space o disk. // See SERVER-11051. if (dbStats.sizeOnDisk == 1 && dbStats.empty) return; - totalNonLocalDBSize += dbStats.sizeOnDisk; + totalNonLocalNonAdminDBSize += dbStats.sizeOnDisk; }); - return totalNonLocalDBSize; + return totalNonLocalNonAdminDBSize; }; for (var pass = 0; pass <= 1; pass++) { @@ -26,9 +26,9 @@ for (var pass = 0; pass <= 1; pass++) { if (pass == 0) t.drop(); - size = getTotalNonLocalSize(); + size = getTotalNonLocalNonAdminSize(); t.save({}); - assert.eq(size, getTotalNonLocalSize()); + assert.eq(size, getTotalNonLocalNonAdminSize()); assert(size <= 32 * 1024 * 1024); t.drop(); diff --git a/jstests/disk/preallocate.js b/jstests/disk/preallocate.js index 2a01dd89820..caf50b85fc9 100644 --- a/jstests/disk/preallocate.js +++ b/jstests/disk/preallocate.js @@ -4,23 +4,23 @@ var baseName = "jstests_preallocate"; var m = MongoRunner.runMongod({}); -var getTotalNonLocalSize = function() { - var totalNonLocalDBSize = 0; +var getTotalNonLocalNonAdminSize = function() { + var totalNonLocalNonAdminDBSize = 0; m.getDBs().databases.forEach(function(dbStats) { - // We accept the local database's space overhead. - if (dbStats.name == "local") + // We accept the local database's and admin database's space overhead. + if (dbStats.name == "local" || dbStats.name == "admin") return; // Databases with "sizeOnDisk=1" and "empty=true" dont' actually take up space o disk. // See SERVER-11051. if (dbStats.sizeOnDisk == 1 && dbStats.empty) return; - totalNonLocalDBSize += dbStats.sizeOnDisk; + totalNonLocalNonAdminDBSize += dbStats.sizeOnDisk; }); - return totalNonLocalDBSize; + return totalNonLocalNonAdminDBSize; }; -assert.eq(0, getTotalNonLocalSize()); +assert.eq(0, getTotalNonLocalNonAdminSize()); m.getDB(baseName).createCollection(baseName + "1"); @@ -30,17 +30,17 @@ if (m.getDB(baseName).serverBits() < 64) expectedMB /= 4; assert.soon(function() { - return getTotalNonLocalSize() >= expectedMB * 1024 * 1024; + return getTotalNonLocalNonAdminSize() >= expectedMB * 1024 * 1024; }, "\n\n\nFAIL preallocate.js expected second file to bring total size over " + expectedMB + "MB"); MongoRunner.stopMongod(m); m = MongoRunner.runMongod({restart: true, cleanData: false, dbpath: m.dbpath}); -size = getTotalNonLocalSize(); +size = getTotalNonLocalNonAdminSize(); m.getDB(baseName).createCollection(baseName + "2"); sleep(2000); // give prealloc a chance -assert.eq(size, getTotalNonLocalSize()); +assert.eq(size, getTotalNonLocalNonAdminSize()); diff --git a/jstests/multiVersion/mixed_storage_version_replication.js b/jstests/multiVersion/mixed_storage_version_replication.js index 0973fbd85ff..5cbdc2985c1 100644 --- a/jstests/multiVersion/mixed_storage_version_replication.js +++ b/jstests/multiVersion/mixed_storage_version_replication.js @@ -537,6 +537,10 @@ function assertDBsEq(db1, db2) { } else if (hash1.md5 != hash2.md5) { for (var i = 0; i < Math.min(collNames1.length, collNames2.length); i++) { var collName = collNames1[i]; + if (collName.startsWith('system.')) { + // Skip system collections. These are not included in the dbhash before 3.3.10. + continue; + } if (hash1.collections[collName] !== hash2.collections[collName]) { if (db1[collName].stats().capped) { if (!db2[collName].stats().capped) { diff --git a/jstests/multiVersion/set_feature_compatibility_version.js b/jstests/multiVersion/set_feature_compatibility_version.js new file mode 100644 index 00000000000..01a060b746d --- /dev/null +++ b/jstests/multiVersion/set_feature_compatibility_version.js @@ -0,0 +1,294 @@ +// Tests for setFeatureCompatibilityVersion. +(function() { + "use strict"; + + var res; + const latest = "latest"; + const downgrade = "3.2"; + + // + // Standalone tests. + // + + var dbpath = MongoRunner.dataPath + "feature_compatibility_version"; + resetDbpath(dbpath); + var conn; + var adminDB; + + // New 3.4 standalone. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: latest}); + assert.neq(null, conn); + adminDB = conn.getDB("admin"); + + // Initially featureCompatibilityVersion is 3.4. + res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.4"); + assert.eq(adminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, "3.4"); + + // featureCompatibilityVersion cannot be set to invalid value. + assert.commandFailed(adminDB.runCommand({setFeatureCompatibilityVersion: 5})); + assert.commandFailed(adminDB.runCommand({setFeatureCompatibilityVersion: "3.0"})); + assert.commandFailed(adminDB.runCommand({setFeatureCompatibilityVersion: "3.6"})); + + // setFeatureCompatibilityVersion rejects unknown fields. + assert.commandFailed(adminDB.runCommand({setFeatureCompatibilityVersion: "3.2", unknown: 1})); + + // setFeatureCompatibilityVersion can only be run on the admin database. + assert.commandFailed(conn.getDB("test").runCommand({setFeatureCompatibilityVersion: "3.2"})); + + // featureCompatibilityVersion cannot be set via setParameter. + assert.commandFailed(adminDB.runCommand({setParameter: 1, featureCompatibilityVersion: "3.2"})); + + // featureCompatibilityVersion can be set to 3.2. + assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "3.2"})); + res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.2"); + assert.eq(adminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, "3.2"); + + // featureCompatibilityVersion can be set to 3.4. + assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "3.4"})); + res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.4"); + assert.eq(adminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, "3.4"); + + MongoRunner.stopMongod(conn); + + // featureCompatibilityVersion is durable. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: latest}); + assert.neq(null, conn); + adminDB = conn.getDB("admin"); + assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "3.2"})); + MongoRunner.stopMongod(conn); + + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: latest, noCleanData: true}); + assert.neq(null, conn); + adminDB = conn.getDB("admin"); + res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.2"); + assert.eq(adminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, "3.2"); + MongoRunner.stopMongod(conn); + + // If you upgrade from 3.2 to 3.4 and have non-local databases, featureCompatibilityVersion is + // 3.2. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: downgrade}); + assert.neq(null, conn); + assert.writeOK(conn.getDB("test").coll.insert({a: 5})); + MongoRunner.stopMongod(conn); + + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: latest, noCleanData: true}); + assert.neq(null, conn); + adminDB = conn.getDB("admin"); + res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.2"); + MongoRunner.stopMongod(conn); + + // + // Replica set tests. + // + + var rst; + var rstConns; + var replSetConfig; + var primaryAdminDB; + var secondaryAdminDB; + + // New 3.4 replica set. + rst = new ReplSetTest({nodes: 2, nodeOpts: {binVersion: latest}}); + rst.startSet(); + rst.initiate(); + primaryAdminDB = rst.getPrimary().getDB("admin"); + secondaryAdminDB = rst.getSecondary().getDB("admin"); + + // Initially featureCompatibilityVersion is 3.4 on primary and secondary. + var ex; + assert.soon( + function() { + try { + res = primaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.4"); + assert.eq( + primaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}) + .version, + "3.4"); + return true; + } catch (e) { + ex = e; + return false; + } + }, + function() { + return ex.toString(); + }); + rst.awaitReplication(); + res = secondaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.4"); + assert.eq(secondaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, + "3.4"); + + // featureCompatibilityVersion propagates to secondary. + assert.commandWorked(primaryAdminDB.runCommand({setFeatureCompatibilityVersion: "3.2"})); + res = primaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.2"); + assert.eq(primaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, + "3.2"); + rst.awaitReplication(); + res = secondaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.2"); + assert.eq(secondaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, + "3.2"); + + // setFeatureCompatibilityVersion cannot be run on secondary. + assert.commandFailed(secondaryAdminDB.runCommand({setFeatureCompatibilityVersion: "3.4"})); + + rst.stopSet(undefined, false, {allowedExitCodes: [MongoRunner.EXIT_ABRUPT]}); + + // A 3.4 secondary with a 3.2 primary will have featureCompatibilityVersion=3.2. + rst = new ReplSetTest({nodes: [{binVersion: downgrade}, {binVersion: latest}]}); + rstConns = rst.startSet(); + replSetConfig = rst.getReplSetConfig(); + replSetConfig.members[0].priority = 2; + replSetConfig.members[1].priority = 1; + rst.initiate(replSetConfig); + rst.waitForState(rstConns[0], ReplSetTest.State.PRIMARY); + secondaryAdminDB = rst.getSecondary().getDB("admin"); + res = secondaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.2"); + rst.stopSet(undefined, false, {allowedExitCodes: [MongoRunner.EXIT_ABRUPT]}); + + // TODO SERVER-25156: Test that adding a 3.2 secondary to a 3.4 replica set with + // featureCompatibilityVersion=3.4 kills the secondary. + // TODO SERVER-25156: Test that adding a 3.2 secondary to a 3.4 replica set with + // featureCompatibilityVersion=3.2 succeeds. + // TODO SERVER-25156: Test that setting featureCompatibilityVersion=3.4 on a replica set kills a + // 3.2 secondary. + + // + // Sharding tests. + // + + var st; + var mongosAdminDB; + var configPrimaryAdminDB; + var shardPrimaryAdminDB; + + // New 3.4 cluster. + st = new ShardingTest({shards: {rs0: {nodes: [{binVersion: latest}, {binVersion: latest}]}}}); + mongosAdminDB = st.s.getDB("admin"); + configPrimaryAdminDB = st.configRS.getPrimary().getDB("admin"); + shardPrimaryAdminDB = st.rs0.getPrimary().getDB("admin"); + + // Initially featureCompatibilityVersion is 3.4 on config and shard. + res = configPrimaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.4"); + assert.eq( + configPrimaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, + "3.4"); + res = shardPrimaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.4"); + assert.eq( + shardPrimaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, + "3.4"); + + // featureCompatibilityVersion cannot be set to invalid value on mongos. + assert.commandFailed(mongosAdminDB.runCommand({setFeatureCompatibilityVersion: 5})); + assert.commandFailed(mongosAdminDB.runCommand({setFeatureCompatibilityVersion: "3.0"})); + assert.commandFailed(mongosAdminDB.runCommand({setFeatureCompatibilityVersion: "3.6"})); + + // setFeatureCompatibilityVersion rejects unknown fields on mongos. + assert.commandFailed( + mongosAdminDB.runCommand({setFeatureCompatibilityVersion: "3.2", unknown: 1})); + + // setFeatureCompatibilityVersion can only be run on the admin database on mongos. + assert.commandFailed(st.s.getDB("test").runCommand({setFeatureCompatibilityVersion: "3.2"})); + + // featureCompatibilityVersion cannot be set via setParameter on mongos. + assert.commandFailed( + mongosAdminDB.runCommand({setParameter: 1, featureCompatibilityVersion: "3.2"})); + + // featureCompatibilityVersion can be set to 3.2 on mongos. + assert.commandWorked(mongosAdminDB.runCommand({setFeatureCompatibilityVersion: "3.2"})); + + // featureCompatibilityVersion propagates to config and shard. + res = configPrimaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.2"); + assert.eq( + configPrimaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, + "3.2"); + res = shardPrimaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.2"); + assert.eq( + shardPrimaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, + "3.2"); + + // featureCompatibilityVersion can be set to 3.4 on mongos. + assert.commandWorked(mongosAdminDB.runCommand({setFeatureCompatibilityVersion: "3.4"})); + res = configPrimaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.4"); + assert.eq( + configPrimaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, + "3.4"); + res = shardPrimaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.4"); + assert.eq( + shardPrimaryAdminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, + "3.4"); + + // Adding a 3.2 shard to a cluster with featureCompatibilityVersion=3.4 fails. + var downgradeShard = new ReplSetTest({ + nodes: [{binVersion: downgrade}, {binVersion: downgrade}], + nodeOptions: {shardsvr: ""}, + useHostName: true + }); + downgradeShard.startSet(); + downgradeShard.initiate(); + assert.commandFailed(mongosAdminDB.runCommand({addShard: downgradeShard.getURL()})); + downgradeShard.stopSet(undefined, false, {allowedExitCodes: [MongoRunner.EXIT_ABRUPT]}); + + // A 3.4 shard added to a cluster with featureCompatibilityVersion=3.2 gets + // featureCompatibilityVersion=3.2. + assert.commandWorked(mongosAdminDB.runCommand({setFeatureCompatibilityVersion: "3.2"})); + var latestShard = new ReplSetTest({ + name: "latestShard", + nodes: [{binVersion: latest}, {binVersion: latest}], + nodeOptions: {shardsvr: ""}, + useHostName: true + }); + latestShard.startSet(); + latestShard.initiate(); + assert.commandWorked(mongosAdminDB.runCommand({addShard: latestShard.getURL()})); + var latestShardPrimaryAdminDB = latestShard.getPrimary().getDB("admin"); + res = latestShardPrimaryAdminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + assert.eq(res.featureCompatibilityVersion, "3.2"); + + // Adding a 3.2 shard to a cluster with featureCompatibilityVersion=3.2 succeeds. + downgradeShard = new ReplSetTest({ + name: "downgradeShard", + nodes: [{binVersion: downgrade}, {binVersion: downgrade}], + nodeOptions: {shardsvr: ""}, + useHostName: true + }); + downgradeShard.startSet(); + downgradeShard.initiate(); + assert.commandWorked(mongosAdminDB.runCommand({addShard: downgradeShard.getURL()})); + + downgradeShard.stopSet(undefined, false, {allowedExitCodes: [MongoRunner.EXIT_ABRUPT]}); + latestShard.stopSet(undefined, false, {allowedExitCodes: [MongoRunner.EXIT_ABRUPT]}); + st.stop(); +})(); diff --git a/jstests/noPassthroughWithMongod/initial_sync_replSetGetStatus.js b/jstests/noPassthroughWithMongod/initial_sync_replSetGetStatus.js index df5600dc5a6..37d723a26c7 100644 --- a/jstests/noPassthroughWithMongod/initial_sync_replSetGetStatus.js +++ b/jstests/noPassthroughWithMongod/initial_sync_replSetGetStatus.js @@ -83,7 +83,7 @@ assert.eq(res.initialSyncStatus.appliedOps, 2); assert.eq(res.initialSyncStatus.failedInitialSyncAttempts, 0); assert.eq(res.initialSyncStatus.maxFailedInitialSyncAttempts, 10); - assert.eq(res.initialSyncStatus.databases.databasesCloned, 1); + assert.eq(res.initialSyncStatus.databases.databasesCloned, 2); assert.eq(res.initialSyncStatus.databases.test.collections, 1); assert.eq(res.initialSyncStatus.databases.test.clonedCollections, 1); assert.eq(res.initialSyncStatus.databases.test["test.foo"].documents, 4); diff --git a/jstests/sharding/features1.js b/jstests/sharding/features1.js index d545ea096d6..8ad65be8833 100644 --- a/jstests/sharding/features1.js +++ b/jstests/sharding/features1.js @@ -235,8 +235,8 @@ // --- listDatabases --- r = db.getMongo().getDBs(); - assert.eq(2, r.databases.length, tojson(r)); - assert.eq("number", typeof(r.totalSize), "listDatabases 2 : " + tojson(r)); + assert.eq(3, r.databases.length, tojson(r)); + assert.eq("number", typeof(r.totalSize), "listDatabases 3 : " + tojson(r)); s.stop(); diff --git a/jstests/sharding/listDatabases.js b/jstests/sharding/listDatabases.js index 0ed7cf2a286..6f9dc3a444d 100644 --- a/jstests/sharding/listDatabases.js +++ b/jstests/sharding/listDatabases.js @@ -30,10 +30,6 @@ var res = mongos.adminCommand("listDatabases"); var dbArray = res.databases; dbInConfigEntryCheck(getDBSection(dbArray, "config")); -// Should not have admin entry if it doesn't exists. -var adminSection = getDBSection(dbArray, 'admin'); -assert(!adminSection); - // Local database should never be returned var localSection = getDBSection(dbArray, 'local'); assert(!localSection); diff --git a/jstests/tool/dumprestore_auth2.js b/jstests/tool/dumprestore_auth2.js index 275b47ceac6..39dcaa19b6e 100644 --- a/jstests/tool/dumprestore_auth2.js +++ b/jstests/tool/dumprestore_auth2.js @@ -35,8 +35,8 @@ var dumpRestoreAuth2 = function(backup_role, restore_role) { "setup2: " + tojson(admindb.system.users.getIndexes())); assert.eq(1, admindb.system.roles.count(), "setup3"); assert.eq(2, admindb.system.roles.getIndexes().length, "setup4"); - assert.eq(1, admindb.system.version.count()); - var versionDoc = admindb.system.version.findOne(); + assert.eq(1, admindb.system.version.find({_id: "authSchema"}).count()); + var versionDoc = admindb.system.version.findOne({_id: "authSchema"}); // Logout root user. admindb.logout(); @@ -110,9 +110,11 @@ var dumpRestoreAuth2 = function(backup_role, restore_role) { assert.eq(1, admindb.system.roles.find({role: 'customRole'}).count(), "didn't restore roles"); assert.eq(2, admindb.system.users.getIndexes().length, "didn't maintain user indexes"); assert.eq(2, admindb.system.roles.getIndexes().length, "didn't maintain role indexes"); - assert.eq(1, admindb.system.version.count(), "didn't restore version"); - assert.docEq( - versionDoc, admindb.system.version.findOne(), "version doc wasn't restored properly"); + assert.eq( + 1, admindb.system.version.find({_id: "authSchema"}).count(), "didn't restore version"); + assert.docEq(versionDoc, + admindb.system.version.findOne({_id: "authSchema"}), + "version doc wasn't restored properly"); admindb.logout(); t.stop(); diff --git a/jstests/views/views_all_commands.js b/jstests/views/views_all_commands.js index 6d9d806432d..2164e21af9e 100644 --- a/jstests/views/views_all_commands.js +++ b/jstests/views/views_all_commands.js @@ -62,6 +62,7 @@ _configsvrCommitChunkMigration: {skip: isAnInternalCommand}, _configsvrMoveChunk: {skip: isAnInternalCommand}, _configsvrRemoveShardFromZone: {skip: isAnInternalCommand}, + _configsvrSetFeatureCompatibilityVersion: {skip: isAnInternalCommand}, _configsvrUpdateZoneKeyRange: {skip: isAnInternalCommand}, _getUserCacheGeneration: {skip: isAnInternalCommand}, _hashBSONElement: {skip: isAnInternalCommand}, @@ -368,6 +369,7 @@ saslStart: {skip: isUnrelated}, serverStatus: {command: {serverStatus: 1}, skip: isUnrelated}, setCommittedSnapshot: {skip: isAnInternalCommand}, + setFeatureCompatibilityVersion: {skip: isUnrelated}, setParameter: {skip: isUnrelated}, setShardVersion: {skip: isUnrelated}, shardCollection: { diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp index 05f72f76b45..a25dc14eed9 100644 --- a/src/mongo/db/auth/role_graph_builtin_roles.cpp +++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp @@ -465,6 +465,12 @@ void addClusterManagerPrivileges(PrivilegeVector* privileges) { privileges, Privilege(ResourcePattern::forExactNamespace(NamespaceString("config", "shards")), writeActions)); + // Fake collection used for setFeatureCompatibilityVersion permissions. + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forExactNamespace( + NamespaceString("$setFeatureCompatibilityVersion", "version")), + writeActions)); } void addClusterAdminPrivileges(PrivilegeVector* privileges) { diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 6d0e7666745..75c10c03af7 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -103,6 +103,7 @@ env.Library( "drop_indexes.cpp", "eval.cpp", "explain_cmd.cpp", + "feature_compatibility_version.cpp", "find_and_modify.cpp", "find_cmd.cpp", "fsync.cpp", @@ -125,6 +126,7 @@ env.Library( "plan_cache_commands.cpp", "rename_collection_cmd.cpp", "repair_cursor.cpp", + "set_feature_compatibility_version_command.cpp", "snapshot_management.cpp", "test_commands.cpp", "top_command.cpp", diff --git a/src/mongo/db/commands/feature_compatibility_version.cpp b/src/mongo/db/commands/feature_compatibility_version.cpp new file mode 100644 index 00000000000..7e069ceda48 --- /dev/null +++ b/src/mongo/db/commands/feature_compatibility_version.cpp @@ -0,0 +1,251 @@ +/** + * Copyright (C) 2016 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/commands/feature_compatibility_version.h" + +#include "mongo/base/status.h" +#include "mongo/db/dbdirectclient.h" +#include "mongo/db/server_parameters.h" +#include "mongo/db/service_context.h" +#include "mongo/db/storage/storage_engine.h" +#include "mongo/rpc/get_status_from_command_result.h" + +namespace mongo { + +constexpr StringData FeatureCompatibilityVersion::kCollection; +constexpr StringData FeatureCompatibilityVersion::kCommandName; +constexpr StringData FeatureCompatibilityVersion::kParameterName; +constexpr StringData FeatureCompatibilityVersion::kVersionField; +constexpr StringData FeatureCompatibilityVersion::kVersion34; +constexpr StringData FeatureCompatibilityVersion::kVersion32; + +StatusWith<ServerGlobalParams::FeatureCompatibilityVersions> FeatureCompatibilityVersion::parse( + const BSONObj& featureCompatibilityVersionDoc) { + bool foundVersionField = false; + ServerGlobalParams::FeatureCompatibilityVersions version; + + for (auto&& elem : featureCompatibilityVersionDoc) { + auto fieldName = elem.fieldNameStringData(); + if (fieldName == "_id") { + continue; + } else if (fieldName == FeatureCompatibilityVersion::kVersionField) { + foundVersionField = true; + if (elem.type() != BSONType::String) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << FeatureCompatibilityVersion::kVersionField + << " must be of type String, but was of type " + << typeName(elem.type()) + << ". Contents of " + << FeatureCompatibilityVersion::kParameterName + << " document in " + << FeatureCompatibilityVersion::kCollection + << ": " + << featureCompatibilityVersionDoc); + } + if (elem.String() == FeatureCompatibilityVersion::kVersion34) { + version = ServerGlobalParams::FeatureCompatibilityVersion_34; + } else if (elem.String() == FeatureCompatibilityVersion::kVersion32) { + version = ServerGlobalParams::FeatureCompatibilityVersion_32; + } else { + return Status(ErrorCodes::BadValue, + str::stream() << "Invalid value for " + << FeatureCompatibilityVersion::kVersionField + << ", found " + << version + << ", expected '" + << FeatureCompatibilityVersion::kVersion34 + << "' or '" + << FeatureCompatibilityVersion::kVersion32 + << "'. Contents of " + << FeatureCompatibilityVersion::kParameterName + << " document in " + << FeatureCompatibilityVersion::kCollection + << ": " + << featureCompatibilityVersionDoc); + } + } else { + return Status(ErrorCodes::BadValue, + str::stream() << "Unrecognized field '" << elem.fieldName() + << "''. Contents of " + << FeatureCompatibilityVersion::kParameterName + << " document in " + << FeatureCompatibilityVersion::kCollection + << ": " + << featureCompatibilityVersionDoc); + } + } + + if (!foundVersionField) { + return Status(ErrorCodes::BadValue, + str::stream() << "Missing required field '" + << FeatureCompatibilityVersion::kVersionField + << "''. Contents of " + << FeatureCompatibilityVersion::kParameterName + << " document in " + << FeatureCompatibilityVersion::kCollection + << ": " + << featureCompatibilityVersionDoc); + } + + return version; +} + +void FeatureCompatibilityVersion::set(OperationContext* txn, StringData version) { + uassert(40284, + "featureCompatibilityVersion must be '3.4' or '3.2'", + version == FeatureCompatibilityVersion::kVersion34 || + version == FeatureCompatibilityVersion::kVersion32); + + // Update admin.system.version. + NamespaceString nss(FeatureCompatibilityVersion::kCollection); + BSONObjBuilder updateCmd; + updateCmd.append("update", nss.coll()); + updateCmd.append( + "updates", + BSON_ARRAY(BSON("q" << BSON("_id" << FeatureCompatibilityVersion::kParameterName) << "u" + << BSON(FeatureCompatibilityVersion::kVersionField << version) + << "upsert" + << true))); + updateCmd.append("writeConcern", + BSON("w" + << "majority")); + DBDirectClient client(txn); + BSONObj result; + client.runCommand(nss.db().toString(), updateCmd.obj(), result); + uassertStatusOK(getStatusFromCommandResult(result)); + uassertStatusOK(getWriteConcernStatusFromCommandResult(result)); + + // Update server parameter. + if (version == FeatureCompatibilityVersion::kVersion34) { + serverGlobalParams.featureCompatibilityVersion.store( + ServerGlobalParams::FeatureCompatibilityVersion_34); + } else if (version == FeatureCompatibilityVersion::kVersion32) { + serverGlobalParams.featureCompatibilityVersion.store( + ServerGlobalParams::FeatureCompatibilityVersion_32); + } +} + +void FeatureCompatibilityVersion::setIfCleanStartup(OperationContext* txn) { + std::vector<std::string> dbNames; + StorageEngine* storageEngine = getGlobalServiceContext()->getGlobalStorageEngine(); + storageEngine->listDatabases(&dbNames); + + for (auto&& dbName : dbNames) { + if (dbName != "local") { + return; + } + } + + if (serverGlobalParams.clusterRole != ClusterRole::ShardServer) { + + // Insert featureCompatibilityDocument into admin.system.version. + // Do not use writeConcern majority, because we may be holding locks. + NamespaceString nss(FeatureCompatibilityVersion::kCollection); + DBDirectClient client(txn); + BSONObj result; + client.runCommand( + nss.db().toString(), + BSON("insert" << nss.coll() << "documents" + << BSON_ARRAY(BSON("_id" << FeatureCompatibilityVersion::kParameterName + << FeatureCompatibilityVersion::kVersionField + << FeatureCompatibilityVersion::kVersion34))), + result); + auto status = getStatusFromCommandResult(result); + if (!status.isOK() && status != ErrorCodes::InterruptedAtShutdown) { + uassertStatusOK(status); + } + + // Update server parameter. + serverGlobalParams.featureCompatibilityVersion.store( + ServerGlobalParams::FeatureCompatibilityVersion_34); + } +} + +void FeatureCompatibilityVersion::onInsertOrUpdate(const BSONObj& doc) { + auto idElement = doc["_id"]; + if (idElement.type() != BSONType::String || + idElement.String() != FeatureCompatibilityVersion::kParameterName) { + return; + } + serverGlobalParams.featureCompatibilityVersion.store( + uassertStatusOK(FeatureCompatibilityVersion::parse(doc))); +} + +void FeatureCompatibilityVersion::onDelete(const BSONObj& doc) { + auto idElement = doc["_id"]; + if (idElement.type() != BSONType::String || + idElement.String() != FeatureCompatibilityVersion::kParameterName) { + return; + } + serverGlobalParams.featureCompatibilityVersion.store( + ServerGlobalParams::FeatureCompatibilityVersion_32); +} + +/** + * Read-only server parameter for featureCompatibilityVersion. + */ +class FeatureCompatibilityVersionParameter : public ServerParameter { +public: + FeatureCompatibilityVersionParameter() + : ServerParameter(ServerParameterSet::getGlobal(), + FeatureCompatibilityVersion::kParameterName.toString(), + false, // allowedToChangeAtStartup + false // allowedToChangeAtRuntime + ) {} + + StringData featureCompatibilityVersionStr() { + switch (serverGlobalParams.featureCompatibilityVersion.load()) { + case ServerGlobalParams::FeatureCompatibilityVersion_34: + return FeatureCompatibilityVersion::kVersion34; + case ServerGlobalParams::FeatureCompatibilityVersion_32: + return FeatureCompatibilityVersion::kVersion32; + default: + MONGO_UNREACHABLE; + } + } + + virtual void append(OperationContext* txn, BSONObjBuilder& b, const std::string& name) { + b.append(name, featureCompatibilityVersionStr()); + } + + virtual Status set(const BSONElement& newValueElement) { + return Status(ErrorCodes::IllegalOperation, + str::stream() << FeatureCompatibilityVersion::kParameterName + << " cannot be set via setParameter"); + } + + virtual Status setFromString(const std::string& str) { + return Status(ErrorCodes::IllegalOperation, + str::stream() << FeatureCompatibilityVersion::kParameterName + << " cannot be set via setParameter"); + } +} featureCompatibilityVersionParameter; + +} // namespace mongo diff --git a/src/mongo/db/commands/feature_compatibility_version.h b/src/mongo/db/commands/feature_compatibility_version.h new file mode 100644 index 00000000000..41764ec7026 --- /dev/null +++ b/src/mongo/db/commands/feature_compatibility_version.h @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2016 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/base/status_with.h" +#include "mongo/base/string_data.h" +#include "mongo/db/server_options.h" + +namespace mongo { + +class BSONObj; +class OperationContext; + +class FeatureCompatibilityVersion { +public: + static constexpr StringData kCollection = "admin.system.version"_sd; + static constexpr StringData kCommandName = "setFeatureCompatibilityVersion"_sd; + static constexpr StringData kParameterName = "featureCompatibilityVersion"_sd; + static constexpr StringData kVersionField = "version"_sd; + static constexpr StringData kVersion34 = "3.4"_sd; + static constexpr StringData kVersion32 = "3.2"_sd; + + /** + * Parses the featureCompatibilityVersion document from admin.system.version, and returns the + * version. + */ + static StatusWith<ServerGlobalParams::FeatureCompatibilityVersions> parse( + const BSONObj& featureCompatibilityVersionDoc); + + /** + * Sets the minimum allowed version in the cluster, which determines what features are + * available. + * 'version' should be '3.4' or '3.2'. + */ + static void set(OperationContext* txn, StringData version); + + /** + * If there are no non-local databases and we are not running with --shardsvr, set + * featureCompatibilityVersion to 3.4. + */ + static void setIfCleanStartup(OperationContext* txn); + + /** + * Examines a document inserted or updated in admin.system.version. If it is the + * featureCompatibilityVersion document, validates the document and updates the server + * parameter. + */ + static void onInsertOrUpdate(const BSONObj& doc); + + /** + * Examines the _id of a document removed from admin.system.version. If it is the + * featureCompatibilityVersion document, resets the server parameter to its default value (3.2). + */ + static void onDelete(const BSONObj& doc); +}; + +} // namespace mongo diff --git a/src/mongo/db/commands/set_feature_compatibility_version_command.cpp b/src/mongo/db/commands/set_feature_compatibility_version_command.cpp new file mode 100644 index 00000000000..1bcac7bff10 --- /dev/null +++ b/src/mongo/db/commands/set_feature_compatibility_version_command.cpp @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2016 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/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/feature_compatibility_version.h" + +namespace mongo { + +namespace { + +/** + * Sets the minimum allowed version for the cluster. If it is 3.2, then the node should not use 3.4 + * features. + * + * Format: + * { + * setFeatureCompatibilityVersion: <string version> + * } + */ +class SetFeatureCompatibilityVersionCommand : public Command { +public: + SetFeatureCompatibilityVersionCommand() : Command(FeatureCompatibilityVersion::kCommandName) {} + + virtual bool slaveOk() const { + return false; + } + + virtual bool adminOnly() const { + return true; + } + + virtual bool supportsWriteConcern(const BSONObj& cmd) const override { + return false; + } + + virtual void help(std::stringstream& help) const { + help << "set the minimum allowed version in the cluster, which determines what features " + "are available"; + } + + Status checkAuthForCommand(Client* client, + const std::string& dbname, + const BSONObj& cmdObj) override { + if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource( + ResourcePattern::forExactNamespace( + NamespaceString("$setFeatureCompatibilityVersion.version")), + ActionType::update)) { + return Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + return Status::OK(); + } + + bool run(OperationContext* txn, + const std::string& dbname, + BSONObj& cmdObj, + int options, + std::string& errmsg, + BSONObjBuilder& result) { + + // Validate command. + std::string version; + for (auto&& elem : cmdObj) { + if (elem.fieldNameStringData() == FeatureCompatibilityVersion::kCommandName) { + uassert(ErrorCodes::TypeMismatch, + str::stream() << FeatureCompatibilityVersion::kCommandName + << " must be of type String, but was of type " + << typeName(elem.type()), + elem.type() == BSONType::String); + version = elem.String(); + } else { + uasserted(ErrorCodes::FailedToParse, + str::stream() << "unrecognized field '" << elem.fieldName() << "'"); + } + } + + uassert(ErrorCodes::BadValue, + str::stream() << "invalid value for " << FeatureCompatibilityVersion::kCommandName + << ", found " + << version + << ", expected '" + << FeatureCompatibilityVersion::kVersion34 + << "' or '" + << FeatureCompatibilityVersion::kVersion32 + << "'", + version == FeatureCompatibilityVersion::kVersion34 || + version == FeatureCompatibilityVersion::kVersion32); + + // Set featureCompatibilityVersion. + FeatureCompatibilityVersion::set(txn, version); + + return true; + } +} setFeatureCompatibilityVersionCommand; + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp index 57d0c52a840..5e2f946c96f 100644 --- a/src/mongo/db/db.cpp +++ b/src/mongo/db/db.cpp @@ -57,6 +57,7 @@ #include "mongo/db/catalog/index_key_validate.h" #include "mongo/db/client.h" #include "mongo/db/clientcursor.h" +#include "mongo/db/commands/feature_compatibility_version.h" #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/concurrency/lock_state.h" #include "mongo/db/concurrency/write_conflict_exception.h" @@ -399,6 +400,26 @@ static void repairDatabasesAndCheckVersion(OperationContext* txn) { return; } + // Check if admin.system.version contains an invalid featureCompatibilityVersion. + // If a valid featureCompatibilityVersion is present, cache it as a server parameter. + if (dbName == "admin") { + if (Collection* versionColl = + db->getCollection(FeatureCompatibilityVersion::kCollection)) { + BSONObj featureCompatibilityVersion; + if (Helpers::findOne(txn, + versionColl, + BSON("_id" << FeatureCompatibilityVersion::kParameterName), + featureCompatibilityVersion)) { + auto version = FeatureCompatibilityVersion::parse(featureCompatibilityVersion); + if (!version.isOK()) { + severe() << version.getStatus(); + fassertFailedNoTrace(40283); + } + serverGlobalParams.featureCompatibilityVersion.store(version.getValue()); + } + } + } + // Major versions match, check indexes const string systemIndexes = db->name() + ".system.indexes"; @@ -730,6 +751,13 @@ static ExitCode _initAndListen(int listenPort) { HostnameCanonicalizationWorker::start(getGlobalServiceContext()); + if (!replSettings.usingReplSets() && !replSettings.isSlave() && + storageGlobalParams.engine != "devnull") { + ScopedTransaction transaction(startupOpCtx.get(), MODE_X); + Lock::GlobalWrite lk(startupOpCtx.get()->lockState()); + FeatureCompatibilityVersion::setIfCleanStartup(startupOpCtx.get()); + } + uassertStatusOK(ShardingState::get(startupOpCtx.get()) ->initializeShardingAwarenessIfNeeded(startupOpCtx.get())); diff --git a/src/mongo/db/op_observer.cpp b/src/mongo/db/op_observer.cpp index 83f8b463945..9b0e1e1b138 100644 --- a/src/mongo/db/op_observer.cpp +++ b/src/mongo/db/op_observer.cpp @@ -33,10 +33,12 @@ #include "mongo/db/auth/authorization_manager_global.h" #include "mongo/db/catalog/collection_options.h" #include "mongo/db/commands/dbhash.h" +#include "mongo/db/commands/feature_compatibility_version.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/s/collection_sharding_state.h" +#include "mongo/db/server_options.h" #include "mongo/db/views/durable_view_catalog.h" #include "mongo/scripting/engine.h" @@ -77,6 +79,12 @@ void OpObserver::onInserts(OperationContext* txn, } } + if (nss.ns() == FeatureCompatibilityVersion::kCollection) { + for (auto it = begin; it != end; it++) { + FeatureCompatibilityVersion::onInsertOrUpdate(*it); + } + } + logOpForDbHash(txn, ns); if (strstr(ns, ".system.js")) { Scope::storedFuncMod(txn); @@ -110,6 +118,10 @@ void OpObserver::onUpdate(OperationContext* txn, const OplogUpdateEntryArgs& arg if (nss.coll() == DurableViewCatalog::viewsCollectionName()) { DurableViewCatalog::onExternalChange(txn, nss); } + + if (args.ns == FeatureCompatibilityVersion::kCollection) { + FeatureCompatibilityVersion::onInsertOrUpdate(args.updatedDoc); + } } CollectionShardingState::DeleteState OpObserver::aboutToDelete(OperationContext* txn, @@ -150,6 +162,9 @@ void OpObserver::onDelete(OperationContext* txn, if (ns.coll() == DurableViewCatalog::viewsCollectionName()) { DurableViewCatalog::onExternalChange(txn, ns); } + if (ns.ns() == FeatureCompatibilityVersion::kCollection) { + FeatureCompatibilityVersion::onDelete(deleteState.idDoc); + } } void OpObserver::onOpMessage(OperationContext* txn, const BSONObj& msgObj) { diff --git a/src/mongo/db/repl/replication_coordinator_external_state.h b/src/mongo/db/repl/replication_coordinator_external_state.h index a4bdb928ea7..857bac4abd7 100644 --- a/src/mongo/db/repl/replication_coordinator_external_state.h +++ b/src/mongo/db/repl/replication_coordinator_external_state.h @@ -225,12 +225,11 @@ public: virtual void shardingOnStepDownHook() = 0; /** - * Called when the instance transitions to primary in order to notify a potentially sharded host - * to perform respective state changes, such as starting the balancer, etc. + * Called when the instance transitions to primary. Calls all drain mode hooks. * * Throws on errors. */ - virtual void shardingOnDrainingStateHook(OperationContext* txn) = 0; + virtual void drainModeHook(OperationContext* txn) = 0; /** * Notifies the bgsync and syncSourceFeedback threads to choose a new sync source. @@ -243,14 +242,6 @@ public: virtual void signalApplierToCancelFetcher() = 0; /** - * Drops all temporary collections on all databases except "local". - * - * The implementation may assume that the caller has acquired the global exclusive lock - * for "txn". - */ - virtual void dropAllTempCollections(OperationContext* txn) = 0; - - /** * Drops all snapshots and clears the "committed" snapshot. */ virtual void dropAllSnapshots() = 0; diff --git a/src/mongo/db/repl/replication_coordinator_external_state_impl.cpp b/src/mongo/db/repl/replication_coordinator_external_state_impl.cpp index 5d91ee3a853..c5d36369d07 100644 --- a/src/mongo/db/repl/replication_coordinator_external_state_impl.cpp +++ b/src/mongo/db/repl/replication_coordinator_external_state_impl.cpp @@ -40,6 +40,7 @@ #include "mongo/db/catalog/database.h" #include "mongo/db/catalog/database_holder.h" #include "mongo/db/client.h" +#include "mongo/db/commands/feature_compatibility_version.h" #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/dbhelpers.h" @@ -523,6 +524,12 @@ void ReplicationCoordinatorExternalStateImpl::shardingOnStepDownHook() { ShardingState::get(getGlobalServiceContext())->clearCollectionMetadata(); } +void ReplicationCoordinatorExternalStateImpl::drainModeHook(OperationContext* txn) { + FeatureCompatibilityVersion::setIfCleanStartup(txn); + shardingOnDrainingStateHook(txn); + dropAllTempCollections(txn); +} + void ReplicationCoordinatorExternalStateImpl::shardingOnDrainingStateHook(OperationContext* txn) { auto status = ShardingStateRecovery::recover(txn); diff --git a/src/mongo/db/repl/replication_coordinator_external_state_impl.h b/src/mongo/db/repl/replication_coordinator_external_state_impl.h index a1dbe52fd53..7e339031d17 100644 --- a/src/mongo/db/repl/replication_coordinator_external_state_impl.h +++ b/src/mongo/db/repl/replication_coordinator_external_state_impl.h @@ -89,10 +89,9 @@ public: virtual void closeConnections(); virtual void killAllUserOperations(OperationContext* txn); virtual void shardingOnStepDownHook(); - virtual void shardingOnDrainingStateHook(OperationContext* txn); + virtual void drainModeHook(OperationContext* txn); virtual void signalApplierToChooseNewSyncSource(); virtual void signalApplierToCancelFetcher(); - virtual void dropAllTempCollections(OperationContext* txn); void dropAllSnapshots() final; void updateCommittedSnapshot(SnapshotName newCommitPoint) final; void forceSnapshotCreation() final; @@ -124,6 +123,22 @@ private: */ void _stopDataReplication_inlock(OperationContext* txn, UniqueLock* lock); + /** + * Called when the instance transitions to primary in order to notify a potentially sharded host + * to perform respective state changes, such as starting the balancer, etc. + * + * Throws on errors. + */ + void shardingOnDrainingStateHook(OperationContext* txn); + + /** + * Drops all temporary collections on all databases except "local". + * + * The implementation may assume that the caller has acquired the global exclusive lock + * for "txn". + */ + void dropAllTempCollections(OperationContext* txn); + // Guards starting threads and setting _startedThreads stdx::mutex _threadMutex; diff --git a/src/mongo/db/repl/replication_coordinator_external_state_mock.cpp b/src/mongo/db/repl/replication_coordinator_external_state_mock.cpp index a269ac2971f..4b104c697fa 100644 --- a/src/mongo/db/repl/replication_coordinator_external_state_mock.cpp +++ b/src/mongo/db/repl/replication_coordinator_external_state_mock.cpp @@ -226,7 +226,7 @@ void ReplicationCoordinatorExternalStateMock::killAllUserOperations(OperationCon void ReplicationCoordinatorExternalStateMock::shardingOnStepDownHook() {} -void ReplicationCoordinatorExternalStateMock::shardingOnDrainingStateHook(OperationContext* txn) {} +void ReplicationCoordinatorExternalStateMock::drainModeHook(OperationContext* txn) {} void ReplicationCoordinatorExternalStateMock::signalApplierToChooseNewSyncSource() {} @@ -234,8 +234,6 @@ void ReplicationCoordinatorExternalStateMock::signalApplierToCancelFetcher() { _isApplierSignaledToCancelFetcher = true; } -void ReplicationCoordinatorExternalStateMock::dropAllTempCollections(OperationContext* txn) {} - void ReplicationCoordinatorExternalStateMock::dropAllSnapshots() {} void ReplicationCoordinatorExternalStateMock::updateCommittedSnapshot(SnapshotName newCommitPoint) { diff --git a/src/mongo/db/repl/replication_coordinator_external_state_mock.h b/src/mongo/db/repl/replication_coordinator_external_state_mock.h index 26c5d9a0ba4..5df6cd7733f 100644 --- a/src/mongo/db/repl/replication_coordinator_external_state_mock.h +++ b/src/mongo/db/repl/replication_coordinator_external_state_mock.h @@ -84,10 +84,9 @@ public: virtual void closeConnections(); virtual void killAllUserOperations(OperationContext* txn); virtual void shardingOnStepDownHook(); - virtual void shardingOnDrainingStateHook(OperationContext* txn); + virtual void drainModeHook(OperationContext* txn); virtual void signalApplierToChooseNewSyncSource(); virtual void signalApplierToCancelFetcher(); - virtual void dropAllTempCollections(OperationContext* txn); virtual void dropAllSnapshots(); virtual void updateCommittedSnapshot(SnapshotName newCommitPoint); virtual void forceSnapshotCreation(); diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp index 3b73a686520..f72d8f18d91 100644 --- a/src/mongo/db/repl/replication_coordinator_impl.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl.cpp @@ -886,8 +886,7 @@ void ReplicationCoordinatorImpl::signalDrainComplete(OperationContext* txn) { _drainFinishedCond.notify_all(); lk.unlock(); - _externalState->shardingOnDrainingStateHook(txn); - _externalState->dropAllTempCollections(txn); + _externalState->drainModeHook(txn); // This is done for compatibility with PV0 replicas wrt how "n" ops are processed. if (isV1ElectionProtocol()) { diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript index 0a5d9ed80e5..506c3839f07 100644 --- a/src/mongo/db/s/SConscript +++ b/src/mongo/db/s/SConscript @@ -90,6 +90,7 @@ env.Library( 'config/configsvr_control_balancer_command.cpp', 'config/configsvr_move_chunk_command.cpp', 'config/configsvr_remove_shard_from_zone_command.cpp', + 'config/configsvr_set_feature_compatibility_version_command.cpp', 'config/configsvr_update_zone_key_range_command.cpp', 'get_shard_version_command.cpp', 'merge_chunks_command.cpp', diff --git a/src/mongo/db/s/config/configsvr_set_feature_compatibility_version_command.cpp b/src/mongo/db/s/config/configsvr_set_feature_compatibility_version_command.cpp new file mode 100644 index 00000000000..2e8fe09f5db --- /dev/null +++ b/src/mongo/db/s/config/configsvr_set_feature_compatibility_version_command.cpp @@ -0,0 +1,148 @@ +/** + * Copyright (C) 2016 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/auth/action_type.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/feature_compatibility_version.h" +#include "mongo/s/client/shard.h" +#include "mongo/s/client/shard_registry.h" +#include "mongo/s/grid.h" + +namespace mongo { + +namespace { + +/** + * Internal sharding command run on config servers to set featureCompatibilityVersion on all shards. + * + * Format: + * { + * _configsvrSetFeatureCompatibilityVersion: <string version> + * } + */ +class ConfigSvrSetFeatureCompatibilityVersionCommand : public Command { +public: + ConfigSvrSetFeatureCompatibilityVersionCommand() + : Command("_configsvrSetFeatureCompatibilityVersion") {} + + void help(std::stringstream& help) const override { + help << "Internal command, which is exported by the sharding config server. Do not call " + "directly. Sets featureCompatibilityVersion on all shards."; + } + + bool slaveOk() const override { + return false; + } + + bool adminOnly() const override { + return true; + } + + bool supportsWriteConcern(const BSONObj& cmd) const override { + return false; + } + + Status checkAuthForCommand(Client* client, + const std::string& dbname, + const BSONObj& cmdObj) override { + if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), ActionType::internal)) { + return Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + return Status::OK(); + } + + bool run(OperationContext* txn, + const std::string& unusedDbName, + BSONObj& cmdObj, + int options, + std::string& errmsg, + BSONObjBuilder& result) override { + uassert(ErrorCodes::IllegalOperation, + "_configsvrSetFeatureCompatibilityVersion can only be run on config servers", + serverGlobalParams.clusterRole == ClusterRole::ConfigServer); + + // Validate command. + std::string version; + for (auto&& elem : cmdObj) { + if (elem.fieldNameStringData() == "_configsvrSetFeatureCompatibilityVersion") { + uassert(ErrorCodes::TypeMismatch, + str::stream() << "_configsvrSetFeatureCompatibilityVersion must be of type " + "String, but was of type " + << typeName(elem.type()), + elem.type() == BSONType::String); + version = elem.String(); + } else { + uasserted(ErrorCodes::FailedToParse, + str::stream() << "unrecognized field '" << elem.fieldName() << "'"); + } + } + + uassert( + ErrorCodes::BadValue, + str::stream() << "invalid value for _configsvrSetFeatureCompatibilityVersion , found " + << version + << ", expected '" + << FeatureCompatibilityVersion::kVersion34 + << "' or '" + << FeatureCompatibilityVersion::kVersion32 + << "'", + version == FeatureCompatibilityVersion::kVersion34 || + version == FeatureCompatibilityVersion::kVersion32); + + // Set featureCompatibilityVersion on self. + FeatureCompatibilityVersion::set(txn, version); + + // Forward to all shards. + std::vector<ShardId> shardIds; + grid.shardRegistry()->getAllShardIds(&shardIds); + for (const ShardId& shardId : shardIds) { + const auto shard = grid.shardRegistry()->getShard(txn, shardId); + if (!shard) { + continue; + } + + auto response = + shard->runCommand(txn, + ReadPreferenceSetting{ReadPreference::PrimaryOnly}, + "admin", + BSON(FeatureCompatibilityVersion::kCommandName << version), + Shard::RetryPolicy::kIdempotent); + uassertStatusOK(response); + uassertStatusOK(response.getValue().commandStatus); + } + + return true; + } +} configsvrSetFeatureCompatibilityVersionCmd; + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/server_options.h b/src/mongo/db/server_options.h index 886b7bb0dd1..3c1b524f250 100644 --- a/src/mongo/db/server_options.h +++ b/src/mongo/db/server_options.h @@ -137,6 +137,22 @@ struct ServerGlobalParams { // for the YAML config, sharding._overrideShardIdentity. Can only be used when in // queryableBackupMode. BSONObj overrideShardIdentity; + + enum FeatureCompatibilityVersions { + /** + * There may be 3.2 nodes in the cluster, so some 3.4 features are not allowed. Default + * value. + */ + FeatureCompatibilityVersion_32, + + /** + * All nodes in the cluster should be 3.4, so all 3.4 features are allowed. + */ + FeatureCompatibilityVersion_34 + }; + + // Read-only parameter featureCompatibilityVersion. + AtomicWord<FeatureCompatibilityVersions> featureCompatibilityVersion; }; extern ServerGlobalParams serverGlobalParams; diff --git a/src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp b/src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp index 53ac9dd8a84..700bbcb9394 100644 --- a/src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp +++ b/src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp @@ -44,6 +44,7 @@ #include "mongo/client/replica_set_monitor.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" +#include "mongo/db/commands/feature_compatibility_version.h" #include "mongo/db/db_raii.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/namespace_string.h" @@ -720,6 +721,30 @@ StatusWith<string> ShardingCatalogManagerImpl::addShard( shardType.setMaxSizeMB(maxSize); } + // If the minimum allowed version for the cluster is 3.4, set the featureCompatibilityVersion to + // 3.4 on the shard. + if (serverGlobalParams.featureCompatibilityVersion.load() == + ServerGlobalParams::FeatureCompatibilityVersion_34) { + auto versionResponse = + _runCommandForAddShard(txn, + targeter.get(), + "admin", + BSON(FeatureCompatibilityVersion::kCommandName + << FeatureCompatibilityVersion::kVersion34)); + if (!versionResponse.isOK()) { + return versionResponse.getStatus(); + } + + if (!versionResponse.getValue().commandStatus.isOK()) { + if (versionResponse.getStatus().code() == ErrorCodes::CommandNotFound) { + return Status(ErrorCodes::OperationFailed, + "featureCompatibilityVersion for cluster is 3.4, cannot add a shard " + "with version below 3.4"); + } + return versionResponse.getValue().commandStatus; + } + } + auto commandRequest = createShardIdentityUpsertForAddShard(txn, shardType.getName()); LOG(2) << "going to insert shardIdentity document into shard: " << shardType; diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index 3a16737708f..8a672869951 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -59,6 +59,7 @@ env.Library( 'cluster_repair_database_cmd.cpp', 'cluster_repl_set_get_status_cmd.cpp', 'cluster_reset_error_cmd.cpp', + 'cluster_set_feature_compatibility_version_cmd.cpp', 'cluster_shard_collection_cmd.cpp', 'cluster_shutdown_cmd.cpp', 'cluster_split_cmd.cpp', diff --git a/src/mongo/s/commands/cluster_set_feature_compatibility_version_cmd.cpp b/src/mongo/s/commands/cluster_set_feature_compatibility_version_cmd.cpp new file mode 100644 index 00000000000..0bcc8ad6e2f --- /dev/null +++ b/src/mongo/s/commands/cluster_set_feature_compatibility_version_cmd.cpp @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2016 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/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/s/client/shard.h" +#include "mongo/s/client/shard_registry.h" +#include "mongo/s/grid.h" + +namespace mongo { + +namespace { + +/** + * Sets the minimum allowed version for the cluster. If it is 3.2, then shards should not use 3.4 + * features. + * + * Format: + * { + * setFeatureCompatibilityVersion: <string version> + * } + */ +class SetFeatureCompatibilityVersionCmd : public Command { +public: + SetFeatureCompatibilityVersionCmd() : Command("setFeatureCompatibilityVersion") {} + + virtual bool slaveOk() const { + return false; + } + + virtual bool adminOnly() const { + return true; + } + + virtual bool supportsWriteConcern(const BSONObj& cmd) const override { + return false; + } + + virtual void help(std::stringstream& help) const { + help << "set the minimum allowed version for the cluster, which determines what features " + "are available"; + } + + Status checkAuthForCommand(Client* client, + const std::string& dbname, + const BSONObj& cmdObj) override { + if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource( + ResourcePattern::forExactNamespace( + NamespaceString("$setFeatureCompatibilityVersion.version")), + ActionType::update)) { + return Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + return Status::OK(); + } + + bool run(OperationContext* txn, + const std::string& dbname, + BSONObj& cmdObj, + int options, + std::string& errmsg, + BSONObjBuilder& result) { + + // Validate command. + std::string version; + for (auto&& elem : cmdObj) { + if (elem.fieldNameStringData() == "setFeatureCompatibilityVersion") { + uassert( + ErrorCodes::TypeMismatch, + str::stream() + << "setFeatureCompatibilityVersion must be of type String, but was of type " + << typeName(elem.type()), + elem.type() == BSONType::String); + version = elem.String(); + } else { + uasserted(ErrorCodes::FailedToParse, + str::stream() << "unrecognized field '" << elem.fieldName() << "'"); + } + } + + uassert(ErrorCodes::BadValue, + str::stream() << "invalid value for setFeatureCompatibilityVersion, found " + << version + << ", expected '3.4' or '3.2'", + version == "3.4" || version == "3.2"); + + // Forward to config shard, which will forward to all shards. + auto configShard = Grid::get(txn)->shardRegistry()->getConfigShard(); + auto response = + configShard->runCommand(txn, + ReadPreferenceSetting{ReadPreference::PrimaryOnly}, + dbname, + BSON("_configsvrSetFeatureCompatibilityVersion" << version), + Shard::RetryPolicy::kIdempotent); + uassertStatusOK(response); + uassertStatusOK(response.getValue().commandStatus); + + return true; + } +} clusterSetFeatureCompatibilityVersionCmd; + +} // namespace +} // namespace mongo diff --git a/src/mongo/shell/shardingtest.js b/src/mongo/shell/shardingtest.js index 6a004ae280c..6c3a67aeded 100644 --- a/src/mongo/shell/shardingtest.js +++ b/src/mongo/shell/shardingtest.js @@ -1203,6 +1203,18 @@ var ShardingTest = function(params) { // Wait for master to be elected before starting mongos var csrsPrimary = this.configRS.getPrimary(); + // If we have mixed version shards, set featureCompatibilityVersion=3.2 on the config servers. + if (jsTestOptions().shardMixedBinVersions) { + function setFeatureCompatibilityVersion() { + assert.commandWorked(csrsPrimary.adminCommand({setFeatureCompatibilityVersion: '3.2'})); + } + if (keyFile) { + authutil.asCluster(csrsPrimary, keyFile, setFeatureCompatibilityVersion); + } else { + setFeatureCompatibilityVersion(); + } + } + // If chunkSize has been requested for this test, write the configuration if (otherParams.chunkSize) { function setChunkSize() { |