summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Hirschhorn <max.hirschhorn@mongodb.com>2016-01-27 10:33:12 -0500
committerMax Hirschhorn <max.hirschhorn@mongodb.com>2016-01-27 10:33:12 -0500
commitffe490c80ecea03b02a0b20d55915c4efd2805e8 (patch)
treef55d8ac7b6dc2f3f933d56d1a7ee94d4bf3b3582
parent58f2842252bc833b3b051d46117d3f243ac2548f (diff)
downloadmongo-ffe490c80ecea03b02a0b20d55915c4efd2805e8.tar.gz
SERVER-11064 Stricter validation of index key patterns.
Each value in the index key pattern must be one of the following: - a number > 0 (ascending) - a number < 0 (descending) - a string (special index type) Since the key pattern is validated on startup for all existing indexes, mongod will fail to start up if an index not meeting the above criteria exists. Additionally, if an index with an invalid key pattern is replicated from an older version, then newer versions of mongod running in a mixed-version replica set will fassert().
-rw-r--r--jstests/multiVersion/invalid_key_pattern_upgrade.js95
-rw-r--r--src/mongo/db/catalog/index_key_validate.cpp16
-rw-r--r--src/mongo/db/catalog/index_key_validate_test.cpp62
3 files changed, 166 insertions, 7 deletions
diff --git a/jstests/multiVersion/invalid_key_pattern_upgrade.js b/jstests/multiVersion/invalid_key_pattern_upgrade.js
new file mode 100644
index 00000000000..27aa8590989
--- /dev/null
+++ b/jstests/multiVersion/invalid_key_pattern_upgrade.js
@@ -0,0 +1,95 @@
+/**
+ * Test the upgrade process for 3.2 ~~> the latest version involving indexes with invalid key
+ * patterns:
+ * - Having an index with an invalid key pattern should cause the mongod to fail to start up.
+ * - A request to build an index with an invalid key pattern should cause a secondary running the
+ * latest version and syncing off a primary running 3.2 to fassert.
+ */
+(function() {
+ 'use strict';
+
+ var testCases = [
+ {a: 0},
+ {a: NaN},
+ {a: true},
+ ];
+
+ // The mongod should not start up when an index with an invalid key pattern exists.
+ testCases.forEach(function(indexKeyPattern) {
+ jsTest.log('Upgrading from a 3.2 instance to the latest version. This should fail when an' +
+ ' index with an invalid key pattern ' + tojson(indexKeyPattern) + ' exists');
+
+ var dbpath = MongoRunner.dataPath + 'invalid_key_pattern_upgrade';
+ resetDbpath(dbpath);
+
+ var defaultOptions = {
+ dbpath: dbpath,
+ noCleanData: true,
+ };
+
+ // Start the old version.
+ var oldVersionOptions = Object.extend({binVersion: '3.2'}, defaultOptions);
+ var conn = MongoRunner.runMongod(oldVersionOptions);
+ assert.neq(null, conn, 'mongod was unable to start up with options ' +
+ tojson(oldVersionOptions));
+
+ // Use write commands in order to make assertions about the success of operations based on
+ // the response from the server.
+ conn.forceWriteMode('commands');
+ assert.commandWorked(conn.getDB('test').coll.createIndex(indexKeyPattern),
+ 'failed to create index with key pattern' + tojson(indexKeyPattern));
+ MongoRunner.stopMongod(conn);
+
+ // Start the newest version.
+ conn = MongoRunner.runMongod(defaultOptions);
+ assert.eq(null, conn, 'mongod should not have been able to start up when an index with' +
+ ' an invalid key pattern' + tojson(indexKeyPattern) + ' exists');
+ });
+
+ // Create a replica set with a primary running 3.2 and a secondary running the latest version.
+ // The secondary should terminate when the command to build an index with an invalid key pattern
+ // replicates.
+ testCases.forEach(function(indexKeyPattern) {
+ var replSetName = 'invalid_key_pattern_replset';
+ var nodes = [
+ {binVersion: '3.2'},
+ {binVersion: 'latest'},
+ ];
+
+ var rst = new ReplSetTest({name: replSetName, nodes: nodes});
+
+ var conns = rst.startSet({startClean: true});
+
+ // Use write commands in order to make assertions about success of operations based on the
+ // response.
+ conns.forEach(function(conn) {
+ conn.forceWriteMode('commands');
+ });
+
+ // Rig the election so that the 3.2 node becomes the primary.
+ var replSetConfig = rst.getReplSetConfig();
+ replSetConfig.members[1].priority = 0;
+
+ rst.initiate(replSetConfig);
+
+ var primary32 = conns[0];
+ var secondaryLatest = conns[1];
+
+ assert.commandWorked(primary32.getDB('test').coll.createIndex(indexKeyPattern),
+ 'failed to create index with key pattern ' + tojson(indexKeyPattern));
+
+ // Verify that the secondary running the latest version terminates when the command to build
+ // an index with an invalid key pattern replicates.
+ assert.soon(function() {
+ try {
+ secondaryLatest.getDB('test').runCommand({ping: 1});
+ } catch (e) {
+ return true;
+ }
+ return false;
+ }, 'secondary should have terminated due to request to build an index with an invalid key' +
+ ' pattern ' + tojson(indexKeyPattern));
+
+ rst.stopSet(undefined, undefined, {allowedExitCodes: [MongoRunner.EXIT_ABRUPT]});
+ });
+})();
diff --git a/src/mongo/db/catalog/index_key_validate.cpp b/src/mongo/db/catalog/index_key_validate.cpp
index ecac64fec1d..4c692f98f37 100644
--- a/src/mongo/db/catalog/index_key_validate.cpp
+++ b/src/mongo/db/catalog/index_key_validate.cpp
@@ -30,6 +30,7 @@
#include "mongo/db/catalog/index_key_validate.h"
+#include <cmath>
#include <limits>
#include "mongo/db/field_ref.h"
@@ -61,8 +62,19 @@ Status validateKeyPattern(const BSONObj& key) {
while (it.more()) {
BSONElement keyElement = it.next();
- if (keyElement.type() == Object || keyElement.type() == Array)
- return Status(code, "Index keys cannot be Objects or Arrays.");
+ if (keyElement.isNumber()) {
+ double value = keyElement.number();
+ if (std::isnan(value)) {
+ return {code, "Values in the index key pattern cannot be NaN."};
+ } else if (value == 0.0) {
+ return {code, "Values in the index key pattern cannot be 0."};
+ }
+ } else if (keyElement.type() != String) {
+ return {code,
+ str::stream() << "Values in index key pattern cannot be of type "
+ << typeName(keyElement.type())
+ << ". Only numbers > 0, numbers < 0, and strings are allowed."};
+ }
if (keyElement.type() == String && pluginName != keyElement.str()) {
return Status(code, "Can't use more than one index plugin for a single index.");
diff --git a/src/mongo/db/catalog/index_key_validate_test.cpp b/src/mongo/db/catalog/index_key_validate_test.cpp
index b8be05048f9..1cfe8c03d69 100644
--- a/src/mongo/db/catalog/index_key_validate_test.cpp
+++ b/src/mongo/db/catalog/index_key_validate_test.cpp
@@ -30,6 +30,8 @@
#include "mongo/db/catalog/index_key_validate.h"
+#include <limits>
+
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/unittest/unittest.h"
@@ -48,8 +50,20 @@ TEST(IndexKeyValidateTest, KeyElementValueOfSmallNegativeIntSucceeds) {
ASSERT_OK(validateKeyPattern(BSON("x" << -5)));
}
-TEST(IndexKeyValidateTest, KeyElementValueOfZeroSucceeds) {
- ASSERT_OK(validateKeyPattern(BSON("x" << 0)));
+TEST(IndexKeyValidateTest, KeyElementValueOfZeroFails) {
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << 0)));
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << 0.0)));
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << -0.0)));
+}
+
+TEST(IndexKeyValidateTest, KeyElementValueOfNaNFails) {
+ if (std::numeric_limits<double>::has_quiet_NaN) {
+ double nan = std::numeric_limits<double>::quiet_NaN();
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << nan)));
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex,
+ validateKeyPattern(BSON("a" << nan << "b"
+ << "2d")));
+ }
}
TEST(IndexKeyValidateTest, KeyElementValuePositiveFloatingPointSucceeds) {
@@ -67,9 +81,47 @@ TEST(IndexKeyValidateTest, KeyElementValueOfBadPluginStringFails) {
ASSERT_EQ(status, ErrorCodes::CannotCreateIndex);
}
-TEST(IndexKeyValidateTest, KeyElementBooleanValueSucceeds) {
- ASSERT_OK(validateKeyPattern(BSON("x" << true)));
- ASSERT_OK(validateKeyPattern(BSON("x" << false)));
+TEST(IndexKeyValidateTest, KeyElementBooleanValueFails) {
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << true)));
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << false)));
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex,
+ validateKeyPattern(BSON("a"
+ << "2dsphere"
+ << "b" << true)));
+}
+
+TEST(IndexKeyValidateTest, KeyElementNullValueFails) {
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << BSONNULL)));
+}
+
+TEST(IndexKeyValidateTest, KeyElementUndefinedValueFails) {
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << BSONUndefined)));
+}
+
+TEST(IndexKeyValidateTest, KeyElementMinKeyValueFails) {
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << MINKEY)));
+}
+
+TEST(IndexKeyValidateTest, KeyElementMaxKeyValueFails) {
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << MAXKEY)));
+}
+
+TEST(IndexKeyValidateTest, KeyElementObjectValueFails) {
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << BSON("y" << 1))));
+}
+
+TEST(IndexKeyValidateTest, KeyElementArrayValueFails) {
+ ASSERT_EQ(ErrorCodes::CannotCreateIndex, validateKeyPattern(BSON("x" << BSON_ARRAY(1))));
+}
+
+TEST(IndexKeyValidateTest, CompoundKeySucceedsOn2dGeoIndex) {
+ ASSERT_OK(validateKeyPattern(BSON("a" << 1 << "b"
+ << "2d")));
+}
+
+TEST(IndexKeyValidateTest, CompoundKeySucceedsOn2dsphereGeoIndex) {
+ ASSERT_OK(validateKeyPattern(BSON("a" << 1 << "b"
+ << "2dsphere")));
}
TEST(IndexKeyValidateTest, KeyElementNameTextFailsOnNonTextIndex) {