diff options
author | Hugh Tong <hugh.tong@mongodb.com> | 2022-12-20 22:23:27 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-12-20 23:08:56 +0000 |
commit | 20770047f9ac49ff8d344bcb10258a9010bcf74b (patch) | |
tree | 4a1957aac0fd5ff4c9163975b2f6edc56beaf4a1 | |
parent | 76bf2602159dece2a833073924638aa3b0796205 (diff) | |
download | mongo-20770047f9ac49ff8d344bcb10258a9010bcf74b.tar.gz |
SERVER-71030 Modify CursorResponse to serialize/deserialize NamespaceString with tenantId
22 files changed, 233 insertions, 69 deletions
diff --git a/buildscripts/resmokeconfig/suites/native_tenant_data_isolation_with_security_token_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/native_tenant_data_isolation_with_security_token_jscore_passthrough.yml index 17d1deaad5a..b8f9616c951 100644 --- a/buildscripts/resmokeconfig/suites/native_tenant_data_isolation_with_security_token_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/native_tenant_data_isolation_with_security_token_jscore_passthrough.yml @@ -16,6 +16,8 @@ selector: # columnstore indexes are under development and cannot be used without enabling the feature flag - featureFlagColumnstoreIndexes - featureFlagSbeFull + # Server side javascript not allowed in Serverless. + - requires_scripting exclude_files: # server-side javascript is not supported in serverless mode. - jstests/core/system_js_drop.js diff --git a/jstests/core/count.js b/jstests/core/count.js index f0ea858eca0..76b858c17b9 100644 --- a/jstests/core/count.js +++ b/jstests/core/count.js @@ -4,6 +4,8 @@ * @tags: [ * requires_fastcount, * assumes_against_mongod_not_mongos, + * # Reliance on the $where operator + * requires_scripting, * ] */ diff --git a/jstests/core/expressions_matching_whole_array.js b/jstests/core/expressions_matching_whole_array.js index 23faf87fe31..a2727e7b9d6 100644 --- a/jstests/core/expressions_matching_whole_array.js +++ b/jstests/core/expressions_matching_whole_array.js @@ -1,4 +1,10 @@ // Test MQL expressions which can match against the whole array stored in the field. +// +// @tags: [ +// # Uses $where operator +// requires_scripting +// ] + (function() { "use strict"; @@ -53,4 +59,4 @@ assertMatches({a: []}, {a: {$gt: MinKey}}); assertMatches({a: [1, 2, 3]}, {a: {$lt: MaxKey}}); assertMatches({a: []}, {a: {$lt: MaxKey}}); -}());
\ No newline at end of file +}()); diff --git a/jstests/core/js3.js b/jstests/core/js3.js index f13ce41f2ac..c9239bb4176 100644 --- a/jstests/core/js3.js +++ b/jstests/core/js3.js @@ -3,6 +3,8 @@ // requires_fastcount, // requires_javascript, // requires_non_retryable_commands, +// # Uses $where operator +// requires_scripting, // ] t = db.jstests_js3; diff --git a/jstests/core/js_global_scope.js b/jstests/core/js_global_scope.js index c5fa98963f4..d78f435acd9 100644 --- a/jstests/core/js_global_scope.js +++ b/jstests/core/js_global_scope.js @@ -8,6 +8,8 @@ // # variables in multiversion setup, so we just disable the test from multiversion suites // # instead. // multiversion_incompatible, +// # Uses $where operator +// requires_scripting, // ] (function() { "use strict"; diff --git a/src/mongo/client/dbclient_cursor.cpp b/src/mongo/client/dbclient_cursor.cpp index a19b8973db4..9ecc2ce069e 100644 --- a/src/mongo/client/dbclient_cursor.cpp +++ b/src/mongo/client/dbclient_cursor.cpp @@ -215,18 +215,14 @@ void DBClientCursor::dataReceived(const Message& reply, bool& retry, string& hos const auto replyObj = commandDataReceived(reply); _cursorId = 0; // Don't try to kill cursor if we get back an error. - // TODO SERVER-70067: pass in the tenant id to parseFromBSON. - auto cr = uassertStatusOK(CursorResponse::parseFromBSON(replyObj)); + + auto cr = uassertStatusOK(CursorResponse::parseFromBSON(replyObj, nullptr, _ns.tenantId())); _cursorId = cr.getCursorId(); uassert(50935, "Received a getMore response with a cursor id of 0 and the moreToCome flag set.", !(_connectionHasPendingReplies && _cursorId == 0)); - // TODO SERVER-70067: Get nss from the parsed cursor directly as it already has the tenant - // information. - _ns = NamespaceString( - _ns.tenantId(), // always reuse the request's tenant in case no tenant in the response. - cr.getNSS().toString()); // find command can change the ns to use for getMores. + _ns = cr.getNSS(); // find command can change the ns to use for getMores. // Store the resume token, if we got one. _postBatchResumeToken = cr.getPostBatchResumeToken(); _batch.objs = cr.releaseBatch(); diff --git a/src/mongo/client/dbclient_cursor_test.cpp b/src/mongo/client/dbclient_cursor_test.cpp index a7c1f9c5666..266307d6b70 100644 --- a/src/mongo/client/dbclient_cursor_test.cpp +++ b/src/mongo/client/dbclient_cursor_test.cpp @@ -31,6 +31,7 @@ #include "mongo/client/dbclient_connection.h" #include "mongo/client/dbclient_cursor.h" #include "mongo/db/query/cursor_response.h" +#include "mongo/idl/server_parameter_test_util.h" #include "mongo/logv2/log.h" #include "mongo/unittest/unittest.h" #include "mongo/util/assert_util.h" @@ -193,34 +194,43 @@ TEST_F(DBClientCursorTest, DBClientCursorGetMoreWithTenant) { const TenantId tenantId(OID::gen()); const NamespaceString nss(tenantId, "test", "coll"); FindCommandRequest findCmd{nss}; - DBClientCursor cursor(&conn, findCmd, ReadPreferenceSetting{}, false); - cursor.setBatchSize(2); - // Set up mock 'find' response. - const long long cursorId = 42; - Message findResponseMsg = mockFindResponse(nss, cursorId, {docObj(1), docObj(2)}); - conn.setCallResponse(findResponseMsg); + RAIIServerParameterControllerForTest multitenancyController("multitenancySupport", true); - // Trigger a find command. - ASSERT(cursor.init()); + for (bool flagStatus : {false, true}) { + RAIIServerParameterControllerForTest featureFlagController("featureFlagRequireTenantID", + flagStatus); - // First batch from the initial find command. - ASSERT_BSONOBJ_EQ(docObj(1), cursor.next()); - ASSERT_BSONOBJ_EQ(docObj(2), cursor.next()); - ASSERT_FALSE(cursor.moreInCurrentBatch()); + DBClientCursor cursor(&conn, findCmd, ReadPreferenceSetting{}, false); + cursor.setBatchSize(2); + ASSERT_EQ(cursor.getNamespaceString(), nss); - // Set a terminal getMore response with cursorId 0. - auto getMoreResponseMsg = mockGetMoreResponse(nss, 0, {docObj(3), docObj(4)}); - conn.setCallResponse(getMoreResponseMsg); + // Set up mock 'find' response. + const long long cursorId = 42; + Message findResponseMsg = mockFindResponse(nss, cursorId, {docObj(1), docObj(2)}); + conn.setCallResponse(findResponseMsg); - // Trigger a subsequent getMore command. - ASSERT_TRUE(cursor.more()); + // Trigger a find command. + ASSERT(cursor.init()); - // Second batch from the getMore command. - ASSERT_BSONOBJ_EQ(docObj(3), cursor.next()); - ASSERT_BSONOBJ_EQ(docObj(4), cursor.next()); - ASSERT_FALSE(cursor.moreInCurrentBatch()); - ASSERT_TRUE(cursor.isDead()); + // First batch from the initial find command. + ASSERT_BSONOBJ_EQ(docObj(1), cursor.next()); + ASSERT_BSONOBJ_EQ(docObj(2), cursor.next()); + ASSERT_FALSE(cursor.moreInCurrentBatch()); + + // Set a terminal getMore response with cursorId 0. + auto getMoreResponseMsg = mockGetMoreResponse(nss, 0, {docObj(3), docObj(4)}); + conn.setCallResponse(getMoreResponseMsg); + + // Trigger a subsequent getMore command. + ASSERT_TRUE(cursor.more()); + + // Second batch from the getMore command. + ASSERT_BSONOBJ_EQ(docObj(3), cursor.next()); + ASSERT_BSONOBJ_EQ(docObj(4), cursor.next()); + ASSERT_FALSE(cursor.moreInCurrentBatch()); + ASSERT_TRUE(cursor.isDead()); + } } TEST_F(DBClientCursorTest, DBClientCursorHandlesOpMsgExhaustCorrectly) { diff --git a/src/mongo/db/commands/count_cmd.cpp b/src/mongo/db/commands/count_cmd.cpp index 9b33d9b0a1a..86ceb40b506 100644 --- a/src/mongo/db/commands/count_cmd.cpp +++ b/src/mongo/db/commands/count_cmd.cpp @@ -279,7 +279,8 @@ public: BSONObj aggResult = CommandHelpers::runCommandDirectly(opCtx, aggRequest); - uassertStatusOK(ViewResponseFormatter(aggResult).appendAsCountResponse(&result)); + uassertStatusOK( + ViewResponseFormatter(aggResult).appendAsCountResponse(&result, dbName.tenantId())); return true; } diff --git a/src/mongo/db/commands/current_op.cpp b/src/mongo/db/commands/current_op.cpp index 8c8c57d885e..8ba08e6c37b 100644 --- a/src/mongo/db/commands/current_op.cpp +++ b/src/mongo/db/commands/current_op.cpp @@ -96,7 +96,8 @@ public: CommandHelpers::appendSimpleCommandStatus(bodyBuilder, true); bodyBuilder.doneFast(); - return CursorResponse::parseFromBSON(replyBuilder.releaseBody()); + return CursorResponse::parseFromBSON( + replyBuilder.releaseBody(), nullptr, request.getNamespace().tenantId()); } virtual void appendToResponse(BSONObjBuilder* result) const final { diff --git a/src/mongo/db/commands/distinct.cpp b/src/mongo/db/commands/distinct.cpp index f61ab426299..59b2305203c 100644 --- a/src/mongo/db/commands/distinct.cpp +++ b/src/mongo/db/commands/distinct.cpp @@ -273,7 +273,8 @@ public: dbName, vts, std::move(viewAggregation.getValue())); BSONObj aggResult = CommandHelpers::runCommandDirectly(opCtx, aggRequest); - uassertStatusOK(ViewResponseFormatter(aggResult).appendAsDistinctResponse(&result)); + uassertStatusOK(ViewResponseFormatter(aggResult).appendAsDistinctResponse( + &result, dbName.tenantId())); return true; } diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 328b214ae5e..3cbff434678 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -1467,7 +1467,8 @@ UsersInfoReply CmdUMCTyped<UsersInfoCommand, UMCInfoParams>::Invocation::typedRu auto bodyBuilder = replyBuilder.getBodyBuilder(); CommandHelpers::appendSimpleCommandStatus(bodyBuilder, true); bodyBuilder.doneFast(); - auto response = CursorResponse::parseFromBSONThrowing(replyBuilder.releaseBody()); + auto response = + CursorResponse::parseFromBSONThrowing(dbname.tenantId(), replyBuilder.releaseBody()); DBClientCursor cursor(&client, response.getNSS(), response.getCursorId(), diff --git a/src/mongo/db/pipeline/document_source_merge_cursors_test.cpp b/src/mongo/db/pipeline/document_source_merge_cursors_test.cpp index ac839a02a96..04bf762d340 100644 --- a/src/mongo/db/pipeline/document_source_merge_cursors_test.cpp +++ b/src/mongo/db/pipeline/document_source_merge_cursors_test.cpp @@ -47,6 +47,7 @@ #include "mongo/db/query/getmore_command_gen.h" #include "mongo/db/query/query_knobs_gen.h" #include "mongo/db/query/query_request_helper.h" +#include "mongo/db/server_feature_flags_gen.h" #include "mongo/executor/network_interface_mock.h" #include "mongo/executor/task_executor.h" #include "mongo/executor/thread_pool_task_executor_test_fixture.h" @@ -177,8 +178,8 @@ RemoteCursor makeRemoteCursor(ShardId shardId, HostAndPort host, CursorResponse } void checkSerializedAsyncResultsMergerParams(const AsyncResultsMergerParams& params, - const AsyncResultsMergerParams& serializedParam) { - static const NamespaceString testNss = NamespaceString(boost::none, kMergeCursorNsStr); + const AsyncResultsMergerParams& serializedParam, + const NamespaceString& testNss) { ASSERT_TRUE(params.getSort()); ASSERT_BSONOBJ_EQ(*params.getSort(), *serializedParam.getSort()); ASSERT_EQ(params.getCompareWholeSortKey(), serializedParam.getCompareWholeSortKey()); @@ -221,7 +222,7 @@ TEST_F(DocumentSourceMergeCursorsTest, ShouldBeAbleToParseSerializedARMParams) { ASSERT(newSpec["$mergeCursors"].type() == BSONType::Object); auto newParams = AsyncResultsMergerParams::parse(IDLParserContext("$mergeCursors test"), newSpec["$mergeCursors"].Obj()); - checkSerializedAsyncResultsMergerParams(params, newParams); + checkSerializedAsyncResultsMergerParams(params, newParams, getTenantIdNss()); // Test that the $mergeCursors stage will accept the serialized format of // AsyncResultsMergerParams. @@ -468,7 +469,7 @@ TEST_F(DocumentSourceMergeCursorsMultiTenancyTest, ShouldBeAbleToParseSerialized // Check that the namespace contains the tenantid prefix. ASSERT_EQ(newParams.toBSON()["nss"].str(), expectedTenantNsStr); ASSERT_EQ(newParams.getNss(), getTenantIdNss()); - checkSerializedAsyncResultsMergerParams(params, newParams); + checkSerializedAsyncResultsMergerParams(params, newParams, getTenantIdNss()); // Test that the $mergeCursors stage will accept the serialized format of // AsyncResultsMergerParams. @@ -522,7 +523,7 @@ TEST_F(DocumentSourceMergeCursorsMultiTenancyAndFeatureFlagTest, // Check that the namespace doesn't contain the tenantid prefix. ASSERT_EQ(newParams.toBSON()["nss"].str(), kMergeCursorNsStr); ASSERT_EQ(newParams.getNss(), getTenantIdNss()); - checkSerializedAsyncResultsMergerParams(params, newParams); + checkSerializedAsyncResultsMergerParams(params, newParams, getTenantIdNss()); // Test that the $mergeCursors stage will accept the serialized format of // AsyncResultsMergerParams. diff --git a/src/mongo/db/query/cursor_response.cpp b/src/mongo/db/query/cursor_response.cpp index 08e51ecba46..d53a24c7c5f 100644 --- a/src/mongo/db/query/cursor_response.cpp +++ b/src/mongo/db/query/cursor_response.cpp @@ -178,7 +178,8 @@ std::vector<StatusWith<CursorResponse>> CursorResponse::parseFromBSONMany( } StatusWith<CursorResponse> CursorResponse::parseFromBSON(const BSONObj& cmdResponse, - const BSONObj* ownedObj) { + const BSONObj* ownedObj, + boost::optional<TenantId> tenantId) { Status cmdStatus = getStatusFromCommandResult(cmdResponse); if (!cmdStatus.isOK()) { return cmdStatus; @@ -306,7 +307,7 @@ StatusWith<CursorResponse> CursorResponse::parseFromBSON(const BSONObj& cmdRespo << writeConcernError.type()}; } - return {{NamespaceString(fullns), + return {{NamespaceStringUtil::deserialize(tenantId, fullns), cursorId, std::move(batch), atClusterTimeElem ? atClusterTimeElem.timestamp() : boost::optional<Timestamp>{}, @@ -324,7 +325,7 @@ void CursorResponse::addToBSON(CursorResponse::ResponseType responseType, BSONObjBuilder cursorBuilder(builder->subobjStart(kCursorField)); cursorBuilder.append(kIdField, _cursorId); - cursorBuilder.append(kNsField, _nss.ns()); + cursorBuilder.append(kNsField, NamespaceStringUtil::serialize(_nss)); const char* batchFieldName = (responseType == ResponseType::InitialResponse) ? kBatchFieldInitial : kBatchField; diff --git a/src/mongo/db/query/cursor_response.h b/src/mongo/db/query/cursor_response.h index f8d69b72dc6..7b33b71e1d5 100644 --- a/src/mongo/db/query/cursor_response.h +++ b/src/mongo/db/query/cursor_response.h @@ -186,14 +186,17 @@ public: * Constructs a CursorResponse from the command BSON response. If 'cmdResponse' is not owned, * the second argument should be the object that owns the response. */ - static StatusWith<CursorResponse> parseFromBSON(const BSONObj& cmdResponse, - const BSONObj* ownedObj = nullptr); + static StatusWith<CursorResponse> parseFromBSON( + const BSONObj& cmdResponse, + const BSONObj* ownedObj = nullptr, + boost::optional<TenantId> tenantId = boost::none); /** * A throwing version of 'parseFromBSON'. */ - static CursorResponse parseFromBSONThrowing(const BSONObj& cmdResponse) { - return uassertStatusOK(parseFromBSON(cmdResponse)); + static CursorResponse parseFromBSONThrowing(boost::optional<TenantId> tenantId, + const BSONObj& cmdResponse) { + return uassertStatusOK(parseFromBSON(cmdResponse, nullptr, tenantId)); } /** diff --git a/src/mongo/db/query/cursor_response_test.cpp b/src/mongo/db/query/cursor_response_test.cpp index abd6d38bbd9..23a513a177a 100644 --- a/src/mongo/db/query/cursor_response_test.cpp +++ b/src/mongo/db/query/cursor_response_test.cpp @@ -34,6 +34,7 @@ #include "mongo/rpc/op_msg_rpc_impls.h" #include "mongo/db/pipeline/resume_token.h" +#include "mongo/idl/server_parameter_test_util.h" #include "mongo/unittest/unittest.h" namespace mongo { @@ -306,6 +307,70 @@ TEST(CursorResponseTest, roundTripThroughCursorResponseBuilderWithPartialResults ASSERT(!cursorBuilderIt.more()); } +TEST(CursorResponseTest, + roundTripThroughCursorResponseBuilderWithPartialResultsReturnedWithTenantId) { + CursorResponseBuilder::Options options; + options.isInitialResponse = true; + TenantId tid(OID::gen()); + NamespaceString nss(tid, "db.coll"); + + RAIIServerParameterControllerForTest multitenancyController("multitenancySupport", true); + + for (bool flagStatus : {false, true}) { + RAIIServerParameterControllerForTest featureFlagController("featureFlagRequireTenantID", + flagStatus); + + rpc::OpMsgReplyBuilder builder; + BSONObj okStatus = BSON("ok" << 1); + BSONObj testDoc = BSON("_id" << 1); + BSONObj expectedBody = + BSON("cursor" << BSON("firstBatch" << BSON_ARRAY(testDoc) << "partialResultsReturned" + << true << "id" << CursorId(123) << "ns" + << NamespaceStringUtil::serialize(nss))); + + // Use CursorResponseBuilder to serialize the cursor response to OpMsgReplyBuilder. + CursorResponseBuilder crb(&builder, options); + crb.append(testDoc); + crb.setPartialResultsReturned(true); + crb.done(CursorId(123), nss); + + // Confirm that the resulting BSONObj response matches the expected body. + auto msg = builder.done(); + auto opMsg = OpMsg::parse(msg); + ASSERT_BSONOBJ_EQ(expectedBody, opMsg.body); + + // Append {"ok": 1} to the opMsg body so that it can be parsed by CursorResponse. + auto swCursorResponse = + CursorResponse::parseFromBSON(opMsg.body.addField(okStatus["ok"]), nullptr, tid); + ASSERT_OK(swCursorResponse.getStatus()); + + // Confirm the CursorReponse parsed from CursorResponseBuilder output has the correct + // content. + CursorResponse response = std::move(swCursorResponse.getValue()); + ASSERT_EQ(response.getCursorId(), CursorId(123)); + + ASSERT_EQ(response.getNSS(), nss); + ASSERT_EQ(response.getBatch().size(), 1U); + ASSERT_BSONOBJ_EQ(response.getBatch()[0], testDoc); + ASSERT_EQ(response.getPartialResultsReturned(), true); + + // Re-serialize a BSONObj response from the CursorResponse. + auto cursorResBSON = response.toBSONAsInitialResponse(); + + // Confirm that the BSON serialized by the CursorResponse is the same as that serialized by + // the CursorResponseBuilder. Field ordering differs between the two, so compare + // per-element. + BSONObjIteratorSorted cursorResIt(cursorResBSON["cursor"].Obj()); + BSONObjIteratorSorted cursorBuilderIt(opMsg.body["cursor"].Obj()); + while (cursorResIt.more()) { + ASSERT(cursorBuilderIt.more()); + ASSERT_EQ(cursorResIt.next().woCompare(cursorBuilderIt.next()), 0); + } + ASSERT(!cursorBuilderIt.more()); + } +} + + TEST(CursorResponseTest, parseFromBSONHandleErrorResponse) { StatusWith<CursorResponse> result = CursorResponse::parseFromBSON(BSON("ok" << 0 << "code" << 123 << "errmsg" @@ -392,6 +457,32 @@ TEST(CursorResponseTest, addToBSONSubsequentResponse) { ASSERT_BSONOBJ_EQ(responseObj, expectedResponse); } +TEST(CursorResponseTest, addToBSONInitialResponseWithTenantId) { + TenantId tid(OID::gen()); + NamespaceString nss(tid, "testdb.testcoll"); + + RAIIServerParameterControllerForTest multitenancyController("multitenancySupport", true); + + for (bool flagStatus : {false, true}) { + RAIIServerParameterControllerForTest featureFlagController("featureFlagRequireTenantID", + flagStatus); + + std::vector<BSONObj> batch = {BSON("_id" << 1), BSON("_id" << 2)}; + CursorResponse response(nss, CursorId(123), batch); + + BSONObjBuilder builder; + response.addToBSON(CursorResponse::ResponseType::InitialResponse, &builder); + BSONObj responseObj = builder.obj(); + + BSONObj expectedResponse = + BSON("cursor" << BSON("id" << CursorId(123) << "ns" + << NamespaceStringUtil::serialize(nss) << "firstBatch" + << BSON_ARRAY(BSON("_id" << 1) << BSON("_id" << 2))) + << "ok" << 1.0); + ASSERT_BSONOBJ_EQ(responseObj, expectedResponse); + } +} + TEST(CursorResponseTest, serializePostBatchResumeToken) { std::vector<BSONObj> batch = {BSON("_id" << 1), BSON("_id" << 2)}; auto postBatchResumeToken = diff --git a/src/mongo/db/query/view_response_formatter.cpp b/src/mongo/db/query/view_response_formatter.cpp index 76c8457c8b5..d169aa61c74 100644 --- a/src/mongo/db/query/view_response_formatter.cpp +++ b/src/mongo/db/query/view_response_formatter.cpp @@ -45,8 +45,9 @@ const char ViewResponseFormatter::kOkField[] = "ok"; ViewResponseFormatter::ViewResponseFormatter(BSONObj aggregationResponse) : _response(std::move(aggregationResponse)) {} -Status ViewResponseFormatter::appendAsCountResponse(BSONObjBuilder* resultBuilder) { - auto cursorResponse = CursorResponse::parseFromBSON(_response); +Status ViewResponseFormatter::appendAsCountResponse(BSONObjBuilder* resultBuilder, + boost::optional<TenantId> tenantId) { + auto cursorResponse = CursorResponse::parseFromBSON(_response, nullptr, tenantId); if (!cursorResponse.isOK()) return cursorResponse.getStatus(); @@ -63,8 +64,9 @@ Status ViewResponseFormatter::appendAsCountResponse(BSONObjBuilder* resultBuilde return Status::OK(); } -Status ViewResponseFormatter::appendAsDistinctResponse(BSONObjBuilder* resultBuilder) { - auto cursorResponse = CursorResponse::parseFromBSON(_response); +Status ViewResponseFormatter::appendAsDistinctResponse(BSONObjBuilder* resultBuilder, + boost::optional<TenantId> tenantId) { + auto cursorResponse = CursorResponse::parseFromBSON(_response, nullptr, tenantId); if (!cursorResponse.isOK()) return cursorResponse.getStatus(); diff --git a/src/mongo/db/query/view_response_formatter.h b/src/mongo/db/query/view_response_formatter.h index 20473e3bbc9..5146efd5229 100644 --- a/src/mongo/db/query/view_response_formatter.h +++ b/src/mongo/db/query/view_response_formatter.h @@ -30,6 +30,7 @@ #pragma once #include "mongo/bson/bsonobj.h" +#include "mongo/db/tenant_id.h" namespace mongo { class BSONObjBuilder; @@ -53,7 +54,7 @@ public: * If '_response' is not a valid cursor-based response from the aggregation command, a non-OK * status is returned and 'resultBuilder' will not be modified. */ - Status appendAsCountResponse(BSONObjBuilder* resultBuilder); + Status appendAsCountResponse(BSONObjBuilder* resultBuilder, boost::optional<TenantId> tenantId); /** * Appends fields to 'resultBuilder' as if '_response' were a response from the distinct @@ -62,7 +63,8 @@ public: * If '_response' is not a valid cursor-based response from the aggregation command, a non-OK * status is returned and 'resultBuilder' will not be modified. */ - Status appendAsDistinctResponse(BSONObjBuilder* resultBuilder); + Status appendAsDistinctResponse(BSONObjBuilder* resultBuilder, + boost::optional<TenantId> tenantId); private: BSONObj _response; diff --git a/src/mongo/db/query/view_response_formatter_test.cpp b/src/mongo/db/query/view_response_formatter_test.cpp index dc86c5c9fc7..274bcda50f1 100644 --- a/src/mongo/db/query/view_response_formatter_test.cpp +++ b/src/mongo/db/query/view_response_formatter_test.cpp @@ -35,6 +35,7 @@ #include "mongo/db/namespace_string.h" #include "mongo/db/query/cursor_response.h" #include "mongo/db/query/view_response_formatter.h" +#include "mongo/idl/server_parameter_test_util.h" #include "mongo/unittest/unittest.h" namespace mongo { @@ -47,7 +48,7 @@ TEST(ViewResponseFormatter, FormatInitialCountResponseSuccessfully) { CursorResponse cr(testNss, testCursor, {BSON("count" << 7)}); ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::InitialResponse)); BSONObjBuilder builder; - ASSERT_OK(formatter.appendAsCountResponse(&builder)); + ASSERT_OK(formatter.appendAsCountResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(fromjson("{'n': 7, ok: 1}"), builder.obj()); } @@ -55,15 +56,33 @@ TEST(ViewResponseFormatter, FormatSubsequentCountResponseSuccessfully) { CursorResponse cr(testNss, testCursor, {BSON("count" << 7)}); ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::SubsequentResponse)); BSONObjBuilder builder; - ASSERT_OK(formatter.appendAsCountResponse(&builder)); + ASSERT_OK(formatter.appendAsCountResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(fromjson("{'n': 7, ok: 1}"), builder.obj()); } +TEST(ViewResponseFormatter, FormatInitialCountResponseWithTenantIdSuccessfully) { + const TenantId tenantId(OID::gen()); + const NamespaceString nss(tenantId, testNss.toString()); + + RAIIServerParameterControllerForTest multitenancyController("multitenancySupport", true); + + for (bool flagStatus : {false, true}) { + RAIIServerParameterControllerForTest featureFlagController("featureFlagRequireTenantID", + flagStatus); + + CursorResponse cr(nss, testCursor, {BSON("count" << 7)}); + ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::InitialResponse)); + BSONObjBuilder builder; + ASSERT_OK(formatter.appendAsCountResponse(&builder, tenantId)); + ASSERT_BSONOBJ_EQ(fromjson("{'n': 7, ok: 1}"), builder.obj()); + } +} + TEST(ViewResponseFormatter, FormatEmptyInitialCountResponseSuccessfully) { CursorResponse cr(testNss, testCursor, {}); ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::InitialResponse)); BSONObjBuilder builder; - ASSERT_OK(formatter.appendAsCountResponse(&builder)); + ASSERT_OK(formatter.appendAsCountResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(fromjson("{'n': 0, ok: 1}"), builder.obj()); } @@ -71,14 +90,14 @@ TEST(ViewResponseFormatter, FormatEmptySubsequentCountResponseSuccessfully) { CursorResponse cr(testNss, testCursor, {}); ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::SubsequentResponse)); BSONObjBuilder builder; - ASSERT_OK(formatter.appendAsCountResponse(&builder)); + ASSERT_OK(formatter.appendAsCountResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(fromjson("{'n': 0, ok: 1}"), builder.obj()); } TEST(ViewResponseFormatter, FormatFailedCountResponseFails) { ViewResponseFormatter formatter(fromjson("{ok: 0, errmsg: 'bad value', code: 2}")); BSONObjBuilder builder; - ASSERT_NOT_OK(formatter.appendAsCountResponse(&builder)); + ASSERT_NOT_OK(formatter.appendAsCountResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(builder.obj(), BSONObj()); } @@ -86,7 +105,7 @@ TEST(ViewResponseFormatter, FormatInitialDistinctResponseSuccessfully) { CursorResponse cr(testNss, testCursor, {fromjson("{_id: null, distinct: [5, 9]}")}); ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::InitialResponse)); BSONObjBuilder builder; - ASSERT_OK(formatter.appendAsDistinctResponse(&builder)); + ASSERT_OK(formatter.appendAsDistinctResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(fromjson("{values: [5, 9], ok: 1}"), builder.obj()); } @@ -94,15 +113,33 @@ TEST(ViewResponseFormatter, FormatSubsequentDistinctResponseSuccessfully) { CursorResponse cr(testNss, testCursor, {fromjson("{_id: null, distinct: [5, 9]}")}); ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::SubsequentResponse)); BSONObjBuilder builder; - ASSERT_OK(formatter.appendAsDistinctResponse(&builder)); + ASSERT_OK(formatter.appendAsDistinctResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(fromjson("{values: [5, 9], ok: 1}"), builder.obj()); } +TEST(ViewResponseFormatter, FormatInitialDistinctResponseWithTenantIdSuccessfully) { + const TenantId tenantId(OID::gen()); + const NamespaceString nss(tenantId, testNss.toString()); + + RAIIServerParameterControllerForTest multitenancyController("multitenancySupport", true); + + for (bool flagStatus : {false, true}) { + RAIIServerParameterControllerForTest featureFlagController("featureFlagRequireTenantID", + flagStatus); + + CursorResponse cr(nss, testCursor, {fromjson("{_id: null, distinct: [5, 9]}")}); + ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::InitialResponse)); + BSONObjBuilder builder; + ASSERT_OK(formatter.appendAsDistinctResponse(&builder, tenantId)); + ASSERT_BSONOBJ_EQ(fromjson("{values: [5, 9], ok: 1}"), builder.obj()); + } +} + TEST(ViewResponseFormatter, FormatEmptyDistinctValuesSuccessfully) { CursorResponse cr(testNss, testCursor, {fromjson("{_id: null, distinct: []}")}); ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::InitialResponse)); BSONObjBuilder builder; - ASSERT_OK(formatter.appendAsDistinctResponse(&builder)); + ASSERT_OK(formatter.appendAsDistinctResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(fromjson("{values: [], ok: 1}"), builder.obj()); } @@ -110,14 +147,14 @@ TEST(ViewResponseFormatter, FormatEmptyDistinctBatchSuccessfully) { CursorResponse cr(testNss, testCursor, {}); ViewResponseFormatter formatter(cr.toBSON(CursorResponse::ResponseType::InitialResponse)); BSONObjBuilder builder; - ASSERT_OK(formatter.appendAsDistinctResponse(&builder)); + ASSERT_OK(formatter.appendAsDistinctResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(fromjson("{values: [], ok: 1}"), builder.obj()); } TEST(ViewResponseFormatter, FormatFailedDistinctResponseFails) { ViewResponseFormatter formatter(fromjson("{ok: 0, errmsg: 'bad value', code: 2}")); BSONObjBuilder builder; - ASSERT_NOT_OK(formatter.appendAsDistinctResponse(&builder)); + ASSERT_NOT_OK(formatter.appendAsDistinctResponse(&builder, boost::none)); ASSERT_BSONOBJ_EQ(builder.obj(), BSONObj()); } diff --git a/src/mongo/db/transaction/transaction_api.cpp b/src/mongo/db/transaction/transaction_api.cpp index c778d81f97c..9dd92deb5d3 100644 --- a/src/mongo/db/transaction/transaction_api.cpp +++ b/src/mongo/db/transaction/transaction_api.cpp @@ -425,11 +425,13 @@ SemiFuture<std::vector<BSONObj>> SEPTransactionClient::exhaustiveFind( const FindCommandRequest& cmd) const { return runCommand(cmd.getDbName().db(), cmd.toBSON({})) .thenRunOn(_executor) - .then([this, batchSize = cmd.getBatchSize()](BSONObj reply) { + .then([this, batchSize = cmd.getBatchSize(), tenantId = cmd.getDbName().tenantId()]( + BSONObj reply) { auto cursorResponse = std::make_shared<CursorResponse>( - uassertStatusOK(CursorResponse::parseFromBSON(reply))); + uassertStatusOK(CursorResponse::parseFromBSON(reply, nullptr, tenantId))); auto response = std::make_shared<std::vector<BSONObj>>(); return AsyncTry([this, + tenantId, batchSize = batchSize, cursorResponse = std::move(cursorResponse), response]() mutable { @@ -450,11 +452,11 @@ SemiFuture<std::vector<BSONObj>> SEPTransactionClient::exhaustiveFind( return runCommand(cursorResponse->getNSS().db(), getMoreRequest.toBSON({})) .thenRunOn(_executor) - .then([response, cursorResponse](BSONObj reply) { + .then([response, cursorResponse, tenantId](BSONObj reply) { // We keep the state of cursorResponse to be able to check the // cursorId in the next iteration. - *cursorResponse = - uassertStatusOK(CursorResponse::parseFromBSON(reply)); + *cursorResponse = uassertStatusOK( + CursorResponse::parseFromBSON(reply, nullptr, tenantId)); uasserted(ErrorCodes::InternalTransactionsExhaustiveFindHasMore, "More documents to fetch"); }) diff --git a/src/mongo/s/commands/cluster_count_cmd.h b/src/mongo/s/commands/cluster_count_cmd.h index 565740d14aa..263fbe2dfc1 100644 --- a/src/mongo/s/commands/cluster_count_cmd.h +++ b/src/mongo/s/commands/cluster_count_cmd.h @@ -169,7 +169,7 @@ public: result.resetToEmpty(); ViewResponseFormatter formatter(aggResult); - auto formatStatus = formatter.appendAsCountResponse(&result); + auto formatStatus = formatter.appendAsCountResponse(&result, boost::none); uassertStatusOK(formatStatus); return true; diff --git a/src/mongo/s/commands/cluster_distinct_cmd.cpp b/src/mongo/s/commands/cluster_distinct_cmd.cpp index 5df1cd7989f..c917ca00421 100644 --- a/src/mongo/s/commands/cluster_distinct_cmd.cpp +++ b/src/mongo/s/commands/cluster_distinct_cmd.cpp @@ -263,7 +263,7 @@ public: opCtx, OpMsgRequest::fromDBAndBody(dbName.db(), std::move(resolvedAggCmd))); ViewResponseFormatter formatter(aggResult); - auto formatStatus = formatter.appendAsDistinctResponse(&result); + auto formatStatus = formatter.appendAsDistinctResponse(&result, boost::none); uassertStatusOK(formatStatus); return true; diff --git a/src/mongo/s/query/async_results_merger_params.idl b/src/mongo/s/query/async_results_merger_params.idl index 1a1371fe0d4..fcd27fd8528 100644 --- a/src/mongo/s/query/async_results_merger_params.idl +++ b/src/mongo/s/query/async_results_merger_params.idl @@ -46,6 +46,7 @@ types: cpp_type: CursorResponse serializer: CursorResponse::toBSONAsInitialResponse deserializer: CursorResponse::parseFromBSONThrowing + deserialize_with_tenant: true structs: RemoteCursor: |