summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMindaugas Malinauskas <mindaugas.malinauskas@mongodb.com>2020-08-21 10:54:14 +0300
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-08-31 12:08:32 +0000
commitf5da4e4b7f7f04267bd92736ee9b16417e6d70ff (patch)
treef0ae8b67db601735b166dc1b592d21f4c92d7ad7
parent1eb0308decd6709fe9d0df9212fa690cd20e03fd (diff)
downloadmongo-f5da4e4b7f7f04267bd92736ee9b16417e6d70ff.tar.gz
SERVER-49499 Upgrade/downgrade behavior and testing for generating document validation errors
-rw-r--r--jstests/multiVersion/doc_validation_error_upgrade_downgrade.js170
-rw-r--r--src/mongo/base/error_codes.yml4
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp20
-rw-r--r--src/mongo/db/commands/write_commands/write_commands.cpp5
-rw-r--r--src/mongo/db/matcher/doc_validation_error.cpp8
5 files changed, 200 insertions, 7 deletions
diff --git a/jstests/multiVersion/doc_validation_error_upgrade_downgrade.js b/jstests/multiVersion/doc_validation_error_upgrade_downgrade.js
new file mode 100644
index 00000000000..b78c474eb7c
--- /dev/null
+++ b/jstests/multiVersion/doc_validation_error_upgrade_downgrade.js
@@ -0,0 +1,170 @@
+/**
+ * Tests document validation behavior during an upgrade of a sharded cluster from 4.4 to 4.7+ and
+ * during a corresponding downgrade as well as document validation behavior of mongod in FCV 4.4
+ * mode.
+ *
+ * TODO SERVER-50524: this test is specific to the 4.4 - 4.7+ upgrade process, and can be removed
+ * when 5.0 becomes last-lts.
+ */
+(function() {
+"use strict";
+load("jstests/multiVersion/libs/multi_cluster.js"); // For upgradeCluster.
+load("jstests/libs/doc_validation_utils.js"); // For assertDocumentValidationFailure.
+const collName = jsTestName();
+
+/**
+ * Performs a direct and an indirect (through aggregation stage $out) insertion of documents that
+ * fail validation and checks the responses. 'sourceDB' is a database that is a source of documents
+ * being copied into database 'targetDB' using aggregation stage $out. Both databases have a
+ * collection named 'collName' and the collection in 'targetDB' has a validator set. 'assertFn' is a
+ * function that verifies that the command result is a document validation error and conforms to
+ * some FCV. The first parameter of the function is either a raw command response, or a wrapper of a
+ * result of write commands ('BulkWriteResult' or 'WriteResult'), and the second - a collection
+ * which documents are being inserted into.
+ */
+function testDocumentValidation(sourceDB, targetDB, assertFn) {
+ const sourceColl = sourceDB[collName];
+
+ // Insert a document into a collection in 'sourceDB'.
+ assert.commandWorked(sourceColl.remove({}));
+ assert.commandWorked(sourceColl.insert({a: 2}));
+
+ // Issue an 'aggregate' command that copies all documents from the source collection to the
+ // target collection.
+ const res = sourceDB.runCommand(
+ {aggregate: collName, pipeline: [{$out: {db: "targetDB", coll: collName}}], cursor: {}});
+
+ // Verify that document insertion failed due to document validation error.
+ assertFn(res, sourceColl);
+
+ // Verify that a direct document insertion to a collection with a document validator fails due
+ // to document validation error.
+ assertFn(targetDB[collName].insert({a: 2}), targetDB[collName]);
+}
+
+/**
+ * Assert that 'res' corresponds to DocumentValidationFailure, and verify that its format conforms
+ * to FCV4.4 - field 'errInfo' is not present.
+ */
+function assertFCV44DocumentValidationFailure(res, coll) {
+ assert.commandFailedWithCode(res, ErrorCodes.DocumentValidationFailure, tojson(res));
+ if (coll.getMongo().writeMode() === "commands") {
+ if (res instanceof BulkWriteResult) {
+ const errors = res.getWriteErrors();
+ for (const error of errors) {
+ assert(!error.hasOwnProperty("errInfo"), tojson(error));
+ }
+ } else {
+ const error = res instanceof WriteResult ? res.getWriteError() : res;
+ assert(!error.hasOwnProperty("errInfo"), tojson(error));
+ }
+ }
+}
+
+// Test document validation behavior of mongod in FCV 4.4 mode.
+(function() {
+const mongod = MongoRunner.runMongod();
+assert.neq(null, mongod, "mongod was unable to start up");
+const testDB = mongod.getDB("test");
+
+// Set FCV to 4.4.
+assert.commandWorked(mongod.adminCommand({setFeatureCompatibilityVersion: lastLTSFCV}));
+
+// Create a collection with a validator.
+assert.commandWorked(testDB.createCollection(collName, {validator: {a: 1}}));
+
+// Verify that a document insertion fails due to document validation error that conforms to FCV4.4.
+assertFCV44DocumentValidationFailure(testDB[collName].insert({a: 2}), testDB[collName]);
+MongoRunner.stopMongod(mongod);
+})();
+
+// Test document validation behavior during an upgrade of a sharded cluster from 4.4 to 4.7+ and a
+// corresponding downgrade.
+(function() {
+// Start a sharded cluster in which all processes are of the 4.4 binVersion.
+const st = new ShardingTest({
+ shards: 2,
+ rs: {nodes: 2, binVersion: "last-lts"},
+ other: {mongosOptions: {binVersion: "last-lts"}, configOptions: {binVersion: "last-lts"}}
+});
+const mongos = st.s;
+
+// Set cluster FCV to 4.4.
+assert.commandWorked(mongos.adminCommand({setFeatureCompatibilityVersion: lastLTSFCV}));
+
+// Two databases 'sourceDB' and 'targetDB' are setup that have different shards set as primary to
+// test if document validation related aspects of inter-shard communication work correctly. This
+// communication is triggered by issuing the aggregate command that reads documents from a
+// collection in a database in one shard ('sourceDB') and inserts them into a database in another
+// shard ('targetDB'). First create a database "sourceDB" and assign it to the first shard.
+let sourceDB = mongos.getDB("sourceDB");
+assert.commandWorked(sourceDB.createCollection(collName));
+st.ensurePrimaryShard("sourceDB", st.shard0.shardName);
+
+let targetDB = mongos.getDB("targetDB");
+
+// Create a collection with a validator.
+assert.commandWorked(targetDB.createCollection(collName, {validator: {a: 1}}));
+
+// Assign database "targetDB" to the second shard.
+st.ensurePrimaryShard("targetDB", st.shard1.shardName);
+
+// Perform document insertion and verify output conformance to FCV4.4.
+testDocumentValidation(sourceDB, targetDB, assertFCV44DocumentValidationFailure);
+
+// Upgrade config servers and the second shard to latest version.
+st.upgradeCluster("latest", {upgradeShards: false, upgradeConfigs: true, upgradeMongos: false});
+st.rs1.upgradeSet({binVersion: "latest"});
+
+// Perform document insertion and verify output conformance to FCV4.4.
+testDocumentValidation(sourceDB, targetDB, assertFCV44DocumentValidationFailure);
+
+// Upgrade the remaining shard.
+st.upgradeCluster("latest", {upgradeShards: true, upgradeConfigs: false, upgradeMongos: false});
+
+// Perform document insertion and verify output conformance to FCV4.4.
+testDocumentValidation(sourceDB, targetDB, assertFCV44DocumentValidationFailure);
+
+// Upgrade the mongos.
+st.upgradeCluster("latest", {upgradeShards: false, upgradeConfigs: false, upgradeMongos: true});
+sourceDB = st.s.getDB("sourceDB");
+targetDB = st.s.getDB("targetDB");
+
+// Perform document insertion and verify output conformance to FCV4.4.
+testDocumentValidation(sourceDB, targetDB, assertFCV44DocumentValidationFailure);
+
+// Set FCV to 4.7.
+assert.commandWorked(st.s.adminCommand({setFeatureCompatibilityVersion: latestFCV}));
+
+// Perform document insertion and verify that the server now provides the "errInfo" field, which
+// contains the document validation failure details.
+testDocumentValidation(sourceDB, targetDB, assertDocumentValidationFailure);
+
+// Start a downgrade. Set FCV to 4.4.
+assert.commandWorked(st.s.adminCommand({setFeatureCompatibilityVersion: lastLTSFCV}));
+
+// Perform document insertion and verify output conformance to FCV4.4.
+testDocumentValidation(sourceDB, targetDB, assertFCV44DocumentValidationFailure);
+
+// Downgrade the mongos.
+st.upgradeCluster("last-lts", {upgradeShards: false, upgradeConfigs: false, upgradeMongos: true});
+sourceDB = st.s.getDB("sourceDB");
+targetDB = st.s.getDB("targetDB");
+
+// Perform document insertion and verify output conformance to FCV4.4.
+testDocumentValidation(sourceDB, targetDB, assertFCV44DocumentValidationFailure);
+
+// Downgrade the first shard.
+st.rs0.upgradeSet({binVersion: "last-lts"});
+
+// Perform document insertion and verify output conformance to FCV4.4.
+testDocumentValidation(sourceDB, targetDB, assertFCV44DocumentValidationFailure);
+
+// Downgrade remaining shards and the config servers.
+st.upgradeCluster("last-lts", {upgradeShards: true, upgradeConfigs: true, upgradeMongos: false});
+
+// Perform document insertion and verify output conformance to FCV4.4.
+testDocumentValidation(sourceDB, targetDB, assertFCV44DocumentValidationFailure);
+st.stop();
+})();
+})(); \ No newline at end of file
diff --git a/src/mongo/base/error_codes.yml b/src/mongo/base/error_codes.yml
index fe34bf4f7d8..676d675dc00 100644
--- a/src/mongo/base/error_codes.yml
+++ b/src/mongo/base/error_codes.yml
@@ -153,7 +153,9 @@ error_codes:
- {code: 120,name: OplogStartMissing}
# Error code 121 is only for the document validator on collections.
- {code: 121,name: DocumentValidationFailure,
- extra: 'doc_validation_error::DocumentValidationFailureInfo'}
+ extra: 'doc_validation_error::DocumentValidationFailureInfo',
+ # TODO SERVER-50524: Make extra info mandatory when 5.0 becomes last-lts.
+ extraIsOptional: True}
- {code: 122,name: OBSOLETE_ReadAfterOptimeTimeout}
- {code: 123,name: NotAReplicaSet}
- {code: 124,name: IncompatibleElectionProtocol}
diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp
index 30520fe8a54..c113ce8e34c 100644
--- a/src/mongo/db/catalog/collection_impl.cpp
+++ b/src/mongo/db/catalog/collection_impl.cpp
@@ -65,6 +65,7 @@
#include "mongo/db/query/internal_plans.h"
#include "mongo/db/repl/oplog.h"
#include "mongo/db/repl/replication_coordinator.h"
+#include "mongo/db/server_options.h"
#include "mongo/db/service_context.h"
#include "mongo/db/storage/durable_catalog.h"
#include "mongo/db/storage/key_string.h"
@@ -400,7 +401,15 @@ Status CollectionImpl::checkValidation(OperationContext* opCtx, const BSONObj& d
if (validatorMatchExpr->matchesBSON(document))
return Status::OK();
- BSONObj generatedError = doc_validation_error::generateError(*validatorMatchExpr, document);
+ // TODO SERVER-50524: remove these FCV checks when 5.0 becomes last-lts in order to make sure
+ // that an upgrade from 4.4 directly to the 5.0 LTS version is supported.
+ const auto isFCVAtLeast47 = serverGlobalParams.featureCompatibility.isVersionInitialized() &&
+ serverGlobalParams.featureCompatibility.isGreaterThanOrEqualTo(
+ ServerGlobalParams::FeatureCompatibility::Version::kVersion47);
+ BSONObj generatedError;
+ if (isFCVAtLeast47) {
+ generatedError = doc_validation_error::generateError(*validatorMatchExpr, document);
+ }
if (_validationAction == ValidationAction::WARN) {
LOGV2_WARNING(20294,
@@ -411,8 +420,13 @@ Status CollectionImpl::checkValidation(OperationContext* opCtx, const BSONObj& d
return Status::OK();
}
- return {doc_validation_error::DocumentValidationFailureInfo(generatedError),
- "Document failed validation"};
+ static constexpr auto kValidationFailureErrorStr = "Document failed validation"_sd;
+ if (isFCVAtLeast47) {
+ return {doc_validation_error::DocumentValidationFailureInfo(generatedError),
+ kValidationFailureErrorStr};
+ } else {
+ return {ErrorCodes::DocumentValidationFailure, kValidationFailureErrorStr};
+ }
}
Collection::Validator CollectionImpl::parseValidator(
diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp
index 8c0d4e0549d..e79e9ce5581 100644
--- a/src/mongo/db/commands/write_commands/write_commands.cpp
+++ b/src/mongo/db/commands/write_commands/write_commands.cpp
@@ -154,8 +154,9 @@ void serializeReply(OperationContext* opCtx,
BSONObjBuilder errInfo(error.subobjStart("errInfo"));
staleInfo->serialize(&errInfo);
}
- } else if (auto docValidationError =
- status.extraInfo<doc_validation_error::DocumentValidationFailureInfo>()) {
+ } else if (ErrorCodes::DocumentValidationFailure == status.code() && status.extraInfo()) {
+ auto docValidationError =
+ status.extraInfo<doc_validation_error::DocumentValidationFailureInfo>();
error.append("code", static_cast<int>(ErrorCodes::DocumentValidationFailure));
error.append("errInfo", docValidationError->getDetails());
} else {
diff --git a/src/mongo/db/matcher/doc_validation_error.cpp b/src/mongo/db/matcher/doc_validation_error.cpp
index c5320f88bc5..bbc2f4eb1cb 100644
--- a/src/mongo/db/matcher/doc_validation_error.cpp
+++ b/src/mongo/db/matcher/doc_validation_error.cpp
@@ -974,6 +974,10 @@ bool hasErrorAnnotations(const MatchExpression& validatorExpr) {
} // namespace
std::shared_ptr<const ErrorExtraInfo> DocumentValidationFailureInfo::parse(const BSONObj& obj) {
+ if (!obj.hasField("errInfo"_sd)) {
+ // TODO SERVER-50524: remove this block when 5.0 becomes last-lts.
+ return nullptr;
+ }
auto errInfo = obj["errInfo"];
uassert(4878100,
"DocumentValidationFailureInfo must have a field 'errInfo' of type object",
@@ -993,7 +997,9 @@ BSONObj generateError(const MatchExpression& validatorExpr, const BSONObj& doc)
ValidationErrorInVisitor inVisitor{&context};
ValidationErrorPostVisitor postVisitor{&context};
// TODO SERVER-49446: Once all nodes have ErrorAnnotations, this check should be converted to an
- // invariant check that all nodes have an annotation.
+ // invariant check that all nodes have an annotation. Also add an invariant to the
+ // DocumentValidationFailureInfo constructor to check that it is initialized with a non-empty
+ // object.
if (!hasErrorAnnotations(validatorExpr)) {
return BSONObj();
}