diff options
author | Mindaugas Malinauskas <mindaugas.malinauskas@mongodb.com> | 2020-08-21 10:54:14 +0300 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-08-31 12:08:32 +0000 |
commit | f5da4e4b7f7f04267bd92736ee9b16417e6d70ff (patch) | |
tree | f0ae8b67db601735b166dc1b592d21f4c92d7ad7 | |
parent | 1eb0308decd6709fe9d0df9212fa690cd20e03fd (diff) | |
download | mongo-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.js | 170 | ||||
-rw-r--r-- | src/mongo/base/error_codes.yml | 4 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_impl.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/commands/write_commands/write_commands.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/matcher/doc_validation_error.cpp | 8 |
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(); } |