summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2016-08-10 14:18:33 -0400
committerTess Avitabile <tess.avitabile@mongodb.com>2016-08-19 17:57:33 -0400
commitdc4a0797603401f7560199f15bd9dd29bd17b3a0 (patch)
tree6442bee3f873585b3bc46bbcdeee6806d34ddb7c
parent9efbffec2c4be8c597a6d39ba3474f5d4a110454 (diff)
downloadmongo-dc4a0797603401f7560199f15bd9dd29bd17b3a0.tar.gz
SERVER-25155 Create setFeatureCompatibilityVersion command
-rw-r--r--buildscripts/resmokeconfig/suites/auth.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/auth_audit.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/replication.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/replication_auth.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/tool.yml5
-rw-r--r--jstests/auth/lib/commands_lib.js17
-rw-r--r--jstests/disk/newcollection.js16
-rw-r--r--jstests/disk/preallocate.js20
-rw-r--r--jstests/multiVersion/mixed_storage_version_replication.js4
-rw-r--r--jstests/multiVersion/set_feature_compatibility_version.js294
-rw-r--r--jstests/noPassthroughWithMongod/initial_sync_replSetGetStatus.js2
-rw-r--r--jstests/sharding/features1.js4
-rw-r--r--jstests/sharding/listDatabases.js4
-rw-r--r--jstests/tool/dumprestore_auth2.js12
-rw-r--r--jstests/views/views_all_commands.js2
-rw-r--r--src/mongo/db/auth/role_graph_builtin_roles.cpp6
-rw-r--r--src/mongo/db/commands/SConscript2
-rw-r--r--src/mongo/db/commands/feature_compatibility_version.cpp251
-rw-r--r--src/mongo/db/commands/feature_compatibility_version.h83
-rw-r--r--src/mongo/db/commands/set_feature_compatibility_version_command.cpp124
-rw-r--r--src/mongo/db/db.cpp28
-rw-r--r--src/mongo/db/op_observer.cpp15
-rw-r--r--src/mongo/db/repl/replication_coordinator_external_state.h13
-rw-r--r--src/mongo/db/repl/replication_coordinator_external_state_impl.cpp7
-rw-r--r--src/mongo/db/repl/replication_coordinator_external_state_impl.h19
-rw-r--r--src/mongo/db/repl/replication_coordinator_external_state_mock.cpp4
-rw-r--r--src/mongo/db/repl/replication_coordinator_external_state_mock.h3
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl.cpp3
-rw-r--r--src/mongo/db/s/SConscript1
-rw-r--r--src/mongo/db/s/config/configsvr_set_feature_compatibility_version_command.cpp148
-rw-r--r--src/mongo/db/server_options.h16
-rw-r--r--src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp25
-rw-r--r--src/mongo/s/commands/SConscript1
-rw-r--r--src/mongo/s/commands/cluster_set_feature_compatibility_version_cmd.cpp129
-rw-r--r--src/mongo/shell/shardingtest.js12
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() {