diff options
author | jannaerin <golden.janna@gmail.com> | 2022-11-14 17:20:04 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-11-15 19:13:40 +0000 |
commit | 919b31026d9d3c629bfd5f29556e3a532395dc62 (patch) | |
tree | 5278ae2d4f46d540b33cbf09213a7b3eb54bfa0e | |
parent | e94c11f55f5e5eb7f764523359fa908c8efef68c (diff) | |
download | mongo-919b31026d9d3c629bfd5f29556e3a532395dc62.tar.gz |
SERVER-70415 Attach tenantId to dbStats during initial sync
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 11 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_impl.cpp | 33 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_impl.h | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state.h | 17 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.cpp | 46 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.h | 5 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.h | 10 | ||||
-rw-r--r-- | src/mongo/db/repl/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/repl/all_database_cloner.cpp | 124 | ||||
-rw-r--r-- | src/mongo/db/repl/all_database_cloner.h | 4 | ||||
-rw-r--r-- | src/mongo/db/repl/all_database_cloner_test.cpp | 293 | ||||
-rw-r--r-- | src/mongo/db/repl/storage_interface.h | 5 | ||||
-rw-r--r-- | src/mongo/db/repl/storage_interface_impl.cpp | 58 | ||||
-rw-r--r-- | src/mongo/db/repl/storage_interface_impl.h | 5 | ||||
-rw-r--r-- | src/mongo/db/repl/storage_interface_mock.h | 9 | ||||
-rw-r--r-- | src/mongo/embedded/embedded_auth_manager.cpp | 8 |
16 files changed, 498 insertions, 136 deletions
diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 69077aebe6a..a23905406da 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -187,6 +187,11 @@ public: virtual bool isAuthEnabled() const = 0; /** + * Returns whether a schema version document exists. + */ + virtual Status hasValidAuthSchemaVersionDocumentForInitialSync(OperationContext* opCtx) = 0; + + /** * Returns via the output parameter "version" the version number of the authorization * system. Returns Status::OK() if it was able to successfully fetch the current * authorization version. If it has problems fetching the most up to date version it @@ -223,6 +228,12 @@ public: BSONObj* result) = 0; /** + * Returns true if there exists at least one user document in the system. If `tenantId` is set, + * only looks for users associated with `tenantId`. + */ + virtual bool hasUser(OperationContext* opCtx, const boost::optional<TenantId>& tenantId) = 0; + + /** * Delegates method call to the underlying AuthzManagerExternalState. */ virtual Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) = 0; diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp index db1081b8bf3..6b95652f6bb 100644 --- a/src/mongo/db/auth/authorization_manager_impl.cpp +++ b/src/mongo/db/auth/authorization_manager_impl.cpp @@ -449,6 +449,39 @@ Status AuthorizationManagerImpl::getUserDescription(OperationContext* opCtx, return _externalState->getUserDescription(opCtx, UserRequest(userName, boost::none), result); } +Status AuthorizationManagerImpl::hasValidAuthSchemaVersionDocumentForInitialSync( + OperationContext* opCtx) { + BSONObj foundDoc; + auto status = _externalState->hasValidStoredAuthorizationVersion(opCtx, &foundDoc); + + if (status == ErrorCodes::NoSuchKey || status == ErrorCodes::TypeMismatch) { + std::string msg = str::stream() + << "During initial sync, found malformed auth schema version document: " + << status.toString() << "; document: " << foundDoc; + return Status(ErrorCodes::AuthSchemaIncompatible, msg); + } + + if (status.isOK()) { + auto version = foundDoc.getIntField(AuthorizationManager::schemaVersionFieldName); + if ((version != AuthorizationManager::schemaVersion26Final) && + (version != AuthorizationManager::schemaVersion28SCRAM)) { + std::string msg = str::stream() + << "During initial sync, found auth schema version " << version + << ", but this version of MongoDB only supports schema versions " + << AuthorizationManager::schemaVersion26Final << " and " + << AuthorizationManager::schemaVersion28SCRAM; + return {ErrorCodes::AuthSchemaIncompatible, msg}; + } + } + + return status; +} + +bool AuthorizationManagerImpl::hasUser(OperationContext* opCtx, + const boost::optional<TenantId>& tenantId) { + return _externalState->hasAnyUserDocuments(opCtx, tenantId).isOK(); +} + Status AuthorizationManagerImpl::rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) { return _externalState->rolesExist(opCtx, roleNames); diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h index fcec7b97e32..0cb99f041f9 100644 --- a/src/mongo/db/auth/authorization_manager_impl.h +++ b/src/mongo/db/auth/authorization_manager_impl.h @@ -67,12 +67,16 @@ public: OID getCacheGeneration() override; + Status hasValidAuthSchemaVersionDocumentForInitialSync(OperationContext* opCtx) override; + bool hasAnyPrivilegeDocuments(OperationContext* opCtx) override; Status getUserDescription(OperationContext* opCtx, const UserName& userName, BSONObj* result) override; + bool hasUser(OperationContext* opCtx, const boost::optional<TenantId>& tenantId) override; + Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) override; StatusWith<ResolvedRoleData> resolveRoles(OperationContext* opCtx, diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h index 96cf71257e0..ad1442361ed 100644 --- a/src/mongo/db/auth/authz_manager_external_state.h +++ b/src/mongo/db/auth/authz_manager_external_state.h @@ -84,6 +84,13 @@ public: * Retrieves the schema version of the persistent data describing users and roles. * Will leave *outVersion unmodified on non-OK status return values. */ + virtual Status hasValidStoredAuthorizationVersion(OperationContext* opCtx, + BSONObj* foundVersionDoc) = 0; + + /** + * Retrieves the schema version of the persistent data describing users and roles. + * Modifies *outVersion if status is NoMatchingDocument. + */ virtual Status getStoredAuthorizationVersion(OperationContext* opCtx, int* outVersion) = 0; /** @@ -169,7 +176,15 @@ public: std::vector<BSONObj>* result) = 0; /** - * Returns true if there exists at least one privilege document in the system. + * Returns true if there exists at least one user document in the system. If `tenantId` is + * set, checks whether a doc associated with this tenantId exists. + */ + virtual Status hasAnyUserDocuments(OperationContext* opCtx, + const boost::optional<TenantId>& tenantId) = 0; + + /** + * Returns true if there exists at least one privilege document in the system. If `tenantId` is + * set, checks whether a doc associated with this tenantId exists. */ virtual bool hasAnyPrivilegeDocuments(OperationContext* opCtx) = 0; diff --git a/src/mongo/db/auth/authz_manager_external_state_local.cpp b/src/mongo/db/auth/authz_manager_external_state_local.cpp index 9f98e5f7032..39d6da595f3 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp @@ -58,17 +58,16 @@ namespace mongo { using std::vector; using ResolveRoleOption = AuthzManagerExternalStateLocal::ResolveRoleOption; -Status AuthzManagerExternalStateLocal::getStoredAuthorizationVersion(OperationContext* opCtx, - int* outVersion) { - BSONObj versionDoc; +Status AuthzManagerExternalStateLocal::hasValidStoredAuthorizationVersion( + OperationContext* opCtx, BSONObj* foundVersionDoc) { Status status = findOne(opCtx, AuthorizationManager::versionCollectionNamespace, AuthorizationManager::versionDocumentQuery, - &versionDoc); + foundVersionDoc); if (status.isOK()) { - BSONElement versionElement = versionDoc[AuthorizationManager::schemaVersionFieldName]; + BSONElement versionElement = + (*foundVersionDoc)[AuthorizationManager::schemaVersionFieldName]; if (versionElement.isNumber()) { - *outVersion = versionElement.numberInt(); return Status::OK(); } else if (versionElement.eoo()) { return Status(ErrorCodes::NoSuchKey, @@ -83,12 +82,24 @@ Status AuthzManagerExternalStateLocal::getStoredAuthorizationVersion(OperationCo << ") for " << AuthorizationManager::schemaVersionFieldName << " field in version document"); } + } else { + return status; + } +} + +Status AuthzManagerExternalStateLocal::getStoredAuthorizationVersion(OperationContext* opCtx, + int* outVersion) { + BSONObj foundVersionDoc; + auto status = hasValidStoredAuthorizationVersion(opCtx, &foundVersionDoc); + if (status.isOK()) { + *outVersion = foundVersionDoc.getIntField(AuthorizationManager::schemaVersionFieldName); + return status; } else if (status == ErrorCodes::NoMatchingDocument) { *outVersion = AuthorizationManager::schemaVersion28SCRAM; return Status::OK(); - } else { - return status; } + + return status; } namespace { @@ -239,16 +250,23 @@ void handleAuthLocalGetUserFailPoint(const std::vector<RoleName>& directRoles) { } } // namespace -// We ignore tenant-specific collections here, since hasAnyPrivilegeDocuments -// only impacts localhost auth bypass which by definition will be a local user. +Status AuthzManagerExternalStateLocal::hasAnyUserDocuments( + OperationContext* opCtx, const boost::optional<TenantId>& tenantId) { + BSONObj userBSONObj; + return findOne(opCtx, + NamespaceString(tenantId, AuthorizationManager::usersCollectionNamespace.ns()), + BSONObj(), + &userBSONObj); +} + +// If tenantId is none, we're checking whether to enable localhost auth bypass which by definition +// will be a local user. bool AuthzManagerExternalStateLocal::hasAnyPrivilegeDocuments(OperationContext* opCtx) { if (_hasAnyPrivilegeDocuments.load()) { return true; } - BSONObj userBSONObj; - Status statusFindUsers = - findOne(opCtx, AuthorizationManager::usersCollectionNamespace, BSONObj(), &userBSONObj); + Status statusFindUsers = hasAnyUserDocuments(opCtx, boost::none); // If we were unable to complete the query, // it's best to assume that there _are_ privilege documents. @@ -256,6 +274,8 @@ bool AuthzManagerExternalStateLocal::hasAnyPrivilegeDocuments(OperationContext* _hasAnyPrivilegeDocuments.store(true); return true; } + + BSONObj userBSONObj; Status statusFindRoles = findOne(opCtx, AuthorizationManager::rolesCollectionNamespace, BSONObj(), &userBSONObj); if (statusFindRoles != ErrorCodes::NoMatchingDocument) { diff --git a/src/mongo/db/auth/authz_manager_external_state_local.h b/src/mongo/db/auth/authz_manager_external_state_local.h index afaf6732b17..c93641176d2 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.h +++ b/src/mongo/db/auth/authz_manager_external_state_local.h @@ -54,6 +54,8 @@ class AuthzManagerExternalStateLocal : public AuthzManagerExternalState { public: virtual ~AuthzManagerExternalStateLocal() = default; + Status hasValidStoredAuthorizationVersion(OperationContext* opCtx, + BSONObj* foundVersionDoc) override; Status getStoredAuthorizationVersion(OperationContext* opCtx, int* outVersion) override; StatusWith<User> getUserObject(OperationContext* opCtx, const UserRequest& userReq) override; Status getUserDescription(OperationContext* opCtx, @@ -79,6 +81,9 @@ public: bool showBuiltinRoles, std::vector<BSONObj>* result) override; + Status hasAnyUserDocuments(OperationContext* opCtx, + const boost::optional<TenantId>& tenantId) final; + bool hasAnyPrivilegeDocuments(OperationContext* opCtx) final; /** diff --git a/src/mongo/db/auth/authz_manager_external_state_s.h b/src/mongo/db/auth/authz_manager_external_state_s.h index a1ac0feee41..5789b464337 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.h +++ b/src/mongo/db/auth/authz_manager_external_state_s.h @@ -53,6 +53,11 @@ public: std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( AuthorizationManager* authzManager) final; + + Status hasValidStoredAuthorizationVersion(OperationContext* opCtx, + BSONObj* foundVersionDoc) override { + return {ErrorCodes::NotImplemented, "AuthzMongos::hasValidStoredAuthorizationVersion"}; + } Status getStoredAuthorizationVersion(OperationContext* opCtx, int* outVersion) override; Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) final; StatusWith<User> getUserObject(OperationContext* opCtx, const UserRequest& userReq) final; @@ -88,6 +93,11 @@ public: } bool hasAnyPrivilegeDocuments(OperationContext* opCtx) final; + + Status hasAnyUserDocuments(OperationContext* opCtx, + const boost::optional<TenantId>& tenantId) final { + return {ErrorCodes::NotImplemented, "AuthzMongos::hasValidStoredAuthorizationVersion"}; + } }; } // namespace mongo diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index ad8be47af0a..b293c042111 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -1046,6 +1046,7 @@ env.Library( LIBDEPS=[ '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/client/clientdriver_network', + '$BUILD_DIR/mongo/db/multitenancy', '$BUILD_DIR/mongo/db/server_feature_flags', '$BUILD_DIR/mongo/util/concurrency/thread_pool', '$BUILD_DIR/mongo/util/net/network', @@ -1061,6 +1062,7 @@ env.Library( '$BUILD_DIR/mongo/db/commands/list_collections_filter', '$BUILD_DIR/mongo/db/index_build_entry_helpers', '$BUILD_DIR/mongo/db/index_builds_coordinator_interface', + '$BUILD_DIR/mongo/util/namespace_string_database_name_util', '$BUILD_DIR/mongo/util/progress_meter', 'repl_server_parameters', 'replication_auth', diff --git a/src/mongo/db/repl/all_database_cloner.cpp b/src/mongo/db/repl/all_database_cloner.cpp index 014dbc81cef..9472d320052 100644 --- a/src/mongo/db/repl/all_database_cloner.cpp +++ b/src/mongo/db/repl/all_database_cloner.cpp @@ -33,6 +33,7 @@ #include <algorithm> #include "mongo/base/string_data.h" +#include "mongo/db/auth/authorization_manager.h" #include "mongo/db/multitenancy_gen.h" #include "mongo/db/repl/all_database_cloner.h" #include "mongo/db/repl/replication_consistency_markers_gen.h" @@ -41,6 +42,7 @@ #include "mongo/logv2/log.h" #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/util/assert_util.h" +#include "mongo/util/database_name_util.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kReplicationInitialSync @@ -143,16 +145,17 @@ BaseCloner::AfterStageBehavior AllDatabaseCloner::getInitialSyncIdStage() { BaseCloner::AfterStageBehavior AllDatabaseCloner::listDatabasesStage() { std::vector<mongo::BSONObj> databasesArray; - if (gMultitenancySupport && serverGlobalParams.featureCompatibility.isVersionInitialized() && - gFeatureFlagRequireTenantID.isEnabled(serverGlobalParams.featureCompatibility)) { - databasesArray = getClient()->getDatabaseInfos(BSONObj(), - true /* nameOnly */, - false /*authorizedDatabases*/, - true /*useListDatabsesForAllTenants*/); - } else { - databasesArray = getClient()->getDatabaseInfos(BSONObj(), true /* nameOnly */); - } + const bool multiTenancyAndRequireTenantIdEnabled = gMultitenancySupport && + serverGlobalParams.featureCompatibility.isVersionInitialized() && + gFeatureFlagRequireTenantID.isEnabled(serverGlobalParams.featureCompatibility); + + databasesArray = getClient()->getDatabaseInfos( + BSONObj(), + true /* nameOnly */, + false /*authorizedDatabases*/, + multiTenancyAndRequireTenantIdEnabled /*useListDatabsesForAllTenants*/); + size_t idxToInsertNextAdmin = 0; for (const auto& dbBSON : databasesArray) { if (!dbBSON.hasField("name")) { LOGV2_DEBUG(21055, @@ -164,8 +167,13 @@ BaseCloner::AfterStageBehavior AllDatabaseCloner::listDatabasesStage() { "db"_attr = dbBSON); continue; } - const auto& dbName = dbBSON["name"].str(); - if (dbName == "local") { + + boost::optional<TenantId> tenantId = dbBSON.hasField("tenantId") + ? boost::make_optional<TenantId>(TenantId::parseFromBSON(dbBSON["tenantId"])) + : boost::none; + DatabaseName dbName = DatabaseNameUtil::deserialize(tenantId, dbBSON["name"].str()); + + if (dbName.db() == "local") { LOGV2_DEBUG(21056, 1, "Excluding database from the 'listDatabases' response: {db}", @@ -174,15 +182,37 @@ BaseCloner::AfterStageBehavior AllDatabaseCloner::listDatabasesStage() { continue; } else { _databases.emplace_back(dbName); - // Make sure "admin" comes first. - if (dbName == "admin" && _databases.size() > 1) { - std::swap(_databases.front(), _databases.back()); + + // Put admin dbs in the front of the vector. + if (dbName.db() == "admin" && _databases.size() > 1) { + std::iter_swap(_databases.begin() + idxToInsertNextAdmin, _databases.end() - 1); + idxToInsertNextAdmin++; } } } + + // Ensure the global admin comes first. We inserted all admin dbs at the front of '_databases', + // find the global admin and move it to the front. + for (auto i = _databases.begin(); size_t(i - _databases.begin()) != idxToInsertNextAdmin; ++i) { + if (!(*i).tenantId()) { + std::iter_swap(_databases.begin(), i); + break; + } + } + + return kContinueNormally; } +void AllDatabaseCloner::handleAdminDbNotValid(const Status& errorStatus) { + LOGV2_DEBUG(21059, + 1, + "Validation failed on 'admin' db due to {error}", + "Validation failed on 'admin' db", + "error"_attr = errorStatus); + setSyncFailedStatus(errorStatus); +} + void AllDatabaseCloner::postStage() { { stdx::lock_guard<Latch> lk(_mutex); @@ -191,10 +221,22 @@ void AllDatabaseCloner::postStage() { _stats.databaseStats.reserve(_databases.size()); for (const auto& dbName : _databases) { _stats.databaseStats.emplace_back(); - _stats.databaseStats.back().dbname = dbName; + _stats.databaseStats.back().dbname = dbName.toStringWithTenantId(); + + auto db = DatabaseNameUtil::serialize(dbName); + + BSONObj cmdObj = BSON("dbStats" << 1); + BSONObjBuilder b(cmdObj); + if (gMultitenancySupport && + serverGlobalParams.featureCompatibility.isVersionInitialized() && + gFeatureFlagRequireTenantID.isEnabled(serverGlobalParams.featureCompatibility) && + dbName.tenantId()) { + dbName.tenantId()->serializeToBSON("$tenant", &b); + } BSONObj res; - getClient()->runCommand(dbName, BSON("dbStats" << 1), res); + getClient()->runCommand(db, b.obj(), res); + // It is possible for the call to 'dbStats' to fail if the sync source contains invalid // views. We should not fail initial sync in this case due to the situation where the // replica set may have lost majority availability and therefore have no access to a @@ -212,10 +254,13 @@ void AllDatabaseCloner::postStage() { } } } + bool foundAuthSchemaDoc = false; + bool foundUser = false; for (const auto& dbName : _databases) { { stdx::lock_guard<Latch> lk(_mutex); - _currentDatabaseCloner = std::make_unique<DatabaseCloner>(dbName, + // TODO SERVER-70430: Pass in dbName directly to the constructor. + _currentDatabaseCloner = std::make_unique<DatabaseCloner>(dbName.toStringWithTenantId(), getSharedData(), getSource(), getClient(), @@ -242,10 +287,9 @@ void AllDatabaseCloner::postStage() { setSyncFailedStatus(dbStatus); return; } - if (StringData(dbName).equalCaseInsensitive("admin")) { + if (!foundUser && StringData(dbName.db()).equalCaseInsensitive("admin")) { LOGV2_DEBUG(21058, 1, "Finished the 'admin' db, now validating it"); // Do special checks for the admin database because of auth. collections. - auto adminStatus = Status(ErrorCodes::NotYetInitialized, ""); { OperationContext* opCtx = cc().getOperationContext(); ServiceContext::UniqueOperationContext opCtxPtr; @@ -253,15 +297,41 @@ void AllDatabaseCloner::postStage() { opCtxPtr = cc().makeOperationContext(); opCtx = opCtxPtr.get(); } - adminStatus = getStorageInterface()->isAdminDbValid(opCtx); + auto authzManager = AuthorizationManager::get(opCtx->getServiceContext()); + + // Check if global admin has a valid auth schema version document. + if (!dbName.tenantId() && !foundAuthSchemaDoc) { + auto status = + authzManager->hasValidAuthSchemaVersionDocumentForInitialSync(opCtx); + if (status == ErrorCodes::AuthSchemaIncompatible) { + handleAdminDbNotValid(status); + return; + } + + foundAuthSchemaDoc = status.isOK(); + } + + // We haven't yet found a user document, look for one. In a multitenant environment, + // user documents will live in tenant-specific admin collections. + foundUser = authzManager->hasUser(opCtx, dbName.tenantId()); } - if (!adminStatus.isOK()) { - LOGV2_DEBUG(21059, - 1, - "Validation failed on 'admin' db due to {error}", - "Validation failed on 'admin' db", - "error"_attr = adminStatus); - setSyncFailedStatus(adminStatus); + + // The global admin db sorts first even in a multitenant environemnt, so if we've found + // a user and haven't found an auth schema doc, we can fail early. + if (!foundAuthSchemaDoc && foundUser) { + std::string msg = str::stream() + << "During initial sync, found documents in " + << AuthorizationManager::usersCollectionNamespace.ns() + << " but could not find an auth schema version document in " + << AuthorizationManager::versionCollectionNamespace.ns() << ". " + << "This indicates that the primary of this replica set was not " + "successfully " + "upgraded to schema version " + << AuthorizationManager::schemaVersion26Final + << ", which is the minimum supported schema version in this version of " + "MongoDB"; + auto errorStatus = Status(ErrorCodes::AuthSchemaIncompatible, msg); + handleAdminDbNotValid(errorStatus); return; } } diff --git a/src/mongo/db/repl/all_database_cloner.h b/src/mongo/db/repl/all_database_cloner.h index d538af3869c..4f747545000 100644 --- a/src/mongo/db/repl/all_database_cloner.h +++ b/src/mongo/db/repl/all_database_cloner.h @@ -127,6 +127,8 @@ private: return "admin db: { " + stage->getName() + ": 1 }"; } + void handleAdminDbNotValid(const Status& errorStatus); + // All member variables are labeled with one of the following codes indicating the // synchronization rules for accessing them. // @@ -139,7 +141,7 @@ private: ConnectStage _connectStage; // (R) ConnectStage _getInitialSyncIdStage; // (R) ClonerStage<AllDatabaseCloner> _listDatabasesStage; // (R) - std::vector<std::string> _databases; // (X) + std::vector<DatabaseName> _databases; // (X) std::unique_ptr<DatabaseCloner> _currentDatabaseCloner; // (MX) Stats _stats; // (MX) }; diff --git a/src/mongo/db/repl/all_database_cloner_test.cpp b/src/mongo/db/repl/all_database_cloner_test.cpp index 99bc4d30e4a..78770847aa6 100644 --- a/src/mongo/db/repl/all_database_cloner_test.cpp +++ b/src/mongo/db/repl/all_database_cloner_test.cpp @@ -37,6 +37,7 @@ #include "mongo/db/repl/storage_interface_mock.h" #include "mongo/db/service_context_test_fixture.h" #include "mongo/dbtests/mock/mock_dbclient_connection.h" +#include "mongo/idl/server_parameter_test_util.h" #include "mongo/logv2/log.h" #include "mongo/unittest/unittest.h" #include "mongo/util/clock_source_mock.h" @@ -61,11 +62,92 @@ protected: _dbWorkThreadPool.get()); } - std::vector<std::string> getDatabasesFromCloner(AllDatabaseCloner* cloner) { + std::vector<DatabaseName> getDatabasesFromCloner(AllDatabaseCloner* cloner) { return cloner->_databases; } }; +TEST_F(AllDatabaseClonerTest, ListDatabaseStageSortsAdminCorrectlyGlobalAdminBeforeTenantAdmin) { + RAIIServerParameterControllerForTest multitenanyController("multitenancySupport", true); + RAIIServerParameterControllerForTest featureFlagController("featureFlagRequireTenantID", true); + auto atid = TenantId(OID::gen()); + auto btid = TenantId(OID::gen()); + // global Admin before tenant secific admins. + _mockServer->setCommandReply("listDatabasesForAllTenants", + BSON("ok" << 1 << "databases" + << BSON_ARRAY(BSON("name" + << "aab" + << "tenantId" << btid) + << BSON("name" + << "a" + << "tenantId" << atid) + << BSON("name" + << "admin" + << "tenantId" << atid) + << BSON("name" + << "admin" + << "tenantId" << btid) + << BSON("name" + << "admin")))); + + auto cloner = makeAllDatabaseCloner(); + cloner->setStopAfterStage_forTest("listDatabases"); + + ASSERT_OK(cloner->run()); + auto databases = getDatabasesFromCloner(cloner.get()); + + ASSERT_EQUALS(5u, databases.size()); + ASSERT_EQUALS("admin", databases[0].db()); + ASSERT(!databases[0].tenantId()); + ASSERT_EQUALS("admin", databases[1].db()); + ASSERT(databases[1].tenantId()); + ASSERT_EQUALS("admin", databases[2].db()); + ASSERT(databases[2].tenantId()); + ASSERT_EQUALS("a", databases[3].db()); + ASSERT_EQUALS("aab", databases[4].db()); +} + +TEST_F(AllDatabaseClonerTest, ListDatabaseStageSortsAdminCorrectlyTenantAdminSetToFirst) { + RAIIServerParameterControllerForTest multitenanyController("multitenancySupport", true); + RAIIServerParameterControllerForTest featureFlagController("featureFlagRequireTenantID", true); + auto atid = TenantId(OID::gen()); + auto btid = TenantId(OID::gen()); + // tenant specific admin is the first database. + _mockServer->setCommandReply("listDatabasesForAllTenants", + BSON("ok" << 1 << "databases" + << BSON_ARRAY(BSON("name" + << "admin" + << "tenantId" << btid) + << BSON("name" + << "a" + << "tenantId" << atid) + << BSON("name" + << "admin") + << BSON("name" + << "admin" + << "tenantId" << atid) + << BSON("name" + << "aab" + << "tenantId" << btid)))); + + auto cloner = makeAllDatabaseCloner(); + cloner->setStopAfterStage_forTest("listDatabases"); + + ASSERT_OK(cloner->run()); + auto databases = getDatabasesFromCloner(cloner.get()); + + ASSERT_EQUALS(5u, databases.size()); + ASSERT_EQUALS("admin", databases[0].db()); + ASSERT(!databases[0].tenantId()); + ASSERT_EQUALS("admin", databases[1].db()); + ASSERT(databases[1].tenantId()); + ASSERT_EQUALS("admin", databases[2].db()); + ASSERT(databases[2].tenantId()); + ASSERT_EQUALS("a", databases[3].db()); + ASSERT_EQUALS("aab", databases[4].db()); +} + + TEST_F(AllDatabaseClonerTest, RetriesConnect) { // Bring the server down. _mockServer->shutdown(); @@ -413,7 +495,7 @@ TEST_F(AllDatabaseClonerTest, AdminIsSetToFirst) { ASSERT_OK(cloner->run()); auto databases = getDatabasesFromCloner(cloner.get()); - ASSERT_EQUALS("admin", databases[0]); + ASSERT_EQUALS("admin", databases[0].db()); _mockServer->setCommandReply( "listDatabases", fromjson("{ok:1, databases:[{name:'admin'}, {name:'a'}, {name:'b'}]}")); @@ -423,7 +505,7 @@ TEST_F(AllDatabaseClonerTest, AdminIsSetToFirst) { ASSERT_OK(cloner->run()); databases = getDatabasesFromCloner(cloner.get()); - ASSERT_EQUALS("admin", databases[0]); + ASSERT_EQUALS("admin", databases[0].db()); } TEST_F(AllDatabaseClonerTest, LocalIsRemoved) { @@ -436,8 +518,8 @@ TEST_F(AllDatabaseClonerTest, LocalIsRemoved) { auto databases = getDatabasesFromCloner(cloner.get()); ASSERT_EQUALS(2u, databases.size()); - ASSERT_EQUALS("a", databases[0]); - ASSERT_EQUALS("aab", databases[1]); + ASSERT_EQUALS("a", databases[0].db()); + ASSERT_EQUALS("aab", databases[1].db()); _mockServer->setCommandReply( "listDatabases", fromjson("{ok:1, databases:[{name:'local'}, {name:'a'}, {name:'b'}]}")); @@ -448,17 +530,11 @@ TEST_F(AllDatabaseClonerTest, LocalIsRemoved) { databases = getDatabasesFromCloner(cloner.get()); ASSERT_EQUALS(2u, databases.size()); - ASSERT_EQUALS("a", databases[0]); - ASSERT_EQUALS("b", databases[1]); + ASSERT_EQUALS("a", databases[0].db()); + ASSERT_EQUALS("b", databases[1].db()); } TEST_F(AllDatabaseClonerTest, DatabaseStats) { - bool isAdminDbValidFnCalled = false; - _storageInterface.isAdminDbValidFn = [&isAdminDbValidFnCalled](OperationContext* opCtx) { - isAdminDbValidFnCalled = true; - return Status::OK(); - }; - _mockServer->setCommandReply( "listDatabases", fromjson("{ok:1, databases:[{name:'a'}, {name:'aab'}, {name: 'admin'}]}")); @@ -492,9 +568,9 @@ TEST_F(AllDatabaseClonerTest, DatabaseStats) { auto databases = getDatabasesFromCloner(cloner.get()); ASSERT_EQUALS(3u, databases.size()); - ASSERT_EQUALS("admin", databases[0]); - ASSERT_EQUALS("aab", databases[1]); - ASSERT_EQUALS("a", databases[2]); + ASSERT_EQUALS("admin", databases[0].db()); + ASSERT_EQUALS("aab", databases[1].db()); + ASSERT_EQUALS("a", databases[2].db()); auto stats = cloner->getStats(); ASSERT_EQUALS(0, stats.databasesCloned); @@ -522,7 +598,6 @@ TEST_F(AllDatabaseClonerTest, DatabaseStats) { // Wait for the failpoint to be reached. dbClonerBeforeFailPoint->waitForTimesEntered(timesEntered + 1); - stats = cloner->getStats(); ASSERT_EQUALS(1, stats.databasesCloned); ASSERT_EQUALS(3, stats.databaseStats.size()); @@ -535,7 +610,6 @@ TEST_F(AllDatabaseClonerTest, DatabaseStats) { ASSERT_EQUALS(Date_t(), stats.databaseStats[2].start); ASSERT_EQUALS(Date_t(), stats.databaseStats[2].end); _clock.advance(Minutes(1)); - ASSERT(isAdminDbValidFnCalled); // Allow the cloner to move to the last DB. timesEntered = dbClonerBeforeFailPoint->setMode( @@ -574,6 +648,189 @@ TEST_F(AllDatabaseClonerTest, DatabaseStats) { ASSERT_EQUALS(_clock.now(), stats.databaseStats[2].end); } + +TEST_F(AllDatabaseClonerTest, + DatabaseStatsMultitenancySupportAndFeatureFlagRequireTenantIdEnabled) { + RAIIServerParameterControllerForTest multitenanyController("multitenancySupport", true); + RAIIServerParameterControllerForTest featureFlagController("featureFlagRequireTenantID", true); + + auto tid = TenantId(OID::gen()); + _mockServer->setCommandReply("listDatabasesForAllTenants", + BSON("ok" << 1 << "databases" + << BSON_ARRAY(BSON("name" + << "aab" + << "tenantId" << tid) + << BSON("name" + << "a" + << "tenantId" << tid) + << BSON("name" + << "admin" + << "tenantId" << tid) + << BSON("name" + << "admin") + << BSON("name" + << "local" + << "tenantId" << tid)))); + + // Make the DatabaseCloner do nothing + _mockServer->setCommandReply("listCollections", createCursorResponse("admin.$cmd", {})); + auto cloner = makeAllDatabaseCloner(); + // Set up the DatabaseCloner to pause so we can check stats. + // We need to use two fail points to do this because fail points cannot have their data + // modified atomically. + auto dbClonerBeforeFailPoint = globalFailPointRegistry().find("hangBeforeClonerStage"); + auto dbClonerAfterFailPoint = globalFailPointRegistry().find("hangAfterClonerStage"); + auto timesEntered = + dbClonerBeforeFailPoint->setMode( + FailPoint::alwaysOn, + 0, + fromjson(str::stream() + << "{cloner: 'DatabaseCloner', stage: 'listCollections', database: 'admin'}")); + dbClonerAfterFailPoint->setMode( + FailPoint::alwaysOn, + 0, + fromjson(str::stream() + << "{cloner: 'DatabaseCloner', stage: 'listCollections', database: 'admin'}")); + _clock.advance(Minutes(1)); + // Run the cloner in a separate thread. + stdx::thread clonerThread([&] { + Client::initThread("ClonerRunner"); + ASSERT_OK(cloner->run()); + }); + + // Wait for the failpoint to be reached + dbClonerBeforeFailPoint->waitForTimesEntered(timesEntered + 1); + auto databases = getDatabasesFromCloner(cloner.get()); + // Expect 4 dbs, since "local" should be removed + std::string adminWithTenantId = str::stream() << tid.toString() << "_admin"; + std::string aWithTenantId = str::stream() << tid.toString() << "_a"; + std::string aabWithTenantId = str::stream() << tid.toString() << "_aab"; + // Checks admin is first db. + ASSERT_EQUALS(4u, databases.size()); + ASSERT_EQUALS("admin", databases[0].db()); + ASSERT_EQUALS("admin", databases[1].db()); + ASSERT_EQUALS("aab", databases[2].db()); + ASSERT_EQUALS("a", databases[3].db()); + + auto stats = cloner->getStats(); + ASSERT_EQUALS(0, stats.databasesCloned); + ASSERT_EQUALS(4, stats.databaseStats.size()); + ASSERT_EQUALS("admin", stats.databaseStats[0].dbname); + ASSERT_EQUALS(adminWithTenantId, stats.databaseStats[1].dbname); + ASSERT_EQUALS(aabWithTenantId, stats.databaseStats[2].dbname); + ASSERT_EQUALS(aWithTenantId, stats.databaseStats[3].dbname); + ASSERT_EQUALS(_clock.now(), stats.databaseStats[0].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[0].end); + ASSERT_EQUALS(Date_t(), stats.databaseStats[1].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[1].end); + ASSERT_EQUALS(Date_t(), stats.databaseStats[2].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[2].end); + ASSERT_EQUALS(Date_t(), stats.databaseStats[3].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[3].end); + _clock.advance(Minutes(1)); + + // Allow the cloner to move to the next DB. + timesEntered = dbClonerBeforeFailPoint->setMode( + FailPoint::alwaysOn, + 0, + fromjson(str::stream() << "{cloner: 'DatabaseCloner', stage: 'listCollections', database: '" + << adminWithTenantId << "'}")); + dbClonerAfterFailPoint->setMode( + FailPoint::alwaysOn, + 0, + fromjson(str::stream() << "{cloner: 'DatabaseCloner', stage: 'listCollections', database: '" + << adminWithTenantId << "'}")); + + // Wait for the failpoint to be reached. + dbClonerBeforeFailPoint->waitForTimesEntered(timesEntered + 1); + + stats = cloner->getStats(); + ASSERT_EQUALS(1, stats.databasesCloned); + ASSERT_EQUALS(4, stats.databaseStats.size()); + ASSERT_EQUALS("admin", stats.databaseStats[0].dbname); + ASSERT_EQUALS(adminWithTenantId, stats.databaseStats[1].dbname); + ASSERT_EQUALS(aabWithTenantId, stats.databaseStats[2].dbname); + ASSERT_EQUALS(aWithTenantId, stats.databaseStats[3].dbname); + ASSERT_EQUALS(_clock.now(), stats.databaseStats[0].end); + ASSERT_EQUALS(_clock.now(), stats.databaseStats[1].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[1].end); + ASSERT_EQUALS(Date_t(), stats.databaseStats[2].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[2].end); + ASSERT_EQUALS(Date_t(), stats.databaseStats[3].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[3].end); + _clock.advance(Minutes(1)); + + // Allow the cloner to move to the tenant admin DB. + timesEntered = dbClonerBeforeFailPoint->setMode( + FailPoint::alwaysOn, + 0, + fromjson(str::stream() << "{cloner: 'DatabaseCloner', stage: 'listCollections', database: '" + << aabWithTenantId << "'}")); + dbClonerAfterFailPoint->setMode( + FailPoint::alwaysOn, + 0, + fromjson(str::stream() << "{cloner: 'DatabaseCloner', stage: 'listCollections', database: '" + << aabWithTenantId << "'}")); + + // Wait for the failpoint to be reached. + dbClonerBeforeFailPoint->waitForTimesEntered(timesEntered + 1); + stats = cloner->getStats(); + ASSERT_EQUALS(2, stats.databasesCloned); + ASSERT_EQUALS(4, stats.databaseStats.size()); + ASSERT_EQUALS("admin", stats.databaseStats[0].dbname); + ASSERT_EQUALS(adminWithTenantId, stats.databaseStats[1].dbname); + ASSERT_EQUALS(aabWithTenantId, stats.databaseStats[2].dbname); + ASSERT_EQUALS(aWithTenantId, stats.databaseStats[3].dbname); + ASSERT_EQUALS(_clock.now(), stats.databaseStats[1].end); + ASSERT_EQUALS(_clock.now(), stats.databaseStats[2].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[2].end); + ASSERT_EQUALS(Date_t(), stats.databaseStats[3].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[3].end); + _clock.advance(Minutes(1)); + + + // Allow the cloner to move to the last DB. + timesEntered = dbClonerBeforeFailPoint->setMode( + FailPoint::alwaysOn, + 0, + fromjson(str::stream() << "{cloner: 'DatabaseCloner', stage: 'listCollections', database: '" + << aWithTenantId << "'}")); + dbClonerAfterFailPoint->setMode( + FailPoint::alwaysOn, + 0, + fromjson(str::stream() << "{cloner: 'DatabaseCloner', stage: 'listCollections', database: '" + << aWithTenantId << "'}")); + + // Wait for the failpoint to be reached. + dbClonerBeforeFailPoint->waitForTimesEntered(timesEntered + 1); + + stats = cloner->getStats(); + ASSERT_EQUALS(3, stats.databasesCloned); + ASSERT_EQUALS(4, stats.databaseStats.size()); + ASSERT_EQUALS("admin", stats.databaseStats[0].dbname); + ASSERT_EQUALS(adminWithTenantId, stats.databaseStats[1].dbname); + ASSERT_EQUALS(aabWithTenantId, stats.databaseStats[2].dbname); + ASSERT_EQUALS(aWithTenantId, stats.databaseStats[3].dbname); + ASSERT_EQUALS(_clock.now(), stats.databaseStats[2].end); + ASSERT_EQUALS(_clock.now(), stats.databaseStats[3].start); + ASSERT_EQUALS(Date_t(), stats.databaseStats[3].end); + _clock.advance(Minutes(1)); + + // Allow the cloner to finish + dbClonerBeforeFailPoint->setMode(FailPoint::off, 0); + dbClonerAfterFailPoint->setMode(FailPoint::off, 0); + clonerThread.join(); + + stats = cloner->getStats(); + ASSERT_EQUALS(4, stats.databasesCloned); + ASSERT_EQUALS("admin", stats.databaseStats[0].dbname); + ASSERT_EQUALS(adminWithTenantId, stats.databaseStats[1].dbname); + ASSERT_EQUALS(aabWithTenantId, stats.databaseStats[2].dbname); + ASSERT_EQUALS(aWithTenantId, stats.databaseStats[3].dbname); + ASSERT_EQUALS(_clock.now(), stats.databaseStats[3].end); +} + + TEST_F(AllDatabaseClonerTest, FailsOnListCollectionsOnOnlyDatabase) { _mockServer->setCommandReply("listDatabases", fromjson("{ok:1, databases:[{name:'a'}]}")); _mockServer->setCommandReply("listCollections", Status{ErrorCodes::NoSuchKey, "fake"}); diff --git a/src/mongo/db/repl/storage_interface.h b/src/mongo/db/repl/storage_interface.h index aff0634f035..44302db357f 100644 --- a/src/mongo/db/repl/storage_interface.h +++ b/src/mongo/db/repl/storage_interface.h @@ -213,11 +213,6 @@ public: virtual Status dropReplicatedDatabases(OperationContext* opCtx) = 0; /** - * Validates that the admin database is valid during initial sync. - */ - virtual Status isAdminDbValid(OperationContext* opCtx) = 0; - - /** * Finds at most "limit" documents returned by a collection or index scan on the collection in * the requested direction. * The documents returned will be copied and buffered. No cursors on the underlying collection diff --git a/src/mongo/db/repl/storage_interface_impl.cpp b/src/mongo/db/repl/storage_interface_impl.cpp index b1697942c24..98dd971cb8d 100644 --- a/src/mongo/db/repl/storage_interface_impl.cpp +++ b/src/mongo/db/repl/storage_interface_impl.cpp @@ -1422,64 +1422,6 @@ boost::optional<Timestamp> StorageInterfaceImpl::getRecoveryTimestamp( return serviceCtx->getStorageEngine()->getRecoveryTimestamp(); } -Status StorageInterfaceImpl::isAdminDbValid(OperationContext* opCtx) { - AutoGetDb autoDB(opCtx, DatabaseName(boost::none, "admin"), MODE_X); - auto adminDb = autoDB.getDb(); - if (!adminDb) { - return Status::OK(); - } - - auto catalog = CollectionCatalog::get(opCtx); - CollectionPtr usersCollection = - catalog->lookupCollectionByNamespace(opCtx, AuthorizationManager::usersCollectionNamespace); - const bool hasUsers = - usersCollection && !Helpers::findOne(opCtx, usersCollection, BSONObj()).isNull(); - CollectionPtr adminVersionCollection = catalog->lookupCollectionByNamespace( - opCtx, AuthorizationManager::versionCollectionNamespace); - BSONObj authSchemaVersionDocument; - if (!adminVersionCollection || - !Helpers::findOne(opCtx, - adminVersionCollection, - AuthorizationManager::versionDocumentQuery, - authSchemaVersionDocument)) { - if (!hasUsers) { - // It's OK to have no auth version document if there are no user documents. - return Status::OK(); - } - std::string msg = str::stream() - << "During initial sync, found documents in " - << AuthorizationManager::usersCollectionNamespace.ns() - << " but could not find an auth schema version document in " - << AuthorizationManager::versionCollectionNamespace.ns() << ". " - << "This indicates that the primary of this replica set was not successfully " - "upgraded to schema version " - << AuthorizationManager::schemaVersion26Final - << ", which is the minimum supported schema version in this version of MongoDB"; - return {ErrorCodes::AuthSchemaIncompatible, msg}; - } - long long foundSchemaVersion; - Status status = bsonExtractIntegerField(authSchemaVersionDocument, - AuthorizationManager::schemaVersionFieldName, - &foundSchemaVersion); - if (!status.isOK()) { - std::string msg = str::stream() - << "During initial sync, found malformed auth schema version document: " - << status.toString() << "; document: " << authSchemaVersionDocument; - return {ErrorCodes::AuthSchemaIncompatible, msg}; - } - if ((foundSchemaVersion != AuthorizationManager::schemaVersion26Final) && - (foundSchemaVersion != AuthorizationManager::schemaVersion28SCRAM)) { - std::string msg = str::stream() - << "During initial sync, found auth schema version " << foundSchemaVersion - << ", but this version of MongoDB only supports schema versions " - << AuthorizationManager::schemaVersion26Final << " and " - << AuthorizationManager::schemaVersion28SCRAM; - return {ErrorCodes::AuthSchemaIncompatible, msg}; - } - - return Status::OK(); -} - void StorageInterfaceImpl::waitForAllEarlierOplogWritesToBeVisible(OperationContext* opCtx, bool primaryOnly) { // Waiting for oplog writes to be visible in the oplog does not use any storage engine resources diff --git a/src/mongo/db/repl/storage_interface_impl.h b/src/mongo/db/repl/storage_interface_impl.h index 3dce1e53c61..c5d2f7ad5b2 100644 --- a/src/mongo/db/repl/storage_interface_impl.h +++ b/src/mongo/db/repl/storage_interface_impl.h @@ -190,11 +190,6 @@ public: Timestamp getAllDurableTimestamp(ServiceContext* serviceCtx) const override; - /** - * Checks that the "admin" database contains a supported version of the auth data schema. - */ - Status isAdminDbValid(OperationContext* opCtx) override; - void waitForAllEarlierOplogWritesToBeVisible(OperationContext* opCtx, bool primaryOnly) override; void oplogDiskLocRegister(OperationContext* opCtx, diff --git a/src/mongo/db/repl/storage_interface_mock.h b/src/mongo/db/repl/storage_interface_mock.h index 747f82776c5..59c470da432 100644 --- a/src/mongo/db/repl/storage_interface_mock.h +++ b/src/mongo/db/repl/storage_interface_mock.h @@ -127,7 +127,6 @@ public: std::size_t)>; using PutSingletonFn = std::function<Status(OperationContext*, const NamespaceString&, const TimestampedBSONObj&)>; - using IsAdminDbValidFn = std::function<Status(OperationContext*)>; using GetCollectionUUIDFn = std::function<StatusWith<UUID>(OperationContext*, const NamespaceString&)>; @@ -346,10 +345,6 @@ public: Timestamp getAllDurableTimestamp(ServiceContext* serviceCtx) const override; - Status isAdminDbValid(OperationContext* opCtx) override { - return isAdminDbValidFn(opCtx); - }; - void waitForAllEarlierOplogWritesToBeVisible(OperationContext* opCtx, bool primaryOnly) override { return; @@ -437,9 +432,7 @@ public: [](OperationContext* opCtx, const NamespaceString& nss, const TimestampedBSONObj& update) { return Status{ErrorCodes::IllegalOperation, "PutSingletonFn not implemented."}; }; - IsAdminDbValidFn isAdminDbValidFn = [](OperationContext*) { - return Status{ErrorCodes::IllegalOperation, "IsAdminDbValidFn not implemented."}; - }; + GetCollectionUUIDFn getCollectionUUIDFn = [](OperationContext* opCtx, const NamespaceString& nss) -> StatusWith<UUID> { return Status{ErrorCodes::IllegalOperation, "GetCollectionUUIDFn not implemented."}; diff --git a/src/mongo/embedded/embedded_auth_manager.cpp b/src/mongo/embedded/embedded_auth_manager.cpp index ec2c7dc9184..6604e4a2756 100644 --- a/src/mongo/embedded/embedded_auth_manager.cpp +++ b/src/mongo/embedded/embedded_auth_manager.cpp @@ -66,6 +66,10 @@ public: UASSERT_NOT_IMPLEMENTED; } + Status hasValidAuthSchemaVersionDocumentForInitialSync(OperationContext* opCtx) override { + UASSERT_NOT_IMPLEMENTED; + } + bool hasAnyPrivilegeDocuments(OperationContext*) override { UASSERT_NOT_IMPLEMENTED; } @@ -74,6 +78,10 @@ public: UASSERT_NOT_IMPLEMENTED; } + bool hasUser(OperationContext* opCtx, const boost::optional<TenantId>& tenantId) override { + UASSERT_NOT_IMPLEMENTED; + } + Status rolesExist(OperationContext*, const std::vector<RoleName>&) override { UASSERT_NOT_IMPLEMENTED; } |