From 3641c00be722f394a2b0987cfcac023930e5bbf1 Mon Sep 17 00:00:00 2001 From: Amirsaman Memaripour Date: Mon, 2 Mar 2020 15:28:39 -0500 Subject: SERVER-46521 Extend mirrored reads generation tests (cherry picked from commit f321509051ee1da3fd3646bec3ba24658062ab19) --- src/mongo/db/commands/command_mirroring_test.cpp | 311 +++++++++++++++++++++-- 1 file changed, 288 insertions(+), 23 deletions(-) diff --git a/src/mongo/db/commands/command_mirroring_test.cpp b/src/mongo/db/commands/command_mirroring_test.cpp index 3f7f2753a5b..68b42d6ea30 100644 --- a/src/mongo/db/commands/command_mirroring_test.cpp +++ b/src/mongo/db/commands/command_mirroring_test.cpp @@ -53,13 +53,35 @@ public: auto client = Client::releaseCurrent(); client.reset(nullptr); } + virtual std::string commandName() = 0; - virtual OpMsgRequest makeCommand(std::string, std::vector) = 0; + virtual OpMsgRequest makeCommand(std::string coll, std::vector args) { + BSONObjBuilder bob; + + bob << commandName() << coll; + bob << "lsid" << _lsid.toBSON(); + + for (auto arg : args) { + bob << arg.firstElement(); + } + + auto request = OpMsgRequest::fromDBAndBody(kDB, bob.obj()); + return request; + } + + BSONObj createCommandAndGetMirrored(std::string coll, std::vector args) { + auto cmd = makeCommand(coll, args); + return getMirroredCommand(cmd); + } - const LogicalSessionId& getLogicalSessionId() const { - return _lsid; + // Checks if "a" and "b" (both BSON objects) are equal. + bool compareBSONObjs(BSONObj a, BSONObj b) { + return (a == b).type == BSONObj::DeferredComparison::Type::kEQ; } + static constexpr auto kDB = "test"_sd; + +private: BSONObj getMirroredCommand(OpMsgRequest& request) { auto cmd = globalCommandRegistry()->findCommand(request.getCommandName()); ASSERT(cmd); @@ -75,25 +97,22 @@ public: return bob.obj(); } - static constexpr auto kDB = "test"_sd; - -private: const LogicalSessionId _lsid; }; class UpdateCommandTest : public CommandMirroringTest { public: - OpMsgRequest makeCommand(std::string coll, std::vector updates) override { - BSONObjBuilder bob; - - bob << "update" << coll; - bob << "lsid" << getLogicalSessionId().toBSON(); + std::string commandName() override { + return "update"; + } - auto request = OpMsgRequest::fromDBAndBody(kDB, bob.obj()); + OpMsgRequest makeCommand(std::string coll, std::vector updates) override { + auto request = CommandMirroringTest::makeCommand(coll, {}); // Directly add `updates` to `OpMsg::sequences` to emulate `OpMsg::parse()` behavior. OpMsg::DocumentSequence seq; seq.name = "updates"; + for (auto update : updates) { seq.objs.emplace_back(std::move(update)); } @@ -105,9 +124,7 @@ public: TEST_F(UpdateCommandTest, NoQuery) { auto update = BSON("q" << BSONObj() << "u" << BSON("$set" << BSON("_id" << 1))); - auto cmd = makeCommand("my_collection", {update}); - - auto mirroredObj = getMirroredCommand(cmd); + auto mirroredObj = createCommandAndGetMirrored("my_collection", {update}); ASSERT_EQ(mirroredObj["find"].String(), "my_collection"); ASSERT_EQ(mirroredObj["filter"].Obj().toString(), "{}"); @@ -119,9 +136,7 @@ TEST_F(UpdateCommandTest, NoQuery) { TEST_F(UpdateCommandTest, SingleQuery) { auto update = BSON("q" << BSON("qty" << BSON("$lt" << 50.0)) << "u" << BSON("$inc" << BSON("qty" << 1))); - auto cmd = makeCommand("products", {update}); - - auto mirroredObj = getMirroredCommand(cmd); + auto mirroredObj = createCommandAndGetMirrored("products", {update}); ASSERT_EQ(mirroredObj["find"].String(), "products"); ASSERT_EQ(mirroredObj["filter"].Obj().toString(), "{ qty: { $lt: 50.0 } }"); @@ -136,9 +151,8 @@ TEST_F(UpdateCommandTest, SingleQueryWithHintAndCollation) { << BSON("locale" << "fr") << "u" << BSON("$inc" << BSON("price" << 10))); - auto cmd = makeCommand("products", {update}); - auto mirroredObj = getMirroredCommand(cmd); + auto mirroredObj = createCommandAndGetMirrored("products", {update}); ASSERT_EQ(mirroredObj["find"].String(), "products"); ASSERT_EQ(mirroredObj["filter"].Obj().toString(), "{ price: { $gt: 100 } }"); @@ -155,9 +169,7 @@ TEST_F(UpdateCommandTest, MultipleQueries) { updates.emplace_back(BSON("q" << BSON("_id" << BSON("$eq" << i)) << "u" << BSON("$inc" << BSON("qty" << 1)))); } - auto cmd = makeCommand("products", updates); - - auto mirroredObj = getMirroredCommand(cmd); + auto mirroredObj = createCommandAndGetMirrored("products", updates); ASSERT_EQ(mirroredObj["find"].String(), "products"); ASSERT_EQ(mirroredObj["filter"].Obj().toString(), "{ _id: { $eq: 0 } }"); @@ -166,5 +178,258 @@ TEST_F(UpdateCommandTest, MultipleQueries) { ASSERT_EQ(mirroredObj["batchSize"].Int(), 1); } +class FindCommandTest : public CommandMirroringTest { +public: + std::string commandName() override { + return "find"; + } + + virtual std::vector getAllowedKeys() const { + return {"find", + "filter", + "skip", + "limit", + "sort", + "hint", + "collation", + "min", + "max", + "batchSize", + "singleBatch"}; + } + + void checkFieldNamesAreAllowed(BSONObj& mirroredObj) { + const auto possibleKeys = getAllowedKeys(); + for (auto key : mirroredObj.getFieldNames>()) { + ASSERT(std::find(possibleKeys.begin(), possibleKeys.end(), key) != possibleKeys.end()); + } + } +}; + +TEST_F(FindCommandTest, MirrorableKeys) { + auto findArgs = {BSON("filter" << BSONObj()), + BSON("sort" << BSONObj()), + BSON("projection" << BSONObj()), + BSON("hint" << BSONObj()), + BSON("skip" << 1), + BSON("limit" << 1), + BSON("batchSize" << 1), + BSON("singleBatch" << true), + BSON("comment" + << "This is a comment."), + BSON("maxTimeMS" << 100), + BSON("readConcern" + << "primary"), + BSON("max" << BSONObj()), + BSON("min" << BSONObj()), + BSON("returnKey" << true), + BSON("showRecordId" << false), + BSON("tailable" << false), + BSON("oplogReplay" << true), + BSON("noCursorTimeout" << true), + BSON("awaitData" << true), + BSON("allowPartialResults" << true), + BSON("collation" << BSONObj())}; + + auto mirroredObj = createCommandAndGetMirrored("test", findArgs); + checkFieldNamesAreAllowed(mirroredObj); +} + +TEST_F(FindCommandTest, BatchSizeReconfiguration) { + auto findArgs = { + BSON("filter" << BSONObj()), BSON("batchSize" << 100), BSON("singleBatch" << false)}; + + auto mirroredObj = createCommandAndGetMirrored("test", findArgs); + ASSERT_EQ(mirroredObj["singleBatch"].Bool(), true); + ASSERT_EQ(mirroredObj["batchSize"].Int(), 1); +} + +TEST_F(FindCommandTest, ValidateMirroredQuery) { + constexpr auto collection = "restaurants"; + const auto filter = BSON("rating" << BSON("$gte" << 9) << "cuisine" + << "Italian"); + constexpr auto skip = 10; + constexpr auto limit = 50; + const auto sortObj = BSON("name" << 1); + const auto hint = BSONObj(); + const auto collation = BSON("locale" + << "\"fr\"" + << "strength" << 1); + const auto min = BSONObj(); + const auto max = BSONObj(); + + auto findArgs = {BSON("filter" << filter), + BSON("skip" << skip), + BSON("limit" << limit), + BSON("sort" << sortObj), + BSON("hint" << hint), + BSON("collation" << collation), + BSON("min" << min), + BSON("max" << max)}; + + auto mirroredObj = createCommandAndGetMirrored(collection, findArgs); + + ASSERT_EQ(mirroredObj["find"].String(), collection); + ASSERT(compareBSONObjs(mirroredObj["filter"].Obj(), filter)); + ASSERT_EQ(mirroredObj["skip"].Int(), skip); + ASSERT_EQ(mirroredObj["limit"].Int(), limit); + ASSERT(compareBSONObjs(mirroredObj["sort"].Obj(), sortObj)); + ASSERT(compareBSONObjs(mirroredObj["hint"].Obj(), hint)); + ASSERT(compareBSONObjs(mirroredObj["collation"].Obj(), collation)); + ASSERT(compareBSONObjs(mirroredObj["min"].Obj(), min)); + ASSERT(compareBSONObjs(mirroredObj["max"].Obj(), max)); +} + +class FindAndModifyCommandTest : public FindCommandTest { +public: + std::string commandName() override { + return "findAndModify"; + } + + std::vector getAllowedKeys() const override { + return {"sort", "collation", "find", "filter", "batchSize", "singleBatch"}; + } +}; + +TEST_F(FindAndModifyCommandTest, MirrorableKeys) { + auto findAndModifyArgs = {BSON("query" << BSONObj()), + BSON("sort" << BSONObj()), + BSON("remove" << false), + BSON("update" << BSONObj()), + BSON("new" << true), + BSON("fields" << BSONObj()), + BSON("upsert" << true), + BSON("bypassDocumentValidation" << false), + BSON("writeConcern" << BSONObj()), + BSON("maxTimeMS" << 100), + BSON("collation" << BSONObj()), + BSON("arrayFilters" << BSONArray())}; + + auto mirroredObj = createCommandAndGetMirrored("test", findAndModifyArgs); + checkFieldNamesAreAllowed(mirroredObj); +} + +TEST_F(FindAndModifyCommandTest, BatchSizeReconfiguration) { + auto findAndModifyArgs = {BSON("query" << BSONObj()), + BSON("update" << BSONObj()), + BSON("batchSize" << 100), + BSON("singleBatch" << false)}; + + auto mirroredObj = createCommandAndGetMirrored("test", findAndModifyArgs); + ASSERT_EQ(mirroredObj["singleBatch"].Bool(), true); + ASSERT_EQ(mirroredObj["batchSize"].Int(), 1); +} + +TEST_F(FindAndModifyCommandTest, ValidateMirroredQuery) { + constexpr auto collection = "people"; + const auto query = BSON("name" + << "Andy"); + const auto sortObj = BSON("rating" << 1); + const auto update = BSON("$inc" << BSON("score" << 1)); + constexpr auto upsert = true; + const auto collation = BSON("locale" + << "\"fr\""); + + auto findAndModifyArgs = {BSON("query" << query), + BSON("sort" << sortObj), + BSON("update" << update), + BSON("upsert" << upsert), + BSON("collation" << collation)}; + + auto mirroredObj = createCommandAndGetMirrored(collection, findAndModifyArgs); + + ASSERT_EQ(mirroredObj["find"].String(), collection); + ASSERT(!mirroredObj.hasField("upsert")); + ASSERT(compareBSONObjs(mirroredObj["filter"].Obj(), query)); + ASSERT(compareBSONObjs(mirroredObj["sort"].Obj(), sortObj)); + ASSERT(compareBSONObjs(mirroredObj["collation"].Obj(), collation)); +} + +class DistinctCommandTest : public FindCommandTest { +public: + std::string commandName() override { + return "distinct"; + } + + std::vector getAllowedKeys() const override { + return {"distinct", "key", "query", "collation"}; + } +}; + +TEST_F(DistinctCommandTest, MirrorableKeys) { + auto distinctArgs = {BSON("key" + << ""), + BSON("query" << BSONObj()), + BSON("readConcern" << BSONObj()), + BSON("collation" << BSONObj())}; + + auto mirroredObj = createCommandAndGetMirrored("test", distinctArgs); + checkFieldNamesAreAllowed(mirroredObj); +} + +TEST_F(DistinctCommandTest, ValidateMirroredQuery) { + constexpr auto collection = "restaurants"; + constexpr auto key = "rating"; + const auto query = BSON("cuisine" + << "italian"); + const auto readConcern = BSON("level" + << "majority"); + const auto collation = BSON("strength" << 1); + + auto distinctArgs = {BSON("key" << key), + BSON("query" << query), + BSON("readConcern" << readConcern), + BSON("collation" << collation)}; + + auto mirroredObj = createCommandAndGetMirrored(collection, distinctArgs); + + ASSERT_EQ(mirroredObj["distinct"].String(), collection); + ASSERT(!mirroredObj.hasField("readConcern")); + ASSERT_EQ(mirroredObj["key"].String(), key); + ASSERT(compareBSONObjs(mirroredObj["query"].Obj(), query)); + ASSERT(compareBSONObjs(mirroredObj["collation"].Obj(), collation)); +} + +class CountCommandTest : public FindCommandTest { +public: + std::string commandName() override { + return "count"; + } + + std::vector getAllowedKeys() const override { + return {"count", "query", "skip", "limit", "hint", "collation"}; + } +}; + +TEST_F(CountCommandTest, MirrorableKeys) { + auto countArgs = {BSON("query" << BSONObj()), + BSON("limit" << 100), + BSON("skip" << 10), + BSON("hint" << BSONObj()), + BSON("readConcern" << BSONObj()), + BSON("collation" << BSONObj())}; + + auto mirroredObj = createCommandAndGetMirrored("test", countArgs); + checkFieldNamesAreAllowed(mirroredObj); +} + +TEST_F(CountCommandTest, ValidateMirroredQuery) { + constexpr auto collection = "orders"; + const auto query = BSON("status" + << "Delivered"); + const auto hint = BSON("status" << 1); + constexpr auto limit = 1000; + + auto countArgs = {BSON("query" << query), BSON("hint" << hint), BSON("limit" << limit)}; + auto mirroredObj = createCommandAndGetMirrored(collection, countArgs); + + ASSERT_EQ(mirroredObj["count"].String(), collection); + ASSERT(!mirroredObj.hasField("skip")); + ASSERT(!mirroredObj.hasField("collation")); + ASSERT(compareBSONObjs(mirroredObj["query"].Obj(), query)); + ASSERT(compareBSONObjs(mirroredObj["hint"].Obj(), hint)); + ASSERT_EQ(mirroredObj["limit"].Int(), limit); +} + } // namespace } // namespace mongo -- cgit v1.2.1