summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLingzhi Deng <lingzhi.deng@mongodb.com>2021-03-16 23:07:37 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-03-17 00:13:15 +0000
commit77a1e00c536e2f76327bfb96aff1b3323bbfcc5a (patch)
tree2c59dfbc8848764df80b00e25cf546fb955d8562
parent0ae1138bbfc066c4c7eb9f857cf4e29447743a3c (diff)
downloadmongo-77a1e00c536e2f76327bfb96aff1b3323bbfcc5a.tar.gz
SERVER-55169: Handle timeseries bucket collections in TenantCollectionCloner
-rw-r--r--buildscripts/resmokelib/testing/hooks/tenant_migration.py17
-rw-r--r--jstests/replsets/tenant_migration_timeseries_collections.js45
-rw-r--r--src/mongo/db/repl/tenant_collection_cloner.cpp14
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.