diff options
author | Lingzhi Deng <lingzhi.deng@mongodb.com> | 2021-03-16 23:07:37 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-03-17 00:13:15 +0000 |
commit | 77a1e00c536e2f76327bfb96aff1b3323bbfcc5a (patch) | |
tree | 2c59dfbc8848764df80b00e25cf546fb955d8562 | |
parent | 0ae1138bbfc066c4c7eb9f857cf4e29447743a3c (diff) | |
download | mongo-77a1e00c536e2f76327bfb96aff1b3323bbfcc5a.tar.gz |
SERVER-55169: Handle timeseries bucket collections in TenantCollectionCloner
3 files changed, 55 insertions, 21 deletions
diff --git a/buildscripts/resmokelib/testing/hooks/tenant_migration.py b/buildscripts/resmokelib/testing/hooks/tenant_migration.py index f857e05e0d2..4c6100d6153 100644 --- a/buildscripts/resmokelib/testing/hooks/tenant_migration.py +++ b/buildscripts/resmokelib/testing/hooks/tenant_migration.py @@ -321,17 +321,6 @@ class _TenantMigrationThread(threading.Thread): # pylint: disable=too-many-inst return abort_reason["code"] == self.INTERNAL_ERR_CODE and abort_reason[ "errmsg"] == "simulate a tenant migration error" - def _is_empty_id_index_spec_err(self, abort_reason): - # TODO (SERVER-55169): Timeseries bucket collections is expected to fail tenant migration - # cloner's non-empty id index check. - err_msg_regex = "Found empty '_id' index spec but the collection is not specified " + \ - "with 'autoIndexId' as false, tenantId: " + self._tenant_id - return abort_reason["code"] == self.ILLEGAL_OPERATION_ERR_CODE and re.search( - err_msg_regex, abort_reason["errmsg"]) - - def _is_blacklisted_abort_reason(self, abort_reason): - return self._is_empty_id_index_spec_err(abort_reason) - def _create_migration_opts(self, donor_rs_index, recipient_rs_index): donor_rs = self._tenant_migration_fixture.get_replset(donor_rs_index) recipient_rs = self._tenant_migration_fixture.get_replset(recipient_rs_index) @@ -391,12 +380,6 @@ class _TenantMigrationThread(threading.Thread): # pylint: disable=too-many-inst migration_opts.get_donor_name() + "' has aborted due to failpoint: " + str(res)) break - elif self._is_blacklisted_abort_reason(abort_reason): - self.logger.info("Tenant migration with donor primary on port " + - str(donor_primary.port) + " of replica set '" + - migration_opts.get_donor_name() + - "' has aborted due to a blacklisted error: " + str(res)) - break else: raise errors.ServerFailure("Tenant migration with donor primary on port " + str(donor_primary.port) + " of replica set '" + diff --git a/jstests/replsets/tenant_migration_timeseries_collections.js b/jstests/replsets/tenant_migration_timeseries_collections.js new file mode 100644 index 00000000000..df5d0a6d8f7 --- /dev/null +++ b/jstests/replsets/tenant_migration_timeseries_collections.js @@ -0,0 +1,45 @@ +/** + * Tests tenant migration with time-series collections. + * + * @tags: [requires_fcv_49, requires_majority_read_concern, incompatible_with_eft, + * incompatible_with_windows_tls] + */ + +(function() { +"use strict"; + +load("jstests/core/timeseries/libs/timeseries.js"); +load("jstests/libs/uuid_util.js"); +load("jstests/replsets/libs/tenant_migration_test.js"); + +const tenantMigrationTest = new TenantMigrationTest({name: jsTestName()}); +if (!tenantMigrationTest.isFeatureFlagEnabled()) { + jsTestLog("Skipping test because the tenant migrations feature flag is disabled"); + return; +} + +const donorPrimary = tenantMigrationTest.getDonorPrimary(); +if (!TimeseriesTest.timeseriesCollectionsEnabled(donorPrimary)) { + jsTestLog("Skipping test because the time-series collection feature flag is disabled"); + tenantMigrationTest.stop(); + return; +} + +const tenantId = "testTenantId"; +const tsDB = tenantMigrationTest.tenantDB(tenantId, "tsDB"); +const donorTSDB = donorPrimary.getDB(tsDB); +assert.commandWorked(donorTSDB.createCollection("tsColl", {timeseries: {timeField: "time"}})); +assert.commandWorked(donorTSDB.runCommand( + {insert: "tsColl", documents: [{_id: 1, time: ISODate()}, {_id: 2, time: ISODate()}]})); + +const migrationId = UUID(); +const migrationOpts = { + migrationIdString: extractUUIDFromObject(migrationId), + tenantId, +}; + +const stateRes = assert.commandWorked(tenantMigrationTest.runMigration(migrationOpts)); +assert.eq(stateRes.state, TenantMigrationTest.DonorState.kCommitted); + +tenantMigrationTest.stop(); +})(); diff --git a/src/mongo/db/repl/tenant_collection_cloner.cpp b/src/mongo/db/repl/tenant_collection_cloner.cpp index b1e36bdda3a..5a16d7a4576 100644 --- a/src/mongo/db/repl/tenant_collection_cloner.cpp +++ b/src/mongo/db/repl/tenant_collection_cloner.cpp @@ -271,14 +271,15 @@ BaseCloner::AfterStageBehavior TenantCollectionCloner::listIndexesStage() { }; // Tenant collections are replicated collections and it's impossible to have an empty _id index - // and collection options 'autoIndexId' as false. These are extra sanity checks made on the - // response received from the remote node. + // and collection options 'autoIndexId' as false except for collections that use clustered + // index. These are extra sanity checks made on the response received from the remote node. uassert( ErrorCodes::IllegalOperation, str::stream() << "Found empty '_id' index spec but the collection is not specified with " "'autoIndexId' as false, tenantId: " << _tenantId << ", namespace: " << this->_sourceNss, - !_idIndexSpec.isEmpty() || _collectionOptions.autoIndexId == CollectionOptions::NO); + _collectionOptions.clusteredIndex || !_idIndexSpec.isEmpty() || + _collectionOptions.autoIndexId == CollectionOptions::NO); if (!_idIndexSpec.isEmpty() && _collectionOptions.autoIndexId == CollectionOptions::NO) { LOGV2_WARNING(4884504, @@ -423,7 +424,12 @@ void TenantCollectionCloner::runQuery() { ? QUERY("query" << BSONObj()) // Use $expr and the aggregation version of $gt to avoid type bracketing. : QUERY("$expr" << BSON("$gt" << BSON_ARRAY("$_id" << _lastDocId["_id"]))); - query.hint(BSON("_id" << 1)); + if (_collectionOptions.clusteredIndex) { + // RecordIds are _id values and has no separate _id index + query.hint(BSON("$natural" << 1)); + } else { + query.hint(BSON("_id" << 1)); + } // Any errors that are thrown here (including NamespaceNotFound) will be handled on the stage // level. |