diff options
author | Dianna Hohensee <dianna.hohensee@10gen.com> | 2016-08-12 15:37:49 -0400 |
---|---|---|
committer | Dianna Hohensee <dianna.hohensee@10gen.com> | 2016-08-17 13:06:18 -0400 |
commit | 8e19a43ce5401c6333efc7c7dee291778bcec94a (patch) | |
tree | 8c472443b4d8bcd4bc55aa5fa4a3f980d1e5975a /src | |
parent | 48594ea613c36a1726759deb5c5093fd7da50a4d (diff) | |
download | mongo-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/SConscript | 7 | ||||
-rw-r--r-- | src/mongo/s/balancer/scoped_migration_request.cpp | 131 | ||||
-rw-r--r-- | src/mongo/s/balancer/scoped_migration_request.h | 109 | ||||
-rw-r--r-- | src/mongo/s/balancer/scoped_migration_request_test.cpp | 194 | ||||
-rw-r--r-- | src/mongo/s/balancer/type_migration.cpp | 146 | ||||
-rw-r--r-- | src/mongo/s/balancer/type_migration.h | 95 | ||||
-rw-r--r-- | src/mongo/s/balancer/type_migration_test.cpp | 229 | ||||
-rw-r--r-- | src/mongo/s/catalog/replset/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/s/catalog/replset/sharding_catalog_manager_impl.cpp | 12 |
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(), |