summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDianna Hohensee <dianna.hohensee@10gen.com>2016-08-12 15:37:49 -0400
committerDianna Hohensee <dianna.hohensee@10gen.com>2016-08-17 13:06:18 -0400
commit8e19a43ce5401c6333efc7c7dee291778bcec94a (patch)
tree8c472443b4d8bcd4bc55aa5fa4a3f980d1e5975a /src
parent48594ea613c36a1726759deb5c5093fd7da50a4d (diff)
downloadmongo-8e19a43ce5401c6333efc7c7dee291778bcec94a.tar.gz
SERVER-25384 Create a config.migrations collection and a Scoped RAII object class to write to it for migrations
Diffstat (limited to 'src')
-rw-r--r--src/mongo/s/SConscript7
-rw-r--r--src/mongo/s/balancer/scoped_migration_request.cpp131
-rw-r--r--src/mongo/s/balancer/scoped_migration_request.h109
-rw-r--r--src/mongo/s/balancer/scoped_migration_request_test.cpp194
-rw-r--r--src/mongo/s/balancer/type_migration.cpp146
-rw-r--r--src/mongo/s/balancer/type_migration.h95
-rw-r--r--src/mongo/s/balancer/type_migration_test.cpp229
-rw-r--r--src/mongo/s/catalog/replset/SConscript1
-rw-r--r--src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp12
9 files changed, 922 insertions, 2 deletions
diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript
index a4147f3bc16..6c96816eb5d 100644
--- a/src/mongo/s/SConscript
+++ b/src/mongo/s/SConscript
@@ -45,6 +45,7 @@ env.Library(
env.Library(
target='common',
source=[
+ 'balancer/type_migration.cpp',
'catalog/mongo_version_range.cpp',
'catalog/type_changelog.cpp',
'catalog/type_chunk.cpp',
@@ -114,6 +115,7 @@ env.Library(
'config_server_test_fixture.cpp',
],
LIBDEPS=[
+ '$BUILD_DIR/mongo/db/repl/replmocks',
'$BUILD_DIR/mongo/db/service_context_d_test_fixture',
'$BUILD_DIR/mongo/client/remote_command_targeter_mock',
'$BUILD_DIR/mongo/executor/network_test_env',
@@ -147,6 +149,7 @@ env.CppUnitTest(
env.CppUnitTest(
target='sharding_common_test',
source=[
+ 'balancer/type_migration_test.cpp',
'catalog/type_changelog_test.cpp',
'catalog/type_chunk_test.cpp',
'catalog/type_collection_test.cpp',
@@ -256,6 +259,7 @@ env.Library(
'balancer/cluster_statistics.cpp',
'balancer/cluster_statistics_impl.cpp',
'balancer/migration_manager.cpp',
+ 'balancer/scoped_migration_request.cpp',
'catalog/catalog_cache.cpp',
'chunk.cpp',
'chunk_manager.cpp',
@@ -270,6 +274,7 @@ env.Library(
LIBDEPS=[
'$BUILD_DIR/mongo/db/audit',
'$BUILD_DIR/mongo/db/lasterror',
+ '$BUILD_DIR/mongo/db/repl/repl_coordinator_global',
'$BUILD_DIR/mongo/executor/task_executor_pool',
'$BUILD_DIR/mongo/s/query/cluster_cursor_manager',
'catalog/replset/sharding_catalog_client_impl',
@@ -347,9 +352,9 @@ env.CppUnitTest(
target='migration_manager_test',
source=[
'balancer/migration_manager_test.cpp',
+ 'balancer/scoped_migration_request_test.cpp',
],
LIBDEPS=[
- '$BUILD_DIR/mongo/db/repl/replmocks',
'$BUILD_DIR/mongo/util/version_impl',
'config_server_test_fixture',
'coreshard',
diff --git a/src/mongo/s/balancer/scoped_migration_request.cpp b/src/mongo/s/balancer/scoped_migration_request.cpp
new file mode 100644
index 00000000000..538537079f8
--- /dev/null
+++ b/src/mongo/s/balancer/scoped_migration_request.cpp
@@ -0,0 +1,131 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kSharding
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/s/balancer/scoped_migration_request.h"
+
+#include "mongo/db/repl/replication_coordinator_global.h"
+#include "mongo/db/write_concern_options.h"
+#include "mongo/s/balancer/type_migration.h"
+#include "mongo/s/catalog/sharding_catalog_client.h"
+#include "mongo/s/grid.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+namespace {
+const WriteConcernOptions kMajorityWriteConcern(WriteConcernOptions::kMajority,
+ WriteConcernOptions::SyncMode::UNSET,
+ Seconds(15));
+}
+
+ScopedMigrationRequest::ScopedMigrationRequest(OperationContext* txn,
+ const NamespaceString& nss,
+ const BSONObj& minKey)
+ : _txn(txn), _nss(nss), _minKey(minKey) {}
+
+ScopedMigrationRequest::~ScopedMigrationRequest() {
+ if (!_txn) {
+ // If the txn object was cleared, nothing should happen in the destructor.
+ return;
+ }
+
+ // Try to delete the entry in the config.migrations collection. If the command fails, that is
+ // okay.
+ BSONObj migrationDocumentIdentifier =
+ BSON(MigrationType::ns(_nss.ns()) << MigrationType::min(_minKey));
+ Status result = grid.catalogClient(_txn)->removeConfigDocuments(
+ _txn, MigrationType::ConfigNS, migrationDocumentIdentifier, kMajorityWriteConcern);
+
+ if (!result.isOK()) {
+ LOG(0) << "Failed to remove config.migrations document for migration '"
+ << migrationDocumentIdentifier.toString() << "'" << causedBy(redact(result));
+ }
+}
+
+ScopedMigrationRequest::ScopedMigrationRequest(ScopedMigrationRequest&& other) {
+ *this = std::move(other);
+ // Set txn to null so that the destructor will do nothing.
+ other._txn = nullptr;
+}
+
+ScopedMigrationRequest& ScopedMigrationRequest::operator=(ScopedMigrationRequest&& other) {
+ if (this != &other) {
+ _txn = other._txn;
+ _nss = other._nss;
+ _minKey = other._minKey;
+ // Set txn to null so that the destructor will do nothing.
+ other._txn = nullptr;
+ }
+
+ return *this;
+}
+
+StatusWith<ScopedMigrationRequest> ScopedMigrationRequest::writeMigration(
+ OperationContext* txn,
+ const MigrateInfo& migrateInfo,
+ const ChunkVersion& chunkVersion,
+ const ChunkVersion& collectionVersion) {
+
+ // Try to write a unique migration document to config.migrations.
+ MigrationType migrationType(migrateInfo, chunkVersion, collectionVersion);
+ Status result = grid.catalogClient(txn)->insertConfigDocument(
+ txn, MigrationType::ConfigNS, migrationType.toBSON(), kMajorityWriteConcern);
+
+ if (result == ErrorCodes::DuplicateKey) {
+ return result;
+ }
+
+ // As long as there isn't a DuplicateKey error, the document may have been written, and it's
+ // safe (won't delete another migration's document) and necessary to try to clean up the
+ // document via the destructor.
+ ScopedMigrationRequest scopedMigrationRequest(
+ txn, NamespaceString(migrateInfo.ns), migrateInfo.minKey);
+
+ // If there was a write error, let the object go out of scope and clean up in the destructor.
+ if (!result.isOK()) {
+ return result;
+ }
+
+ return std::move(scopedMigrationRequest);
+}
+
+ScopedMigrationRequest ScopedMigrationRequest::createForRecovery(OperationContext* txn,
+ const NamespaceString& nss,
+ const BSONObj& minKey) {
+ return ScopedMigrationRequest(txn, nss, minKey);
+}
+
+void ScopedMigrationRequest::keepDocumentOnDestruct() {
+ _txn = nullptr;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/balancer/scoped_migration_request.h b/src/mongo/s/balancer/scoped_migration_request.h
new file mode 100644
index 00000000000..68f46cfdb50
--- /dev/null
+++ b/src/mongo/s/balancer/scoped_migration_request.h
@@ -0,0 +1,109 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/base/status_with.h"
+#include "mongo/s/balancer/balancer_policy.h"
+#include "mongo/s/chunk_version.h"
+#include "mongo/s/migration_secondary_throttle_options.h"
+
+namespace mongo {
+
+/**
+ * RAII class that handles writes to the config.migrations collection for a migration that comes
+ * through the balancer.
+ *
+ * A migration must have an entry in the config.migrations collection so that the Balancer can
+ * recover from stepdown/crash. This entry must be entered before a migration begins and then
+ * removed once the migration has finished.
+ *
+ * This class should only be used by the Balancer!
+ */
+class ScopedMigrationRequest {
+ MONGO_DISALLOW_COPYING(ScopedMigrationRequest);
+
+public:
+ /**
+ * Deletes this migration's entry in the config.migrations collection, using majority write
+ * concern. If there is a balancer stepdown/crash before the write propagates to a majority of
+ * servers, that is alright because the balancer recovery process will handle it.
+ *
+ * If keepDocumentOnDestruct() has been called, then no attempt to remove the document is made.
+ */
+ ~ScopedMigrationRequest();
+
+ ScopedMigrationRequest(ScopedMigrationRequest&& other);
+ ScopedMigrationRequest& operator=(ScopedMigrationRequest&& other);
+
+ /**
+ * Inserts an unique migration entry in the config.migrations collection. If the write is
+ * successful, a ScopedMigrationRequest object is returned; otherwise, the write error.
+ *
+ * The destructor will handle removing the document when it is no longer needed.
+ */
+ static StatusWith<ScopedMigrationRequest> writeMigration(OperationContext* txn,
+ const MigrateInfo& migrate,
+ const ChunkVersion& chunkVersion,
+ const ChunkVersion& collectionVersion);
+
+ /**
+ * Creates a ScopedMigrationRequest object without inserting a document into config.migrations.
+ * The destructor will handle removing the migration document when it is no longer needed.
+ *
+ * This should only be used on Balancer recovery when a config.migrations document already
+ * exists for the migration.
+ */
+ static ScopedMigrationRequest createForRecovery(OperationContext* txn,
+ const NamespaceString& nss,
+ const BSONObj& minKey);
+
+ /**
+ * Clears the operation context so that the destructor will not remove the config.migrations
+ * document for the migration.
+ *
+ * This should only be used on the Balancer when it is interrupted and must leave entries in
+ * config.migrations so that ongoing migrations can be recovered later.
+ */
+ void keepDocumentOnDestruct();
+
+private:
+ ScopedMigrationRequest(OperationContext* txn,
+ const NamespaceString& nss,
+ const BSONObj& minKey);
+
+ // Need an operation context with which to do a write in the destructor.
+ OperationContext* _txn;
+
+ // ns and minkey are needed to identify the migration document when it is removed from
+ // config.migrations by the destructor.
+ NamespaceString _nss;
+ BSONObj _minKey;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/balancer/scoped_migration_request_test.cpp b/src/mongo/s/balancer/scoped_migration_request_test.cpp
new file mode 100644
index 00000000000..cc2a956e70b
--- /dev/null
+++ b/src/mongo/s/balancer/scoped_migration_request_test.cpp
@@ -0,0 +1,194 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/s/balancer/scoped_migration_request.h"
+
+#include "mongo/s/balancer/type_migration.h"
+#include "mongo/s/client/shard_registry.h"
+#include "mongo/s/config_server_test_fixture.h"
+#include "mongo/s/migration_secondary_throttle_options.h"
+
+namespace mongo {
+namespace {
+
+using unittest::assertGet;
+
+const std::string kNs = "TestDB.TestColl";
+const BSONObj kMin = BSON("a" << 10);
+const BSONObj kMax = BSON("a" << 20);
+const ShardId kFromShard("shard0000");
+const ShardId kToShard("shard0001");
+const std::string kName = "TestDB.TestColl-a_10";
+
+class ScopedMigrationRequestTest : public ConfigServerTestFixture {
+public:
+ /**
+ * Queries config.migrations for a document with name (_id) "chunkName" and asserts that the
+ * number of documents returned equals "expectedNumberOfDocuments".
+ */
+ void checkMigrationsCollectionForDocument(std::string chunkName,
+ const unsigned long expectedNumberOfDocuments);
+
+ /**
+ * Makes a ScopedMigrationRequest and checks that the migration was written to
+ * config.migrations. This exercises the ScopedMigrationRequest move and assignment
+ * constructors.
+ */
+ ScopedMigrationRequest makeScopedMigrationRequest(const MigrateInfo& migrateInfo);
+};
+
+void ScopedMigrationRequestTest::checkMigrationsCollectionForDocument(
+ std::string chunkName, const unsigned long expectedNumberOfDocuments) {
+ auto response = shardRegistry()->getConfigShard()->exhaustiveFindOnConfig(
+ operationContext(),
+ ReadPreferenceSetting{ReadPreference::PrimaryOnly},
+ repl::ReadConcernLevel::kMajorityReadConcern,
+ NamespaceString(MigrationType::ConfigNS),
+ BSON(MigrationType::name(chunkName)),
+ BSONObj(),
+ boost::none);
+ Shard::QueryResponse queryResponse = unittest::assertGet(response);
+ std::vector<BSONObj> docs = queryResponse.docs;
+ ASSERT_EQUALS(expectedNumberOfDocuments, docs.size());
+}
+
+ScopedMigrationRequest ScopedMigrationRequestTest::makeScopedMigrationRequest(
+ const MigrateInfo& migrateInfo) {
+ ScopedMigrationRequest scopedMigrationRequest =
+ assertGet(ScopedMigrationRequest::writeMigration(operationContext(),
+ migrateInfo,
+ ChunkVersion(1, 2, OID::gen()),
+ ChunkVersion(1, 2, OID::gen())));
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 1);
+
+ return std::move(scopedMigrationRequest);
+}
+
+MigrateInfo makeMigrateInfo() {
+ const ChunkVersion kChunkVersion{1, 2, OID::gen()};
+
+ BSONObjBuilder chunkBuilder;
+ chunkBuilder.append(ChunkType::name(), kName);
+ chunkBuilder.append(ChunkType::ns(), kNs);
+ chunkBuilder.append(ChunkType::min(), kMin);
+ chunkBuilder.append(ChunkType::max(), kMax);
+ kChunkVersion.appendForChunk(&chunkBuilder);
+ chunkBuilder.append(ChunkType::shard(), kFromShard.toString());
+
+ ChunkType chunkType = assertGet(ChunkType::fromBSON(chunkBuilder.obj()));
+ ASSERT_OK(chunkType.validate());
+
+ return MigrateInfo(kNs, kToShard, chunkType);
+}
+
+TEST_F(ScopedMigrationRequestTest, CreateScopedMigrationRequest) {
+ MigrateInfo migrateInfo = makeMigrateInfo();
+
+ {
+ ScopedMigrationRequest scopedMigrationRequest =
+ assertGet(ScopedMigrationRequest::writeMigration(operationContext(),
+ migrateInfo,
+ ChunkVersion(1, 2, OID::gen()),
+ ChunkVersion(1, 2, OID::gen())));
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 1);
+ }
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 0);
+}
+
+/**
+ * A document is created via scoped object, but document is not removed in destructor because
+ * keepDocumentOnDestruct() is called beforehand. Then recreate the scoped object without writing to
+ * the migraitons collection, and remove on destruct.
+ *
+ * Simulates (mostly) Balancer recovery.
+ */
+TEST_F(ScopedMigrationRequestTest, CreateScopedMigrationRequestOnRecovery) {
+ MigrateInfo migrateInfo = makeMigrateInfo();
+
+ // Insert the document for the MigrationRequest and then prevent its removal in the destructor.
+ {
+ ScopedMigrationRequest scopedMigrationRequest =
+ assertGet(ScopedMigrationRequest::writeMigration(operationContext(),
+ migrateInfo,
+ ChunkVersion(1, 2, OID::gen()),
+ ChunkVersion(1, 2, OID::gen())));
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 1);
+
+ scopedMigrationRequest.keepDocumentOnDestruct();
+ }
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 1);
+
+ // Trying to write a migration that already exists should fail.
+ {
+ StatusWith<ScopedMigrationRequest> statusWithScopedMigrationRequest =
+ ScopedMigrationRequest::writeMigration(operationContext(),
+ migrateInfo,
+ ChunkVersion(1, 2, OID::gen()),
+ ChunkVersion(1, 2, OID::gen()));
+
+ ASSERT_EQUALS(ErrorCodes::DuplicateKey, statusWithScopedMigrationRequest.getStatus());
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 1);
+ }
+
+ // Create a new scoped object without inserting a document, and check that the destructor
+ // still removes the document corresponding to the MigrationRequest.
+ {
+ ScopedMigrationRequest scopedMigrationRequest = ScopedMigrationRequest::createForRecovery(
+ operationContext(), NamespaceString(migrateInfo.ns), migrateInfo.minKey);
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 1);
+ }
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 0);
+}
+
+TEST_F(ScopedMigrationRequestTest, MoveAndAssignmentConstructors) {
+ MigrateInfo migrateInfo = makeMigrateInfo();
+
+ // Test that when the move and assignment constructors are used and the original variable goes
+ // out of scope, the original object's destructor does not remove the migration document.
+ {
+ ScopedMigrationRequest anotherScopedMigrationRequest =
+ makeScopedMigrationRequest(migrateInfo);
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 1);
+ }
+
+ checkMigrationsCollectionForDocument(migrateInfo.getName(), 0);
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/balancer/type_migration.cpp b/src/mongo/s/balancer/type_migration.cpp
new file mode 100644
index 00000000000..0fb97ba7b51
--- /dev/null
+++ b/src/mongo/s/balancer/type_migration.cpp
@@ -0,0 +1,146 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/s/balancer/type_migration.h"
+
+#include "mongo/bson/util/bson_extract.h"
+#include "mongo/s/catalog/type_chunk.h"
+
+namespace mongo {
+
+const std::string MigrationType::ConfigNS = "config.migrations";
+
+const BSONField<std::string> MigrationType::name("_id");
+const BSONField<std::string> MigrationType::ns("ns");
+const BSONField<BSONObj> MigrationType::min("min");
+const BSONField<BSONObj> MigrationType::max("max");
+const BSONField<std::string> MigrationType::fromShard("fromShard");
+const BSONField<std::string> MigrationType::toShard("toShard");
+const BSONField<std::string> MigrationType::chunkVersionField("chunkVersion");
+const BSONField<std::string> MigrationType::collectionVersionField("collectionVersion");
+
+MigrationType::MigrationType() = default;
+
+MigrationType::MigrationType(MigrateInfo info,
+ const ChunkVersion& chunkVersion,
+ const ChunkVersion& collectionVersion)
+ : _nss(NamespaceString(info.ns)),
+ _min(info.minKey),
+ _max(info.maxKey),
+ _chunkVersion(chunkVersion),
+ _collectionVersion(collectionVersion),
+ _fromShard(info.from),
+ _toShard(info.to) {}
+
+StatusWith<MigrationType> MigrationType::fromBSON(const BSONObj& source) {
+ MigrationType migrationType;
+
+ {
+ std::string migrationNS;
+ Status status = bsonExtractStringField(source, ns.name(), &migrationNS);
+ if (!status.isOK())
+ return status;
+ migrationType._nss = NamespaceString(migrationNS);
+ }
+
+ {
+ auto chunkRangeStatus = ChunkRange::fromBSON(source);
+ if (!chunkRangeStatus.isOK())
+ return chunkRangeStatus.getStatus();
+
+ const auto chunkRange = std::move(chunkRangeStatus.getValue());
+ migrationType._min = chunkRange.getMin().getOwned();
+ migrationType._max = chunkRange.getMax().getOwned();
+ }
+
+ {
+ auto chunkVersionStatus =
+ ChunkVersion::parseFromBSONWithFieldForCommands(source, chunkVersionField.name());
+ if (!chunkVersionStatus.isOK()) {
+ return chunkVersionStatus.getStatus();
+ }
+ migrationType._chunkVersion = std::move(chunkVersionStatus.getValue());
+ }
+
+ {
+ auto collectionVersionStatus =
+ ChunkVersion::parseFromBSONWithFieldForCommands(source, collectionVersionField.name());
+ if (!collectionVersionStatus.isOK()) {
+ return collectionVersionStatus.getStatus();
+ }
+ migrationType._collectionVersion = std::move(collectionVersionStatus.getValue());
+ }
+
+ {
+ std::string migrationToShard;
+ Status status = bsonExtractStringField(source, toShard.name(), &migrationToShard);
+ if (!status.isOK())
+ return status;
+ migrationType._toShard = migrationToShard;
+ }
+
+ {
+ std::string migrationFromShard;
+ Status status = bsonExtractStringField(source, fromShard.name(), &migrationFromShard);
+ if (!status.isOK())
+ return status;
+ migrationType._fromShard = migrationFromShard;
+ }
+
+ return migrationType;
+}
+
+BSONObj MigrationType::toBSON() const {
+ BSONObjBuilder builder;
+ if (_nss && _min)
+ builder.append(name.name(), getName());
+ if (_nss)
+ builder.append(ns.name(), _nss->ns());
+ if (_min)
+ builder.append(min.name(), _min.get());
+ if (_max)
+ builder.append(max.name(), _max.get());
+ if (_chunkVersion)
+ _chunkVersion->appendWithFieldForCommands(&builder, chunkVersionField.name());
+ if (_collectionVersion)
+ _collectionVersion->appendWithFieldForCommands(&builder, collectionVersionField.name());
+ if (_fromShard)
+ builder.append(fromShard.name(), _fromShard->toString());
+ if (_toShard)
+ builder.append(toShard.name(), _toShard->toString());
+
+ return builder.obj();
+}
+
+std::string MigrationType::getName() const {
+ return ChunkType::genID(_nss->ns(), _min.get());
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/balancer/type_migration.h b/src/mongo/s/balancer/type_migration.h
new file mode 100644
index 00000000000..f1ee489d916
--- /dev/null
+++ b/src/mongo/s/balancer/type_migration.h
@@ -0,0 +1,95 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/s/balancer/balancer_policy.h"
+#include "mongo/s/chunk_version.h"
+#include "mongo/s/client/shard.h"
+
+namespace mongo {
+
+/**
+ * This class represents the layout and contents of documents contained in the config.migrations
+ * collection. All manipulation of documents coming from that collection should be done with this
+ * class.
+ */
+class MigrationType {
+public:
+ // Name of the migrations collection in the config server.
+ static const std::string ConfigNS;
+
+ // Field names and types in the migrations collection type.
+ static const BSONField<std::string> name;
+ static const BSONField<std::string> ns;
+ static const BSONField<BSONObj> min;
+ static const BSONField<BSONObj> max;
+ static const BSONField<std::string> fromShard;
+ static const BSONField<std::string> toShard;
+ static const BSONField<std::string> chunkVersionField;
+ static const BSONField<std::string> collectionVersionField;
+
+ /**
+ * The Balancer encapsulates migration information in MigrateInfo objects, so this facilitates
+ * conversion to a config.migrations entry format.
+ */
+ MigrationType(MigrateInfo info,
+ const ChunkVersion& chunkVersion,
+ const ChunkVersion& collectionVersion);
+
+ /**
+ * Constructs a new MigrationType object from BSON. Expects all fields to be present, and errors
+ * if they are not.
+ */
+ static StatusWith<MigrationType> fromBSON(const BSONObj& source);
+
+ /**
+ * Returns the BSON representation of the config.migrations document entry.
+ */
+ BSONObj toBSON() const;
+
+ /**
+ * Uniquely identifies a chunk by collection and min key.
+ */
+ std::string getName() const;
+
+private:
+ MigrationType();
+
+ // Required fields for config.migrations.
+ boost::optional<NamespaceString> _nss;
+ boost::optional<BSONObj> _min;
+ boost::optional<BSONObj> _max;
+ boost::optional<ChunkVersion> _chunkVersion;
+ boost::optional<ChunkVersion> _collectionVersion;
+ boost::optional<ShardId> _fromShard;
+ boost::optional<ShardId> _toShard;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/balancer/type_migration_test.cpp b/src/mongo/s/balancer/type_migration_test.cpp
new file mode 100644
index 00000000000..8b020c57312
--- /dev/null
+++ b/src/mongo/s/balancer/type_migration_test.cpp
@@ -0,0 +1,229 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/jsobj.h"
+#include "mongo/s/balancer/type_migration.h"
+#include "mongo/s/catalog/type_chunk.h"
+
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+using unittest::assertGet;
+
+const std::string kName = "TestDB.TestColl-a_10";
+const std::string kNs = "TestDB.TestColl";
+const BSONObj kMin = BSON("a" << 10);
+const BSONObj kMax = BSON("a" << 20);
+const ShardId kFromShard("shard0000");
+const ShardId kToShard("shard0001");
+
+TEST(MigrationTypeTest, ConvertFromMigrationInfo) {
+ const ChunkVersion version(1, 2, OID::gen());
+
+ BSONObjBuilder chunkBuilder;
+ chunkBuilder.append(ChunkType::name(), kName);
+ chunkBuilder.append(ChunkType::ns(), kNs);
+ chunkBuilder.append(ChunkType::min(), kMin);
+ chunkBuilder.append(ChunkType::max(), kMax);
+ version.appendForChunk(&chunkBuilder);
+ chunkBuilder.append(ChunkType::shard(), kFromShard.toString());
+
+ ChunkType chunkType = assertGet(ChunkType::fromBSON(chunkBuilder.obj()));
+ ASSERT_OK(chunkType.validate());
+
+ MigrateInfo migrateInfo(kNs, kToShard, chunkType);
+ MigrationType migrationType(migrateInfo, version, version);
+
+ BSONObjBuilder builder;
+ builder.append(MigrationType::name(), kName);
+ builder.append(MigrationType::ns(), kNs);
+ builder.append(MigrationType::min(), kMin);
+ builder.append(MigrationType::max(), kMax);
+ version.appendWithFieldForCommands(&builder, MigrationType::chunkVersionField.name());
+ version.appendWithFieldForCommands(&builder, MigrationType::collectionVersionField.name());
+ builder.append(MigrationType::fromShard(), kFromShard.toString());
+ builder.append(MigrationType::toShard(), kToShard.toString());
+
+ BSONObj obj = builder.obj();
+
+ ASSERT_EQUALS(obj, migrationType.toBSON());
+}
+
+TEST(MigrationTypeTest, FromAndToBSON) {
+ const ChunkVersion version(1, 2, OID::gen());
+
+ BSONObjBuilder builder;
+ builder.append(MigrationType::name(), kName);
+ builder.append(MigrationType::ns(), kNs);
+ builder.append(MigrationType::min(), kMin);
+ builder.append(MigrationType::max(), kMax);
+ version.appendWithFieldForCommands(&builder, MigrationType::chunkVersionField.name());
+ version.appendWithFieldForCommands(&builder, MigrationType::collectionVersionField.name());
+ builder.append(MigrationType::fromShard(), kFromShard.toString());
+ builder.append(MigrationType::toShard(), kToShard.toString());
+
+ BSONObj obj = builder.obj();
+
+ MigrationType migrationType = assertGet(MigrationType::fromBSON(obj));
+ ASSERT_EQUALS(obj, migrationType.toBSON());
+}
+
+TEST(MigrationTypeTest, MissingRequiredNamespaceField) {
+ const ChunkVersion version(1, 2, OID::gen());
+
+ BSONObjBuilder builder;
+ builder.append(MigrationType::min(), kMin);
+ builder.append(MigrationType::max(), kMax);
+ version.appendWithFieldForCommands(&builder, MigrationType::chunkVersionField.name());
+ version.appendWithFieldForCommands(&builder, MigrationType::collectionVersionField.name());
+ builder.append(MigrationType::fromShard(), kFromShard.toString());
+ builder.append(MigrationType::toShard(), kToShard.toString());
+
+ BSONObj obj = builder.obj();
+
+ StatusWith<MigrationType> migrationType = MigrationType::fromBSON(obj);
+ ASSERT_EQUALS(migrationType.getStatus(), ErrorCodes::NoSuchKey);
+ ASSERT_STRING_CONTAINS(migrationType.getStatus().reason(), MigrationType::ns.name());
+}
+
+TEST(MigrationTypeTest, MissingRequiredMinField) {
+ const ChunkVersion version(1, 2, OID::gen());
+
+ BSONObjBuilder builder;
+ builder.append(MigrationType::ns(), kNs);
+ builder.append(MigrationType::max(), kMax);
+ version.appendWithFieldForCommands(&builder, MigrationType::chunkVersionField.name());
+ version.appendWithFieldForCommands(&builder, MigrationType::collectionVersionField.name());
+ builder.append(MigrationType::fromShard(), kFromShard.toString());
+ builder.append(MigrationType::toShard(), kToShard.toString());
+
+ BSONObj obj = builder.obj();
+
+ StatusWith<MigrationType> migrationType = MigrationType::fromBSON(obj);
+ ASSERT_EQUALS(migrationType.getStatus(), ErrorCodes::NoSuchKey);
+ ASSERT_STRING_CONTAINS(migrationType.getStatus().reason(), MigrationType::min.name());
+}
+
+TEST(MigrationTypeTest, MissingRequiredMaxField) {
+ const ChunkVersion version(1, 2, OID::gen());
+
+ BSONObjBuilder builder;
+ builder.append(MigrationType::ns(), kNs);
+ builder.append(MigrationType::min(), kMin);
+ version.appendWithFieldForCommands(&builder, MigrationType::chunkVersionField.name());
+ version.appendWithFieldForCommands(&builder, MigrationType::collectionVersionField.name());
+ builder.append(MigrationType::fromShard(), kFromShard.toString());
+ builder.append(MigrationType::toShard(), kToShard.toString());
+
+ BSONObj obj = builder.obj();
+
+ StatusWith<MigrationType> migrationType = MigrationType::fromBSON(obj);
+ ASSERT_EQUALS(migrationType.getStatus(), ErrorCodes::NoSuchKey);
+ ASSERT_STRING_CONTAINS(migrationType.getStatus().reason(), MigrationType::max.name());
+}
+
+TEST(MigrationTypeTest, MissingRequiredChunkVersionField) {
+ const ChunkVersion version(1, 2, OID::gen());
+
+ BSONObjBuilder builder;
+ builder.append(MigrationType::ns(), kNs);
+ builder.append(MigrationType::min(), kMin);
+ builder.append(MigrationType::max(), kMax);
+ version.appendWithFieldForCommands(&builder, MigrationType::collectionVersionField.name());
+ builder.append(MigrationType::fromShard(), kFromShard.toString());
+ builder.append(MigrationType::toShard(), kToShard.toString());
+
+ BSONObj obj = builder.obj();
+
+ StatusWith<MigrationType> migrationType = MigrationType::fromBSON(obj);
+ ASSERT_EQUALS(migrationType.getStatus(), ErrorCodes::NoSuchKey);
+ ASSERT_STRING_CONTAINS(migrationType.getStatus().reason(),
+ MigrationType::chunkVersionField.name());
+}
+
+TEST(MigrationTypeTest, MissingRequiredCollectionVersionField) {
+ const ChunkVersion version(1, 2, OID::gen());
+
+ BSONObjBuilder builder;
+ builder.append(MigrationType::ns(), kNs);
+ builder.append(MigrationType::min(), kMin);
+ builder.append(MigrationType::max(), kMax);
+ version.appendWithFieldForCommands(&builder, MigrationType::chunkVersionField.name());
+ builder.append(MigrationType::fromShard(), kFromShard.toString());
+ builder.append(MigrationType::toShard(), kToShard.toString());
+
+ BSONObj obj = builder.obj();
+
+ StatusWith<MigrationType> migrationType = MigrationType::fromBSON(obj);
+ ASSERT_EQUALS(migrationType.getStatus(), ErrorCodes::NoSuchKey);
+ ASSERT_STRING_CONTAINS(migrationType.getStatus().reason(),
+ MigrationType::collectionVersionField.name());
+}
+
+TEST(MigrationTypeTest, MissingRequiredFromShardField) {
+ const ChunkVersion version(1, 2, OID::gen());
+
+ BSONObjBuilder builder;
+ builder.append(MigrationType::ns(), kNs);
+ builder.append(MigrationType::min(), kMin);
+ builder.append(MigrationType::max(), kMax);
+ version.appendWithFieldForCommands(&builder, MigrationType::chunkVersionField.name());
+ version.appendWithFieldForCommands(&builder, MigrationType::collectionVersionField.name());
+ builder.append(MigrationType::toShard(), kToShard.toString());
+
+ BSONObj obj = builder.obj();
+
+ StatusWith<MigrationType> migrationType = MigrationType::fromBSON(obj);
+ ASSERT_EQUALS(migrationType.getStatus(), ErrorCodes::NoSuchKey);
+ ASSERT_STRING_CONTAINS(migrationType.getStatus().reason(), MigrationType::fromShard.name());
+}
+
+TEST(MigrationTypeTest, MissingRequiredToShardField) {
+ const ChunkVersion version(1, 2, OID::gen());
+
+ BSONObjBuilder builder;
+ builder.append(MigrationType::ns(), kNs);
+ builder.append(MigrationType::min(), kMin);
+ builder.append(MigrationType::max(), kMax);
+ version.appendWithFieldForCommands(&builder, MigrationType::chunkVersionField.name());
+ version.appendWithFieldForCommands(&builder, MigrationType::collectionVersionField.name());
+ builder.append(MigrationType::fromShard(), kFromShard.toString());
+
+ BSONObj obj = builder.obj();
+
+ StatusWith<MigrationType> migrationType = MigrationType::fromBSON(obj);
+ ASSERT_EQUALS(migrationType.getStatus(), ErrorCodes::NoSuchKey);
+ ASSERT_STRING_CONTAINS(migrationType.getStatus().reason(), MigrationType::toShard.name());
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/catalog/replset/SConscript b/src/mongo/s/catalog/replset/SConscript
index bc50301fc6c..45528541104 100644
--- a/src/mongo/s/catalog/replset/SConscript
+++ b/src/mongo/s/catalog/replset/SConscript
@@ -116,7 +116,6 @@ env.CppUnitTest(
'sharding_catalog_merge_chunk_test.cpp',
],
LIBDEPS=[
- '$BUILD_DIR/mongo/db/repl/replmocks',
'$BUILD_DIR/mongo/s/config_server_test_fixture',
]
)
diff --git a/src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp b/src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp
index c85d0464d90..53ac9dd8a84 100644
--- a/src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp
+++ b/src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp
@@ -54,6 +54,7 @@
#include "mongo/executor/task_executor.h"
#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/s/balancer/balancer_policy.h"
+#include "mongo/s/balancer/type_migration.h"
#include "mongo/s/catalog/config_server_version.h"
#include "mongo/s/catalog/sharding_catalog_client.h"
#include "mongo/s/catalog/type_collection.h"
@@ -1414,6 +1415,17 @@ Status ShardingCatalogManagerImpl::_initConfigIndexes(OperationContext* txn) {
}
result = configShard->createIndexOnConfig(
+ txn,
+ NamespaceString(MigrationType::ConfigNS),
+ BSON(MigrationType::ns() << 1 << MigrationType::min() << 1),
+ unique);
+ if (!result.isOK()) {
+ return Status(result.code(),
+ str::stream() << "couldn't create ns_1_min_1 index on config.migrations"
+ << causedBy(result));
+ }
+
+ result = configShard->createIndexOnConfig(
txn, NamespaceString(ShardType::ConfigNS), BSON(ShardType::host() << 1), unique);
if (!result.isOK()) {
return Status(result.code(),