From 48432412d79d0712367837a12637d3682c04fddb Mon Sep 17 00:00:00 2001 From: Kaloian Manassiev Date: Fri, 10 Jul 2015 12:42:27 -0400 Subject: SERVER-18084 Move mongod metadata management to be under mongo/db Moves the metadata management code specific to mongod under the mongo/db directory along with its tests. --- src/mongo/db/SConscript | 2 +- src/mongo/db/clientcursor.cpp | 2 + src/mongo/db/clientcursor.h | 9 - src/mongo/db/commands/cleanup_orphaned_cmd.cpp | 2 +- src/mongo/db/commands/create_indexes.cpp | 5 +- src/mongo/db/commands/mr.cpp | 2 +- .../db/commands/write_commands/batch_executor.cpp | 3 +- src/mongo/db/dbhelpers.cpp | 62 +- src/mongo/db/exec/shard_filter.cpp | 2 +- src/mongo/db/ops/update_lifecycle_impl.cpp | 10 +- src/mongo/db/ops/update_lifecycle_impl.h | 4 +- src/mongo/db/query/get_executor.cpp | 4 +- src/mongo/db/range_deleter.h | 2 +- src/mongo/db/s/SConscript | 34 + src/mongo/db/s/collection_metadata.cpp | 727 +++++++++++ src/mongo/db/s/collection_metadata.h | 318 +++++ src/mongo/db/s/collection_metadata_test.cpp | 1321 ++++++++++++++++++++ src/mongo/db/s/metadata_loader.cpp | 291 +++++ src/mongo/db/s/metadata_loader.h | 159 +++ src/mongo/db/s/metadata_loader_test.cpp | 924 ++++++++++++++ src/mongo/db/s/sharding_state.cpp | 9 +- src/mongo/dbtests/dbhelper_tests.cpp | 3 + src/mongo/dbtests/merge_chunk_tests.cpp | 4 +- src/mongo/dbtests/sharding.cpp | 395 +----- src/mongo/s/SConscript | 49 +- src/mongo/s/chunk_diff.cpp | 97 +- src/mongo/s/chunk_diff.h | 68 +- src/mongo/s/chunk_diff_test.cpp | 381 +++++- src/mongo/s/chunk_manager.cpp | 11 +- src/mongo/s/collection_metadata.cpp | 728 ----------- src/mongo/s/collection_metadata.h | 318 ----- src/mongo/s/collection_metadata_test.cpp | 1321 -------------------- src/mongo/s/d_merge.cpp | 19 +- src/mongo/s/d_migrate.cpp | 4 +- src/mongo/s/d_split.cpp | 58 +- src/mongo/s/d_state.cpp | 60 +- src/mongo/s/metadata_loader.cpp | 287 ----- src/mongo/s/metadata_loader.h | 159 --- src/mongo/s/metadata_loader_test.cpp | 924 -------------- 39 files changed, 4383 insertions(+), 4395 deletions(-) create mode 100644 src/mongo/db/s/collection_metadata.cpp create mode 100644 src/mongo/db/s/collection_metadata.h create mode 100644 src/mongo/db/s/collection_metadata_test.cpp create mode 100644 src/mongo/db/s/metadata_loader.cpp create mode 100644 src/mongo/db/s/metadata_loader.h create mode 100644 src/mongo/db/s/metadata_loader_test.cpp delete mode 100644 src/mongo/s/collection_metadata.cpp delete mode 100644 src/mongo/s/collection_metadata.h delete mode 100644 src/mongo/s/collection_metadata_test.cpp delete mode 100644 src/mongo/s/metadata_loader.cpp delete mode 100644 src/mongo/s/metadata_loader.h delete mode 100644 src/mongo/s/metadata_loader_test.cpp (limited to 'src') diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 08ff1f3f687..a3e7f20f1af 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -604,7 +604,6 @@ serveronlyLibdeps = [ "$BUILD_DIR/mongo/s/catalog/replset/catalog_manager_replica_set", "$BUILD_DIR/mongo/s/client/sharding_connection_hook", "$BUILD_DIR/mongo/s/coreshard", - "$BUILD_DIR/mongo/s/metadata", "$BUILD_DIR/mongo/s/serveronly", "$BUILD_DIR/mongo/scripting/scripting_server", "$BUILD_DIR/mongo/util/elapsed_tracker", @@ -637,6 +636,7 @@ serveronlyLibdeps = [ "repl/rslog", "repl/sync_tail", "repl/topology_coordinator_impl", + "s/metadata", "s/sharding", "startup_warnings_mongod", "stats/counters", diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp index c0b61f99404..7b45e3444ea 100644 --- a/src/mongo/db/clientcursor.cpp +++ b/src/mongo/db/clientcursor.cpp @@ -26,6 +26,8 @@ * it in the license file. */ +#include "mongo/platform/basic.h" + #include "mongo/db/clientcursor.h" #include diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h index c8bd1853768..3f7e9797ae5 100644 --- a/src/mongo/db/clientcursor.h +++ b/src/mongo/db/clientcursor.h @@ -31,12 +31,10 @@ #include "mongo/db/jsobj.h" #include "mongo/db/query/plan_executor.h" #include "mongo/db/record_id.h" -#include "mongo/s/collection_metadata.h" #include "mongo/util/net/message.h" namespace mongo { -class ClientCursor; class Collection; class CursorManager; class RecoveryUnit; @@ -290,13 +288,6 @@ private: // TODO: Document. uint64_t _leftoverMaxTimeMicros; - // For chunks that are being migrated, there is a period of time when that chunks data is in - // two shards, the donor and the receiver one. That data is picked up by a cursor on the - // receiver side, even before the migration was decided. The CollectionMetadata allow one - // to inquiry if any given document of the collection belongs indeed to this shard or if it - // is coming from (or a vestige of) an ongoing migration. - CollectionMetadataPtr _collMetadata; - // Only one of these is not-NULL. RecoveryUnit* _unownedRU; std::unique_ptr _ownedRU; diff --git a/src/mongo/db/commands/cleanup_orphaned_cmd.cpp b/src/mongo/db/commands/cleanup_orphaned_cmd.cpp index 3af1687c790..8d7a3088bee 100644 --- a/src/mongo/db/commands/cleanup_orphaned_cmd.cpp +++ b/src/mongo/db/commands/cleanup_orphaned_cmd.cpp @@ -45,8 +45,8 @@ #include "mongo/db/range_arithmetic.h" #include "mongo/db/range_deleter_service.h" #include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharding_state.h" -#include "mongo/s/collection_metadata.h" #include "mongo/util/log.h" namespace { diff --git a/src/mongo/db/commands/create_indexes.cpp b/src/mongo/db/commands/create_indexes.cpp index 8db9d4576b8..890d76d0929 100644 --- a/src/mongo/db/commands/create_indexes.cpp +++ b/src/mongo/db/commands/create_indexes.cpp @@ -47,6 +47,7 @@ #include "mongo/db/op_observer.h" #include "mongo/db/ops/insert.h" #include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharding_state.h" #include "mongo/s/shard_key_pattern.h" @@ -292,8 +293,8 @@ private: invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X)); if (shardingState.enabled()) { - CollectionMetadataPtr metadata(shardingState.getCollectionMetadata(ns.toString())); - + std::shared_ptr metadata( + shardingState.getCollectionMetadata(ns.toString())); if (metadata) { ShardKeyPattern shardKeyPattern(metadata->getKeyPattern()); if (!shardKeyPattern.isUniqueIndexCompatible(newIdxKey)) { diff --git a/src/mongo/db/commands/mr.cpp b/src/mongo/db/commands/mr.cpp index 5c6f38dbf48..6c45af50775 100644 --- a/src/mongo/db/commands/mr.cpp +++ b/src/mongo/db/commands/mr.cpp @@ -55,11 +55,11 @@ #include "mongo/db/query/query_planner.h" #include "mongo/db/range_preserver.h" #include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharded_connection_info.h" #include "mongo/db/s/sharding_state.h" #include "mongo/s/catalog/catalog_cache.h" #include "mongo/s/chunk_manager.h" -#include "mongo/s/collection_metadata.h" #include "mongo/s/config.h" #include "mongo/s/d_state.h" #include "mongo/s/grid.h" diff --git a/src/mongo/db/commands/write_commands/batch_executor.cpp b/src/mongo/db/commands/write_commands/batch_executor.cpp index 428b9873133..d6475bd8cca 100644 --- a/src/mongo/db/commands/write_commands/batch_executor.cpp +++ b/src/mongo/db/commands/write_commands/batch_executor.cpp @@ -65,13 +65,12 @@ #include "mongo/db/repl/repl_settings.h" #include "mongo/db/repl/replication_coordinator_global.h" #include "mongo/db/server_parameters.h" -#include "mongo/db/service_context.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharded_connection_info.h" #include "mongo/db/s/sharding_state.h" #include "mongo/db/stats/counters.h" #include "mongo/db/stats/top.h" #include "mongo/db/write_concern.h" -#include "mongo/s/collection_metadata.h" #include "mongo/s/shard_key_pattern.h" #include "mongo/s/stale_exception.h" #include "mongo/s/write_ops/batched_upsert_detail.h" diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp index 9580fd07114..6618ef14b9b 100644 --- a/src/mongo/db/dbhelpers.cpp +++ b/src/mongo/db/dbhelpers.cpp @@ -1,32 +1,30 @@ -// dbhelpers.cpp - /** -* Copyright (C) 2008-2014 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 . -* -* 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. -*/ + * Copyright (C) 2008-2014 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 . + * + * 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::kDefault @@ -61,6 +59,7 @@ #include "mongo/db/storage_options.h" #include "mongo/db/write_concern.h" #include "mongo/db/write_concern_options.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharding_state.h" #include "mongo/s/shard_key_pattern.h" #include "mongo/util/log.h" @@ -391,10 +390,11 @@ long long Helpers::removeRange(OperationContext* txn, // in the future we might want to. verify(shardingState.enabled()); - // In write lock, so will be the most up-to-date version - CollectionMetadataPtr metadataNow = shardingState.getCollectionMetadata(ns); - bool docIsOrphan; + + // In write lock, so will be the most up-to-date version + std::shared_ptr metadataNow = + shardingState.getCollectionMetadata(ns); if (metadataNow) { ShardKeyPattern kp(metadataNow->getKeyPattern()); BSONObj key = kp.extractShardKeyFromDoc(obj); diff --git a/src/mongo/db/exec/shard_filter.cpp b/src/mongo/db/exec/shard_filter.cpp index 8b28453caf8..26fe05144dd 100644 --- a/src/mongo/db/exec/shard_filter.cpp +++ b/src/mongo/db/exec/shard_filter.cpp @@ -35,7 +35,7 @@ #include "mongo/db/exec/filter.h" #include "mongo/db/exec/scoped_timer.h" #include "mongo/db/exec/working_set_common.h" -#include "mongo/s/collection_metadata.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/s/shard_key_pattern.h" #include "mongo/stdx/memory.h" #include "mongo/util/log.h" diff --git a/src/mongo/db/ops/update_lifecycle_impl.cpp b/src/mongo/db/ops/update_lifecycle_impl.cpp index b003169edf2..fe0b9710f30 100644 --- a/src/mongo/db/ops/update_lifecycle_impl.cpp +++ b/src/mongo/db/ops/update_lifecycle_impl.cpp @@ -34,20 +34,24 @@ #include "mongo/db/catalog/database.h" #include "mongo/db/field_ref.h" #include "mongo/db/catalog/collection.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharding_state.h" #include "mongo/s/chunk_version.h" namespace mongo { + namespace { -CollectionMetadataPtr getMetadata(const NamespaceString& nsString) { + +std::shared_ptr getMetadata(const NamespaceString& nsString) { if (shardingState.enabled()) { return shardingState.getCollectionMetadata(nsString.ns()); } - return CollectionMetadataPtr(); -} + return nullptr; } +} // namespace + UpdateLifecycleImpl::UpdateLifecycleImpl(bool ignoreVersion, const NamespaceString& nsStr) : _nsString(nsStr), _shardVersion((!ignoreVersion && getMetadata(_nsString)) diff --git a/src/mongo/db/ops/update_lifecycle_impl.h b/src/mongo/db/ops/update_lifecycle_impl.h index 114ebd72ba2..d7e5616f4eb 100644 --- a/src/mongo/db/ops/update_lifecycle_impl.h +++ b/src/mongo/db/ops/update_lifecycle_impl.h @@ -31,10 +31,12 @@ #include "mongo/base/disallow_copying.h" #include "mongo/db/namespace_string.h" #include "mongo/db/ops/update_lifecycle.h" -#include "mongo/db/catalog/collection.h" +#include "mongo/s/chunk_version.h" namespace mongo { +class Collection; + class UpdateLifecycleImpl : public UpdateLifecycle { MONGO_DISALLOW_COPYING(UpdateLifecycleImpl); diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 86bcc3beddf..bd32c486c53 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -70,6 +70,7 @@ #include "mongo/db/server_options.h" #include "mongo/db/server_parameters.h" #include "mongo/db/service_context.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharding_state.h" #include "mongo/db/storage_options.h" #include "mongo/db/storage/oplog_hack.h" @@ -168,9 +169,8 @@ void fillOutPlannerParams(OperationContext* txn, // If the caller wants a shard filter, make sure we're actually sharded. if (plannerParams->options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { - CollectionMetadataPtr collMetadata = + std::shared_ptr collMetadata = shardingState.getCollectionMetadata(canonicalQuery->ns()); - if (collMetadata) { plannerParams->shardKey = collMetadata->getKeyPattern(); } else { diff --git a/src/mongo/db/range_deleter.h b/src/mongo/db/range_deleter.h index f58625f8083..39f30e17a62 100644 --- a/src/mongo/db/range_deleter.h +++ b/src/mongo/db/range_deleter.h @@ -37,7 +37,7 @@ #include "mongo/base/string_data.h" #include "mongo/db/clientcursor.h" #include "mongo/db/jsobj.h" -#include "mongo/db/operation_context.h" +#include "mongo/db/range_arithmetic.h" #include "mongo/db/write_concern_options.h" #include "mongo/stdx/mutex.h" #include "mongo/stdx/thread.h" diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript index 1ed15c8bfac..ada4ca88974 100644 --- a/src/mongo/db/s/SConscript +++ b/src/mongo/db/s/SConscript @@ -2,6 +2,22 @@ Import("env") +env.Library( + target='metadata', + source=[ + 'collection_metadata.cpp', + 'metadata_loader.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base/base', + '$BUILD_DIR/mongo/bson/bson', + '$BUILD_DIR/mongo/db/common', + '$BUILD_DIR/mongo/db/range_arithmetic', + '$BUILD_DIR/mongo/s/catalog/catalog_types', + '$BUILD_DIR/mongo/s/common', + ] +) + env.Library( target='sharding', source=[ @@ -15,3 +31,21 @@ env.Library( '$BUILD_DIR/mongo/db/common', ] ) + +env.CppUnitTest( + target='metadata_test', + source=[ + 'metadata_loader_test.cpp', + 'collection_metadata_test.cpp', + ], + LIBDEPS=[ + 'metadata', + '$BUILD_DIR/mongo/db/auth/authorization_manager_mock_init', + '$BUILD_DIR/mongo/db/common', + '$BUILD_DIR/mongo/db/coredb', + '$BUILD_DIR/mongo/dbtests/mocklib', + '$BUILD_DIR/mongo/s/catalog/legacy/catalog_manager_legacy', + '$BUILD_DIR/mongo/s/coreshard', + '$BUILD_DIR/mongo/s/mongoscore', + ] +) \ No newline at end of file diff --git a/src/mongo/db/s/collection_metadata.cpp b/src/mongo/db/s/collection_metadata.cpp new file mode 100644 index 00000000000..b8c9cfa7b7a --- /dev/null +++ b/src/mongo/db/s/collection_metadata.cpp @@ -0,0 +1,727 @@ +/** + * Copyright (C) 2012 10gen 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 . + * + * 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/db/s/collection_metadata.h" + +#include "mongo/bson/util/builder.h" +#include "mongo/s/catalog/type_chunk.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + +using std::unique_ptr; +using std::make_pair; +using std::string; +using std::vector; +using str::stream; + +CollectionMetadata::CollectionMetadata() = default; + +CollectionMetadata::~CollectionMetadata() = default; + +CollectionMetadata* CollectionMetadata::cloneMigrate(const ChunkType& chunk, + const ChunkVersion& newShardVersion, + string* errMsg) const { + // The error message string is optional. + string dummy; + if (errMsg == NULL) { + errMsg = &dummy; + } + + // Check that we have the exact chunk that will be subtracted. + if (!rangeMapContains(_chunksMap, chunk.getMin(), chunk.getMax())) { + *errMsg = stream() << "cannot remove chunk " + << rangeToString(chunk.getMin(), chunk.getMax()) + << ", this shard does not contain the chunk"; + + if (rangeMapOverlaps(_chunksMap, chunk.getMin(), chunk.getMax())) { + RangeVector overlap; + getRangeMapOverlap(_chunksMap, chunk.getMin(), chunk.getMax(), &overlap); + + *errMsg += stream() << " and it overlaps " << overlapToString(overlap); + } + + warning() << *errMsg; + return NULL; + } + + // If left with no chunks, check that the version is zero. + if (_chunksMap.size() == 1) { + if (newShardVersion.isSet()) { + *errMsg = stream() << "cannot set shard version to non-zero value " + << newShardVersion.toString() << " when removing last chunk " + << rangeToString(chunk.getMin(), chunk.getMax()); + + warning() << *errMsg; + return NULL; + } + } + // Can't move version backwards when subtracting chunks. This is what guarantees that + // no read or write would be taken once we subtract data from the current shard. + else if (newShardVersion <= _shardVersion) { + *errMsg = stream() << "cannot remove chunk " + << rangeToString(chunk.getMin(), chunk.getMax()) + << " because the new shard version " << newShardVersion.toString() + << " is not greater than the current shard version " + << _shardVersion.toString(); + + warning() << *errMsg; + return NULL; + } + + unique_ptr metadata(new CollectionMetadata); + metadata->_keyPattern = this->_keyPattern; + metadata->_keyPattern.getOwned(); + metadata->fillKeyPatternFields(); + metadata->_pendingMap = this->_pendingMap; + metadata->_chunksMap = this->_chunksMap; + metadata->_chunksMap.erase(chunk.getMin()); + metadata->_shardVersion = newShardVersion; + metadata->_collVersion = newShardVersion > _collVersion ? newShardVersion : this->_collVersion; + metadata->fillRanges(); + + invariant(metadata->isValid()); + return metadata.release(); +} + +CollectionMetadata* CollectionMetadata::clonePlusChunk(const ChunkType& chunk, + const ChunkVersion& newShardVersion, + string* errMsg) const { + // The error message string is optional. + string dummy; + if (errMsg == NULL) { + errMsg = &dummy; + } + + // It is acceptable to move version backwards (e.g., undoing a migration that went bad + // during commit) but only cloning away the last chunk may reset the version to 0. + if (!newShardVersion.isSet()) { + *errMsg = stream() << "cannot add chunk " << rangeToString(chunk.getMin(), chunk.getMax()) + << " with zero shard version"; + + warning() << *errMsg; + return NULL; + } + + invariant(chunk.getMin().woCompare(chunk.getMax()) < 0); + + // Check that there isn't any chunk on the interval to be added. + if (rangeMapOverlaps(_chunksMap, chunk.getMin(), chunk.getMax())) { + RangeVector overlap; + getRangeMapOverlap(_chunksMap, chunk.getMin(), chunk.getMax(), &overlap); + + *errMsg = stream() << "cannot add chunk " << rangeToString(chunk.getMin(), chunk.getMax()) + << " because the chunk overlaps " << overlapToString(overlap); + + warning() << *errMsg; + return NULL; + } + + unique_ptr metadata(new CollectionMetadata); + metadata->_keyPattern = this->_keyPattern; + metadata->_keyPattern.getOwned(); + metadata->fillKeyPatternFields(); + metadata->_pendingMap = this->_pendingMap; + metadata->_chunksMap = this->_chunksMap; + metadata->_chunksMap.insert(make_pair(chunk.getMin().getOwned(), chunk.getMax().getOwned())); + metadata->_shardVersion = newShardVersion; + metadata->_collVersion = newShardVersion > _collVersion ? newShardVersion : this->_collVersion; + metadata->fillRanges(); + + invariant(metadata->isValid()); + return metadata.release(); +} + +CollectionMetadata* CollectionMetadata::cloneMinusPending(const ChunkType& pending, + string* errMsg) const { + // The error message string is optional. + string dummy; + if (errMsg == NULL) { + errMsg = &dummy; + } + + // Check that we have the exact chunk that will be subtracted. + if (!rangeMapContains(_pendingMap, pending.getMin(), pending.getMax())) { + *errMsg = stream() << "cannot remove pending chunk " + << rangeToString(pending.getMin(), pending.getMax()) + << ", this shard does not contain the chunk"; + + if (rangeMapOverlaps(_pendingMap, pending.getMin(), pending.getMax())) { + RangeVector overlap; + getRangeMapOverlap(_pendingMap, pending.getMin(), pending.getMax(), &overlap); + + *errMsg += stream() << " and it overlaps " << overlapToString(overlap); + } + + warning() << *errMsg; + return NULL; + } + + unique_ptr metadata(new CollectionMetadata); + metadata->_keyPattern = this->_keyPattern; + metadata->_keyPattern.getOwned(); + metadata->fillKeyPatternFields(); + metadata->_pendingMap = this->_pendingMap; + metadata->_pendingMap.erase(pending.getMin()); + metadata->_chunksMap = this->_chunksMap; + metadata->_rangesMap = this->_rangesMap; + metadata->_shardVersion = _shardVersion; + metadata->_collVersion = _collVersion; + + invariant(metadata->isValid()); + return metadata.release(); +} + +CollectionMetadata* CollectionMetadata::clonePlusPending(const ChunkType& pending, + string* errMsg) const { + // The error message string is optional. + string dummy; + if (errMsg == NULL) { + errMsg = &dummy; + } + + if (rangeMapOverlaps(_chunksMap, pending.getMin(), pending.getMax())) { + RangeVector overlap; + getRangeMapOverlap(_chunksMap, pending.getMin(), pending.getMax(), &overlap); + + *errMsg = stream() << "cannot add pending chunk " + << rangeToString(pending.getMin(), pending.getMax()) + << " because the chunk overlaps " << overlapToString(overlap); + + warning() << *errMsg; + return NULL; + } + + unique_ptr metadata(new CollectionMetadata); + metadata->_keyPattern = this->_keyPattern; + metadata->_keyPattern.getOwned(); + metadata->fillKeyPatternFields(); + metadata->_pendingMap = this->_pendingMap; + metadata->_chunksMap = this->_chunksMap; + metadata->_rangesMap = this->_rangesMap; + metadata->_shardVersion = _shardVersion; + metadata->_collVersion = _collVersion; + + // If there are any pending chunks on the interval to be added this is ok, since pending + // chunks aren't officially tracked yet and something may have changed on servers we do not + // see yet. + // We remove any chunks we overlap, the remote request starting a chunk migration must have + // been authoritative. + + if (rangeMapOverlaps(_pendingMap, pending.getMin(), pending.getMax())) { + RangeVector pendingOverlap; + getRangeMapOverlap(_pendingMap, pending.getMin(), pending.getMax(), &pendingOverlap); + + warning() << "new pending chunk " << rangeToString(pending.getMin(), pending.getMax()) + << " overlaps existing pending chunks " << overlapToString(pendingOverlap) + << ", a migration may not have completed"; + + for (RangeVector::iterator it = pendingOverlap.begin(); it != pendingOverlap.end(); ++it) { + metadata->_pendingMap.erase(it->first); + } + } + + metadata->_pendingMap.insert(make_pair(pending.getMin(), pending.getMax())); + + invariant(metadata->isValid()); + return metadata.release(); +} + +CollectionMetadata* CollectionMetadata::cloneSplit(const ChunkType& chunk, + const vector& splitKeys, + const ChunkVersion& newShardVersion, + string* errMsg) const { + // The error message string is optional. + string dummy; + if (errMsg == NULL) { + errMsg = &dummy; + } + + // The version required in both resulting chunks could be simply an increment in the + // minor portion of the current version. However, we are enforcing uniqueness over the + // attributes of the configdb collection 'chunks'. So in practice, a + // migrate somewhere may force this split to pick up a version that has the major + // portion higher than the one that this shard has been using. + // + // TODO drop the uniqueness constraint and tighten the check below so that only the + // minor portion of version changes + if (newShardVersion <= _shardVersion) { + *errMsg = stream() << "cannot split chunk " << rangeToString(chunk.getMin(), chunk.getMax()) + << ", new shard version " << newShardVersion.toString() + << " is not greater than current version " << _shardVersion.toString(); + + warning() << *errMsg; + return NULL; + } + + // Check that we have the exact chunk that will be subtracted. + if (!rangeMapContains(_chunksMap, chunk.getMin(), chunk.getMax())) { + *errMsg = stream() << "cannot split chunk " << rangeToString(chunk.getMin(), chunk.getMax()) + << ", this shard does not contain the chunk"; + + if (rangeMapOverlaps(_chunksMap, chunk.getMin(), chunk.getMax())) { + RangeVector overlap; + getRangeMapOverlap(_chunksMap, chunk.getMin(), chunk.getMax(), &overlap); + + *errMsg += stream() << " and it overlaps " << overlapToString(overlap); + } + + warning() << *errMsg; + return NULL; + } + + // Check that the split key is valid + for (vector::const_iterator it = splitKeys.begin(); it != splitKeys.end(); ++it) { + if (!rangeContains(chunk.getMin(), chunk.getMax(), *it)) { + *errMsg = stream() << "cannot split chunk " + << rangeToString(chunk.getMin(), chunk.getMax()) << " at key " + << *it; + + warning() << *errMsg; + return NULL; + } + } + + unique_ptr metadata(new CollectionMetadata); + metadata->_keyPattern = this->_keyPattern; + metadata->_keyPattern.getOwned(); + metadata->fillKeyPatternFields(); + metadata->_pendingMap = this->_pendingMap; + metadata->_chunksMap = this->_chunksMap; + metadata->_shardVersion = newShardVersion; // will increment 2nd, 3rd,... chunks below + + BSONObj startKey = chunk.getMin(); + for (vector::const_iterator it = splitKeys.begin(); it != splitKeys.end(); ++it) { + BSONObj split = *it; + invariant(split.woCompare(startKey) > 0); + metadata->_chunksMap[startKey] = split.getOwned(); + metadata->_chunksMap.insert(make_pair(split.getOwned(), chunk.getMax().getOwned())); + metadata->_shardVersion.incMinor(); + startKey = split; + } + + metadata->_collVersion = + metadata->_shardVersion > _collVersion ? metadata->_shardVersion : _collVersion; + metadata->fillRanges(); + + invariant(metadata->isValid()); + return metadata.release(); +} + +CollectionMetadata* CollectionMetadata::cloneMerge(const BSONObj& minKey, + const BSONObj& maxKey, + const ChunkVersion& newShardVersion, + string* errMsg) const { + if (newShardVersion <= _shardVersion) { + *errMsg = stream() << "cannot merge range " << rangeToString(minKey, maxKey) + << ", new shard version " << newShardVersion.toString() + << " is not greater than current version " << _shardVersion.toString(); + + warning() << *errMsg; + return NULL; + } + + RangeVector overlap; + getRangeMapOverlap(_chunksMap, minKey, maxKey, &overlap); + + if (overlap.empty() || overlap.size() == 1) { + *errMsg = stream() << "cannot merge range " << rangeToString(minKey, maxKey) + << (overlap.empty() ? ", no chunks found in this range" + : ", only one chunk found in this range"); + + warning() << *errMsg; + return NULL; + } + + bool validStartEnd = true; + bool validNoHoles = true; + if (overlap.begin()->first.woCompare(minKey) != 0) { + // First chunk doesn't start with minKey + validStartEnd = false; + } else if (overlap.rbegin()->second.woCompare(maxKey) != 0) { + // Last chunk doesn't end with maxKey + validStartEnd = false; + } else { + // Check that there are no holes + BSONObj prevMaxKey = minKey; + for (RangeVector::iterator it = overlap.begin(); it != overlap.end(); ++it) { + if (it->first.woCompare(prevMaxKey) != 0) { + validNoHoles = false; + break; + } + prevMaxKey = it->second; + } + } + + if (!validStartEnd || !validNoHoles) { + *errMsg = stream() << "cannot merge range " << rangeToString(minKey, maxKey) + << ", overlapping chunks " << overlapToString(overlap) + << (!validStartEnd ? " do not have the same min and max key" + : " are not all adjacent"); + + warning() << *errMsg; + return NULL; + } + + unique_ptr metadata(new CollectionMetadata); + metadata->_keyPattern = this->_keyPattern; + metadata->_keyPattern.getOwned(); + metadata->fillKeyPatternFields(); + metadata->_pendingMap = this->_pendingMap; + metadata->_chunksMap = this->_chunksMap; + metadata->_rangesMap = this->_rangesMap; + metadata->_shardVersion = newShardVersion; + metadata->_collVersion = newShardVersion > _collVersion ? newShardVersion : this->_collVersion; + + for (RangeVector::iterator it = overlap.begin(); it != overlap.end(); ++it) { + metadata->_chunksMap.erase(it->first); + } + + metadata->_chunksMap.insert(make_pair(minKey, maxKey)); + + invariant(metadata->isValid()); + return metadata.release(); +} + +bool CollectionMetadata::keyBelongsToMe(const BSONObj& key) const { + // For now, collections don't move. So if the collection is not sharded, assume + // the document with the given key can be accessed. + if (_keyPattern.isEmpty()) { + return true; + } + + if (_rangesMap.size() <= 0) { + return false; + } + + RangeMap::const_iterator it = _rangesMap.upper_bound(key); + if (it != _rangesMap.begin()) + it--; + + bool good = rangeContains(it->first, it->second, key); + +#if 0 + // DISABLED because of SERVER-11175 - huge amount of logging + // Logs if the point doesn't belong here. + if ( !good ) { + log() << "bad: " << key << " " << it->first << " " << key.woCompare( it->first ) << " " + << key.woCompare( it->second ); + + for ( RangeMap::const_iterator i = _rangesMap.begin(); i != _rangesMap.end(); ++i ) { + log() << "\t" << i->first << "\t" << i->second << "\t"; + } + } +#endif + + return good; +} + +bool CollectionMetadata::keyIsPending(const BSONObj& key) const { + // If we aren't sharded, then the key is never pending (though it belongs-to-me) + if (_keyPattern.isEmpty()) { + return false; + } + + if (_pendingMap.size() <= 0) { + return false; + } + + RangeMap::const_iterator it = _pendingMap.upper_bound(key); + if (it != _pendingMap.begin()) + it--; + + bool isPending = rangeContains(it->first, it->second, key); + return isPending; +} + +bool CollectionMetadata::getNextChunk(const BSONObj& lookupKey, ChunkType* chunk) const { + RangeMap::const_iterator upperChunkIt = _chunksMap.upper_bound(lookupKey); + RangeMap::const_iterator lowerChunkIt = upperChunkIt; + + if (upperChunkIt != _chunksMap.begin()) { + --lowerChunkIt; + } else { + lowerChunkIt = _chunksMap.end(); + } + + if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(lookupKey) > 0) { + chunk->setMin(lowerChunkIt->first); + chunk->setMax(lowerChunkIt->second); + return true; + } + + if (upperChunkIt != _chunksMap.end()) { + chunk->setMin(upperChunkIt->first); + chunk->setMax(upperChunkIt->second); + return true; + } + + return false; +} + +BSONObj CollectionMetadata::toBSON() const { + BSONObjBuilder bb; + toBSON(bb); + return bb.obj(); +} + +void CollectionMetadata::toBSONChunks(BSONArrayBuilder& bb) const { + if (_chunksMap.empty()) + return; + + for (RangeMap::const_iterator it = _chunksMap.begin(); it != _chunksMap.end(); ++it) { + BSONArrayBuilder chunkBB(bb.subarrayStart()); + chunkBB.append(it->first); + chunkBB.append(it->second); + chunkBB.done(); + } +} + +void CollectionMetadata::toBSONPending(BSONArrayBuilder& bb) const { + if (_pendingMap.empty()) + return; + + for (RangeMap::const_iterator it = _pendingMap.begin(); it != _pendingMap.end(); ++it) { + BSONArrayBuilder pendingBB(bb.subarrayStart()); + pendingBB.append(it->first); + pendingBB.append(it->second); + pendingBB.done(); + } +} + +void CollectionMetadata::toBSON(BSONObjBuilder& bb) const { + _collVersion.addToBSON(bb, "collVersion"); + _shardVersion.addToBSON(bb, "shardVersion"); + bb.append("keyPattern", _keyPattern); + + BSONArrayBuilder chunksBB(bb.subarrayStart("chunks")); + toBSONChunks(chunksBB); + chunksBB.done(); + + BSONArrayBuilder pendingBB(bb.subarrayStart("pending")); + toBSONPending(pendingBB); + pendingBB.done(); +} + +bool CollectionMetadata::getNextOrphanRange(const BSONObj& origLookupKey, KeyRange* range) const { + if (_keyPattern.isEmpty()) + return false; + + BSONObj lookupKey = origLookupKey; + BSONObj maxKey = getMaxKey(); // so we don't keep rebuilding + while (lookupKey.woCompare(maxKey) < 0) { + RangeMap::const_iterator lowerChunkIt = _chunksMap.end(); + RangeMap::const_iterator upperChunkIt = _chunksMap.end(); + + if (!_chunksMap.empty()) { + upperChunkIt = _chunksMap.upper_bound(lookupKey); + lowerChunkIt = upperChunkIt; + if (upperChunkIt != _chunksMap.begin()) + --lowerChunkIt; + else + lowerChunkIt = _chunksMap.end(); + } + + // If we overlap, continue after the overlap + // TODO: Could optimize slightly by finding next non-contiguous chunk + if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(lookupKey) > 0) { + lookupKey = lowerChunkIt->second; + continue; + } + + RangeMap::const_iterator lowerPendingIt = _pendingMap.end(); + RangeMap::const_iterator upperPendingIt = _pendingMap.end(); + + if (!_pendingMap.empty()) { + upperPendingIt = _pendingMap.upper_bound(lookupKey); + lowerPendingIt = upperPendingIt; + if (upperPendingIt != _pendingMap.begin()) + --lowerPendingIt; + else + lowerPendingIt = _pendingMap.end(); + } + + // If we overlap, continue after the overlap + // TODO: Could optimize slightly by finding next non-contiguous chunk + if (lowerPendingIt != _pendingMap.end() && + lowerPendingIt->second.woCompare(lookupKey) > 0) { + lookupKey = lowerPendingIt->second; + continue; + } + + // + // We know that the lookup key is not covered by a chunk or pending range, and where the + // previous chunk and pending chunks are. Now we fill in the bounds as the closest + // bounds of the surrounding ranges in both maps. + // + + range->keyPattern = _keyPattern; + range->minKey = getMinKey(); + range->maxKey = maxKey; + + if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(range->minKey) > 0) { + range->minKey = lowerChunkIt->second; + } + + if (upperChunkIt != _chunksMap.end() && upperChunkIt->first.woCompare(range->maxKey) < 0) { + range->maxKey = upperChunkIt->first; + } + + if (lowerPendingIt != _pendingMap.end() && + lowerPendingIt->second.woCompare(range->minKey) > 0) { + range->minKey = lowerPendingIt->second; + } + + if (upperPendingIt != _pendingMap.end() && + upperPendingIt->first.woCompare(range->maxKey) < 0) { + range->maxKey = upperPendingIt->first; + } + + return true; + } + + return false; +} + +string CollectionMetadata::toString() const { + StringBuilder ss; + ss << " CollectionManager version: " << _shardVersion.toString() << " key: " << _keyPattern; + if (_rangesMap.empty()) { + return ss.str(); + } + + RangeMap::const_iterator it = _rangesMap.begin(); + ss << it->first << " -> " << it->second; + while (it != _rangesMap.end()) { + ss << ", " << it->first << " -> " << it->second; + } + return ss.str(); +} + +BSONObj CollectionMetadata::getMinKey() const { + BSONObjIterator it(_keyPattern); + BSONObjBuilder minKeyB; + while (it.more()) + minKeyB << it.next().fieldName() << MINKEY; + return minKeyB.obj(); +} + +BSONObj CollectionMetadata::getMaxKey() const { + BSONObjIterator it(_keyPattern); + BSONObjBuilder maxKeyB; + while (it.more()) + maxKeyB << it.next().fieldName() << MAXKEY; + return maxKeyB.obj(); +} + +bool CollectionMetadata::isValid() const { + if (_shardVersion > _collVersion) + return false; + if (_collVersion.majorVersion() == 0) + return false; + if (_collVersion.epoch() != _shardVersion.epoch()) + return false; + + if (_shardVersion.majorVersion() > 0) { + // Must be chunks + if (_rangesMap.size() == 0 || _chunksMap.size() == 0) + return false; + } else { + // No chunks + if (_shardVersion.minorVersion() > 0) + return false; + if (_rangesMap.size() > 0 || _chunksMap.size() > 0) + return false; + } + + return true; +} + +bool CollectionMetadata::isValidKey(const BSONObj& key) const { + BSONObjIterator it(_keyPattern); + while (it.more()) { + BSONElement next = it.next(); + if (!key.hasField(next.fieldName())) + return false; + } + return key.nFields() == _keyPattern.nFields(); +} + +void CollectionMetadata::fillRanges() { + if (_chunksMap.empty()) + return; + + // Load the chunk information, coallesceing their ranges. The version for this shard + // would be the highest version for any of the chunks. + RangeMap::const_iterator it = _chunksMap.begin(); + BSONObj min, max; + while (it != _chunksMap.end()) { + BSONObj currMin = it->first; + BSONObj currMax = it->second; + ++it; + + // coalesce the chunk's bounds in ranges if they are adjacent chunks + if (min.isEmpty()) { + min = currMin; + max = currMax; + continue; + } + if (max == currMin) { + max = currMax; + continue; + } + + _rangesMap.insert(make_pair(min, max)); + + min = currMin; + max = currMax; + } + dassert(!min.isEmpty()); + + _rangesMap.insert(make_pair(min, max)); +} + +void CollectionMetadata::fillKeyPatternFields() { + // Parse the shard keys into the states 'keys' and 'keySet' members. + BSONObjIterator patternIter = _keyPattern.begin(); + while (patternIter.more()) { + BSONElement current = patternIter.next(); + + _keyFields.mutableVector().push_back(new FieldRef); + FieldRef* const newFieldRef = _keyFields.mutableVector().back(); + newFieldRef->parse(current.fieldNameStringData()); + } +} + + +} // namespace mongo diff --git a/src/mongo/db/s/collection_metadata.h b/src/mongo/db/s/collection_metadata.h new file mode 100644 index 00000000000..0955394747d --- /dev/null +++ b/src/mongo/db/s/collection_metadata.h @@ -0,0 +1,318 @@ +/** + * Copyright (C) 2012 10gen 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 . + * + * 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/disallow_copying.h" +#include "mongo/base/owned_pointer_vector.h" +#include "mongo/db/field_ref_set.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/range_arithmetic.h" +#include "mongo/s/chunk_version.h" + +namespace mongo { + +class ChunkType; +class MetadataLoader; +class CollectionMetadata; + +typedef std::shared_ptr CollectionMetadataPtr; + +/** + * The collection metadata has metadata information about a collection, in particular the + * sharding information. It's main goal in life is to be capable of answering if a certain + * document belongs to it or not. (In some scenarios such as chunk migration, a given + * document is in a shard but cannot be accessed.) + * + * To build a collection from config data, please check the MetadataLoader. The methods + * here allow building a new incarnation of a collection's metadata based on an existing + * one (e.g, we're splitting in a given collection.). + * + * This class is immutable once constructed. + */ +class CollectionMetadata { + MONGO_DISALLOW_COPYING(CollectionMetadata); + +public: + ~CollectionMetadata(); + + // + // cloning support + // + + /** + * Returns a new metadata's instance based on 'this's state by removing a 'pending' chunk. + * + * The shard and collection version of the new metadata are unaffected. The caller owns the + * new metadata. + * + * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was + * provided. + */ + CollectionMetadata* cloneMinusPending(const ChunkType& pending, std::string* errMsg) const; + + /** + * Returns a new metadata's instance based on 'this's state by adding a 'pending' chunk. + * + * The shard and collection version of the new metadata are unaffected. The caller owns the + * new metadata. + * + * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was + * provided. + */ + CollectionMetadata* clonePlusPending(const ChunkType& pending, std::string* errMsg) const; + + /** + * Returns a new metadata's instance based on 'this's state by removing 'chunk'. + * When cloning away the last chunk, 'newShardVersion' must be zero. In any case, + * the caller owns the new metadata when the cloning is successful. + * + * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was + * provided. + */ + CollectionMetadata* cloneMigrate(const ChunkType& chunk, + const ChunkVersion& newShardVersion, + std::string* errMsg) const; + + /** + * Returns a new metadata's instance by splitting an existing 'chunk' at the points + * described by 'splitKeys'. The first resulting chunk will have 'newShardVersion' and + * subsequent one would have that with the minor version incremented at each chunk. The + * caller owns the metadata. + * + * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was + * provided. + * + * Note: 'splitKeys' must be sorted in ascending order. + */ + CollectionMetadata* cloneSplit(const ChunkType& chunk, + const std::vector& splitKeys, + const ChunkVersion& newShardVersion, + std::string* errMsg) const; + + /** + * Returns a new metadata instance by merging a key range which starts and ends at existing + * chunks into a single chunk. The range may not have holes. The resulting metadata will + * have the 'newShardVersion'. The caller owns the new metadata. + * + * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was + * provided. + */ + CollectionMetadata* cloneMerge(const BSONObj& minKey, + const BSONObj& maxKey, + const ChunkVersion& newShardVersion, + std::string* errMsg) const; + + // + // verification logic + // + + /** + * Returns true if the document key 'key' is a valid instance of a shard key for this + * metadata. The 'key' must contain exactly the same fields as the shard key pattern. + */ + bool isValidKey(const BSONObj& key) const; + + /** + * Returns true if the document key 'key' belongs to this chunkset. Recall that documents of + * an in-flight chunk migration may be present and should not be considered part of the + * collection / chunkset yet. Key must be the full shard key. + */ + bool keyBelongsToMe(const BSONObj& key) const; + + /** + * Returns true if the document key 'key' is or has been migrated to this shard, and may + * belong to us after a subsequent config reload. Key must be the full shard key. + */ + bool keyIsPending(const BSONObj& key) const; + + /** + * Given a key 'lookupKey' in the shard key range, get the next chunk which overlaps or is + * greater than this key. Returns true if a chunk exists, false otherwise. + * + * Passing a key that is not a valid shard key for this range results in undefined behavior. + */ + bool getNextChunk(const BSONObj& lookupKey, ChunkType* chunk) const; + + /** + * Given a key in the shard key range, get the next range which overlaps or is greater than + * this key. + * + * This allows us to do the following to iterate over all orphan ranges: + * + * KeyRange range; + * BSONObj lookupKey = metadata->getMinKey(); + * while( metadata->getNextOrphanRange( lookupKey, &orphanRange ) ) { + * // Do stuff with range + * lookupKey = orphanRange.maxKey; + * } + * + * @param lookupKey passing a key that does not belong to this metadata is undefined. + * @param orphanRange the output range. Note that the NS is not set. + */ + bool getNextOrphanRange(const BSONObj& lookupKey, KeyRange* orphanRange) const; + + // + // accessors + // + + ChunkVersion getCollVersion() const { + return _collVersion; + } + + ChunkVersion getShardVersion() const { + return _shardVersion; + } + + BSONObj getKeyPattern() const { + return _keyPattern; + } + + const std::vector& getKeyPatternFields() const { + return _keyFields.vector(); + } + + BSONObj getMinKey() const; + + BSONObj getMaxKey() const; + + std::size_t getNumChunks() const { + return _chunksMap.size(); + } + + std::size_t getNumPending() const { + return _pendingMap.size(); + } + + // + // reporting + // + + /** + * BSON output of the metadata information. + */ + BSONObj toBSON() const; + + /** + * BSON output of the metadata information, into a builder. + */ + void toBSON(BSONObjBuilder& bb) const; + + /** + * BSON output of the chunks metadata into a BSONArray + */ + void toBSONChunks(BSONArrayBuilder& bb) const; + + /** + * BSON output of the pending metadata into a BSONArray + */ + void toBSONPending(BSONArrayBuilder& bb) const; + + /** + * std::string output of the metadata information. + */ + std::string toString() const; + + /** + * Use the MetadataLoader to fill the empty metadata from the config server, or use + * clone*() methods to use existing metadatas to build new ones. + * + * Unless you are the MetadataLoader or a test you should probably not be using this + * directly. + */ + CollectionMetadata(); + + /** + * TESTING ONLY + * + * Returns a new metadata's instance based on 'this's state by adding 'chunk'. The new + * metadata can never be zero, though (see cloneMinus). The caller owns the new metadata. + * + * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was + * provided. + */ + CollectionMetadata* clonePlusChunk(const ChunkType& chunk, + const ChunkVersion& newShardVersion, + std::string* errMsg) const; + +private: + // Effectively, the MetadataLoader is this class's builder. So we open an exception + // and grant it friendship. + friend class MetadataLoader; + + // a version for this collection that identifies the collection incarnation (ie, a + // dropped and recreated collection with the same name would have a different version) + ChunkVersion _collVersion; + + // + // sharded state below, for when the collection gets sharded + // + + // highest ChunkVersion for which this metadata's information is accurate + ChunkVersion _shardVersion; + + // key pattern for chunks under this range + BSONObj _keyPattern; + + // A vector owning the FieldRefs parsed from the shard-key pattern of field names. + OwnedPointerVector _keyFields; + + // + // RangeMaps represent chunks by mapping the min key to the chunk's max key, allowing + // efficient lookup and intersection. + // + + // Map of ranges of chunks that are migrating but have not been confirmed added yet + RangeMap _pendingMap; + + // Map of chunks tracked by this shard + RangeMap _chunksMap; + + // A second map from a min key into a range or contiguous chunks. The map is redundant + // w.r.t. _chunkMap but we expect high chunk contiguity, especially in small + // installations. + RangeMap _rangesMap; + + /** + * Returns true if this metadata was loaded with all necessary information. + */ + bool isValid() const; + + /** + * Try to find chunks that are adjacent and record these intervals in the _rangesMap + */ + void fillRanges(); + + /** + * Creates the _keyField* local data + */ + void fillKeyPatternFields(); +}; + +} // namespace mongo diff --git a/src/mongo/db/s/collection_metadata_test.cpp b/src/mongo/db/s/collection_metadata_test.cpp new file mode 100644 index 00000000000..3df8e8b82f7 --- /dev/null +++ b/src/mongo/db/s/collection_metadata_test.cpp @@ -0,0 +1,1321 @@ +/** + * Copyright (C) 2012 10gen 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 . + * + * 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 +#include + +#include "mongo/db/jsobj.h" +#include "mongo/db/s/collection_metadata.h" +#include "mongo/db/s/metadata_loader.h" +#include "mongo/dbtests/mock/mock_conn_registry.h" +#include "mongo/dbtests/mock/mock_remote_db_server.h" +#include "mongo/s/catalog/legacy/catalog_manager_legacy.h" +#include "mongo/s/catalog/type_chunk.h" +#include "mongo/s/catalog/type_collection.h" +#include "mongo/s/chunk_version.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/net/hostandport.h" + +namespace mongo { +namespace { + +using std::make_pair; +using std::string; +using std::unique_ptr; +using std::vector; + +const std::string CONFIG_HOST_PORT = "$dummy_config:27017"; + +class NoChunkFixture : public mongo::unittest::Test { +protected: + void setUp() { + _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(_dummyConfig.get()); + + OID epoch = OID::gen(); + + CollectionType collType; + collType.setNs(NamespaceString{"test.foo"}); + collType.setKeyPattern(BSON("a" << 1)); + collType.setUnique(false); + collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + collType.setEpoch(epoch); + ASSERT_OK(collType.validate()); + + _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); + + // Need a chunk on another shard, otherwise the chunks are invalid in general and we + // can't load metadata + ChunkType chunkType; + chunkType.setNS(NamespaceString{"test.foo"}.ns()); + chunkType.setShard("shard0001"); + chunkType.setMin(BSON("a" << MINKEY)); + chunkType.setMax(BSON("a" << MAXKEY)); + chunkType.setVersion(ChunkVersion(1, 0, epoch)); + chunkType.setName(OID::gen().toString()); + ASSERT_OK(collType.validate()); + + _dummyConfig->insert(ChunkType::ConfigNS, chunkType.toBSON()); + + ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); + ASSERT(configLoc.isValid()); + CatalogManagerLegacy catalogManager; + catalogManager.init(configLoc); + + MetadataLoader loader; + Status status = loader.makeCollectionMetadata( + &catalogManager, "test.foo", "shard0000", NULL, &_metadata); + ASSERT_OK(status); + ASSERT_EQUALS(0u, _metadata.getNumChunks()); + } + + void tearDown() { + MockConnRegistry::get()->clear(); + } + + const CollectionMetadata& getCollMetadata() const { + return _metadata; + } + +private: + unique_ptr _dummyConfig; + CollectionMetadata _metadata; +}; + +TEST_F(NoChunkFixture, BasicBelongsToMe) { + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY))); + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 10))); +} + +TEST_F(NoChunkFixture, CompoundKeyBelongsToMe) { + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 1 << "b" << 2))); +} + +TEST_F(NoChunkFixture, IsKeyValid) { + ASSERT_TRUE(getCollMetadata().isValidKey(BSON("a" + << "abcde"))); + ASSERT_TRUE(getCollMetadata().isValidKey(BSON("a" << 3))); + ASSERT_FALSE(getCollMetadata().isValidKey(BSON("a" + << "abcde" + << "b" << 1))); + ASSERT_FALSE(getCollMetadata().isValidKey(BSON("c" + << "abcde"))); +} + +TEST_F(NoChunkFixture, getNextFromEmpty) { + ChunkType nextChunk; + ASSERT(!getCollMetadata().getNextChunk(getCollMetadata().getMinKey(), &nextChunk)); +} + +TEST_F(NoChunkFixture, FirstChunkClonePlus) { + ChunkType chunk; + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + string errMsg; + const ChunkVersion version(99, 0, OID()); + unique_ptr cloned( + getCollMetadata().clonePlusChunk(chunk, version, &errMsg)); + + ASSERT(errMsg.empty()); + ASSERT_EQUALS(1u, cloned->getNumChunks()); + ASSERT_EQUALS(cloned->getShardVersion().toLong(), version.toLong()); + ASSERT_EQUALS(cloned->getCollVersion().toLong(), version.toLong()); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 15))); +} + +TEST_F(NoChunkFixture, MustHaveVersionForFirstChunk) { + ChunkType chunk; + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + string errMsg; + unique_ptr cloned( + getCollMetadata() // br + .clonePlusChunk(chunk, ChunkVersion(0, 0, OID()), &errMsg)); + + ASSERT(cloned == NULL); + ASSERT_FALSE(errMsg.empty()); +} + +TEST_F(NoChunkFixture, NoPendingChunks) { + ASSERT(!getCollMetadata().keyIsPending(BSON("a" << 15))); + ASSERT(!getCollMetadata().keyIsPending(BSON("a" << 25))); +} + +TEST_F(NoChunkFixture, FirstPendingChunk) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(cloned->keyIsPending(BSON("a" << 15))); + ASSERT(!cloned->keyIsPending(BSON("a" << 25))); + ASSERT(cloned->keyIsPending(BSON("a" << 10))); + ASSERT(!cloned->keyIsPending(BSON("a" << 20))); +} + +TEST_F(NoChunkFixture, EmptyMultiPendingChunk) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + chunk.setMin(BSON("a" << 40)); + chunk.setMax(BSON("a" << 50)); + + cloned.reset(cloned->clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(cloned->keyIsPending(BSON("a" << 15))); + ASSERT(!cloned->keyIsPending(BSON("a" << 25))); + ASSERT(cloned->keyIsPending(BSON("a" << 45))); + ASSERT(!cloned->keyIsPending(BSON("a" << 55))); +} + +TEST_F(NoChunkFixture, MinusPendingChunk) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + cloned.reset(cloned->cloneMinusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(!cloned->keyIsPending(BSON("a" << 15))); + ASSERT(!cloned->keyIsPending(BSON("a" << 25))); +} + +TEST_F(NoChunkFixture, OverlappingPendingChunk) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 30)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + chunk.setMin(BSON("a" << 20)); + chunk.setMax(BSON("a" << 40)); + + cloned.reset(cloned->clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(!cloned->keyIsPending(BSON("a" << 15))); + ASSERT(cloned->keyIsPending(BSON("a" << 25))); + ASSERT(cloned->keyIsPending(BSON("a" << 35))); + ASSERT(!cloned->keyIsPending(BSON("a" << 45))); +} + +TEST_F(NoChunkFixture, OverlappingPendingChunks) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 30)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + chunk.setMin(BSON("a" << 30)); + chunk.setMax(BSON("a" << 50)); + + cloned.reset(cloned->clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + chunk.setMin(BSON("a" << 20)); + chunk.setMax(BSON("a" << 40)); + + cloned.reset(cloned->clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(!cloned->keyIsPending(BSON("a" << 15))); + ASSERT(cloned->keyIsPending(BSON("a" << 25))); + ASSERT(cloned->keyIsPending(BSON("a" << 35))); + ASSERT(!cloned->keyIsPending(BSON("a" << 45))); +} + +TEST_F(NoChunkFixture, MinusInvalidPendingChunk) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 30)); + + cloned.reset(getCollMetadata().cloneMinusPending(chunk, &errMsg)); + + ASSERT_NOT_EQUALS(errMsg, ""); + ASSERT(cloned == NULL); +} + +TEST_F(NoChunkFixture, MinusOverlappingPendingChunk) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 30)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + chunk.setMin(BSON("a" << 15)); + chunk.setMax(BSON("a" << 35)); + + cloned.reset(cloned->cloneMinusPending(chunk, &errMsg)); + + ASSERT_NOT_EQUALS(errMsg, ""); + ASSERT(cloned == NULL); +} + +TEST_F(NoChunkFixture, PlusChunkWithPending) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(cloned->keyIsPending(BSON("a" << 15))); + ASSERT(!cloned->keyIsPending(BSON("a" << 25))); + + chunk.setMin(BSON("a" << 20)); + chunk.setMax(BSON("a" << 30)); + + cloned.reset(cloned->clonePlusChunk( + chunk, ChunkVersion(1, 0, cloned->getCollVersion().epoch()), &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(cloned->keyIsPending(BSON("a" << 15))); + ASSERT(!cloned->keyIsPending(BSON("a" << 25))); +} + +TEST_F(NoChunkFixture, MergeChunkEmpty) { + string errMsg; + unique_ptr cloned; + + cloned.reset(getCollMetadata().cloneMerge( + BSON("a" << 15), BSON("a" << 25), ChunkVersion(1, 0, OID::gen()), &errMsg)); + + ASSERT_NOT_EQUALS(errMsg, ""); + ASSERT(cloned == NULL); +} + +TEST_F(NoChunkFixture, OrphanedDataRangeBegin) { + const CollectionMetadata& metadata = getCollMetadata(); + + KeyRange keyRange; + BSONObj lookupKey = metadata.getMinKey(); + ASSERT(metadata.getNextOrphanRange(lookupKey, &keyRange)); + + ASSERT(keyRange.minKey.woCompare(metadata.getMinKey()) == 0); + ASSERT(keyRange.maxKey.woCompare(metadata.getMaxKey()) == 0); + + // Make sure we don't have any more ranges + ASSERT(!metadata.getNextOrphanRange(keyRange.maxKey, &keyRange)); +} + +TEST_F(NoChunkFixture, OrphanedDataRangeMiddle) { + const CollectionMetadata& metadata = getCollMetadata(); + + KeyRange keyRange; + BSONObj lookupKey = BSON("a" << 20); + ASSERT(metadata.getNextOrphanRange(lookupKey, &keyRange)); + + ASSERT(keyRange.minKey.woCompare(metadata.getMinKey()) == 0); + ASSERT(keyRange.maxKey.woCompare(metadata.getMaxKey()) == 0); + ASSERT(keyRange.keyPattern.woCompare(metadata.getKeyPattern()) == 0); + + // Make sure we don't have any more ranges + ASSERT(!metadata.getNextOrphanRange(keyRange.maxKey, &keyRange)); +} + +TEST_F(NoChunkFixture, OrphanedDataRangeEnd) { + const CollectionMetadata& metadata = getCollMetadata(); + + KeyRange keyRange; + ASSERT(!metadata.getNextOrphanRange(metadata.getMaxKey(), &keyRange)); +} + +TEST_F(NoChunkFixture, PendingOrphanedDataRanges) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + ASSERT_EQUALS(errMsg, string("")); + ASSERT(cloned != NULL); + + KeyRange keyRange; + ASSERT(cloned->getNextOrphanRange(cloned->getMinKey(), &keyRange)); + ASSERT(keyRange.minKey.woCompare(cloned->getMinKey()) == 0); + ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10)) == 0); + ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0); + + ASSERT(cloned->getNextOrphanRange(keyRange.maxKey, &keyRange)); + ASSERT(keyRange.minKey.woCompare(BSON("a" << 20)) == 0); + ASSERT(keyRange.maxKey.woCompare(cloned->getMaxKey()) == 0); + ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0); + + ASSERT(!cloned->getNextOrphanRange(keyRange.maxKey, &keyRange)); +} + +/** + * Fixture with single chunk containing: + * [10->20) + */ +class SingleChunkFixture : public mongo::unittest::Test { +protected: + void setUp() { + _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(_dummyConfig.get()); + + OID epoch = OID::gen(); + ChunkVersion chunkVersion = ChunkVersion(1, 0, epoch); + + CollectionType collType; + collType.setNs(NamespaceString{"test.foo"}); + collType.setKeyPattern(BSON("a" << 1)); + collType.setUnique(false); + collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + collType.setEpoch(epoch); + _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); + + BSONObj fooSingle = BSON( + ChunkType::name("test.foo-a_10") + << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << 10)) + << ChunkType::max(BSON("a" << 20)) + << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(chunkVersion.toLong())) + << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000")); + _dummyConfig->insert(ChunkType::ConfigNS, fooSingle); + + ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); + ASSERT(configLoc.isValid()); + CatalogManagerLegacy catalogManager; + catalogManager.init(configLoc); + + MetadataLoader loader; + Status status = loader.makeCollectionMetadata( + &catalogManager, "test.foo", "shard0000", NULL, &_metadata); + ASSERT_OK(status); + } + + void tearDown() { + MockConnRegistry::get()->clear(); + } + + const CollectionMetadata& getCollMetadata() const { + return _metadata; + } + +private: + unique_ptr _dummyConfig; + CollectionMetadata _metadata; +}; + +TEST_F(SingleChunkFixture, BasicBelongsToMe) { + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 10))); + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 15))); + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 19))); +} + +TEST_F(SingleChunkFixture, DoesntBelongsToMe) { + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 0))); + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 9))); + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 20))); + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 1234))); + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY))); + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY))); +} + +TEST_F(SingleChunkFixture, CompoudKeyBelongsToMe) { + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 15 << "a" << 14))); +} + +TEST_F(SingleChunkFixture, getNextFromEmpty) { + ChunkType nextChunk; + ASSERT(getCollMetadata().getNextChunk(getCollMetadata().getMinKey(), &nextChunk)); + ASSERT_EQUALS(0, nextChunk.getMin().woCompare(BSON("a" << 10))); + ASSERT_EQUALS(0, nextChunk.getMax().woCompare(BSON("a" << 20))); +} + +TEST_F(SingleChunkFixture, GetLastChunkIsFalse) { + ChunkType nextChunk; + ASSERT(!getCollMetadata().getNextChunk(getCollMetadata().getMaxKey(), &nextChunk)); +} + +TEST_F(SingleChunkFixture, LastChunkCloneMinus) { + ChunkType chunk; + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + string errMsg; + const ChunkVersion zeroVersion(0, 0, getCollMetadata().getShardVersion().epoch()); + unique_ptr cloned( + getCollMetadata().cloneMigrate(chunk, zeroVersion, &errMsg)); + + ASSERT(errMsg.empty()); + ASSERT_EQUALS(0u, cloned->getNumChunks()); + ASSERT_EQUALS(cloned->getShardVersion().toLong(), zeroVersion.toLong()); + ASSERT_EQUALS(cloned->getCollVersion().toLong(), getCollMetadata().getCollVersion().toLong()); + ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 15))); +} + +TEST_F(SingleChunkFixture, LastChunkMinusCantHaveNonZeroVersion) { + ChunkType chunk; + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + string errMsg; + ChunkVersion version(99, 0, OID()); + unique_ptr cloned(getCollMetadata().cloneMigrate(chunk, version, &errMsg)); + + ASSERT(cloned == NULL); + ASSERT_FALSE(errMsg.empty()); +} + +TEST_F(SingleChunkFixture, PlusPendingChunk) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 20)); + chunk.setMax(BSON("a" << 30)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(cloned->keyBelongsToMe(BSON("a" << 15))); + ASSERT(!cloned->keyBelongsToMe(BSON("a" << 25))); + ASSERT(!cloned->keyIsPending(BSON("a" << 15))); + ASSERT(cloned->keyIsPending(BSON("a" << 25))); +} + +TEST_F(SingleChunkFixture, PlusOverlapPendingChunk) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_NOT_EQUALS(errMsg, ""); + ASSERT(cloned == NULL); +} + +TEST_F(SingleChunkFixture, MinusChunkWithPending) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 20)); + chunk.setMax(BSON("a" << 30)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(cloned->keyIsPending(BSON("a" << 25))); + ASSERT(!cloned->keyIsPending(BSON("a" << 35))); + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + cloned.reset( + cloned->cloneMigrate(chunk, ChunkVersion(0, 0, cloned->getCollVersion().epoch()), &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(cloned->keyIsPending(BSON("a" << 25))); + ASSERT(!cloned->keyIsPending(BSON("a" << 35))); +} + +TEST_F(SingleChunkFixture, SingleSplit) { + ChunkVersion version; + getCollMetadata().getCollVersion().cloneTo(&version); + version.incMinor(); + + ChunkType chunk; + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + vector splitPoints; + splitPoints.push_back(BSON("a" << 14)); + + string errMsg; + unique_ptr cloned( + getCollMetadata().cloneSplit(chunk, splitPoints, version, &errMsg)); + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ChunkVersion newVersion(cloned->getCollVersion()); + ASSERT_EQUALS(version.epoch(), newVersion.epoch()); + ASSERT_EQUALS(version.majorVersion(), newVersion.majorVersion()); + ASSERT_EQUALS(version.minorVersion() + 1, newVersion.minorVersion()); + + ASSERT(cloned->getNextChunk(BSON("a" << MINKEY), &chunk)); + ASSERT(chunk.getMin().woCompare(BSON("a" << 10)) == 0); + ASSERT(chunk.getMax().woCompare(BSON("a" << 14)) == 0); + + ASSERT(cloned->getNextChunk(BSON("a" << 14), &chunk)); + ASSERT(chunk.getMin().woCompare(BSON("a" << 14)) == 0); + ASSERT(chunk.getMax().woCompare(BSON("a" << 20)) == 0); + + ASSERT_FALSE(cloned->getNextChunk(BSON("a" << 20), &chunk)); +} + +TEST_F(SingleChunkFixture, MultiSplit) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + vector splitPoints; + splitPoints.push_back(BSON("a" << 14)); + splitPoints.push_back(BSON("a" << 16)); + + ChunkVersion version; + getCollMetadata().getCollVersion().cloneTo(&version); + version.incMinor(); + + cloned.reset(getCollMetadata().cloneSplit(chunk, splitPoints, version, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ChunkVersion newVersion(cloned->getCollVersion()); + ASSERT_EQUALS(version.epoch(), newVersion.epoch()); + ASSERT_EQUALS(version.majorVersion(), newVersion.majorVersion()); + ASSERT_EQUALS(version.minorVersion() + 2, newVersion.minorVersion()); + + ASSERT(cloned->getNextChunk(BSON("a" << MINKEY), &chunk)); + ASSERT(chunk.getMin().woCompare(BSON("a" << 10)) == 0); + ASSERT(chunk.getMax().woCompare(BSON("a" << 14)) == 0); + + ASSERT(cloned->getNextChunk(BSON("a" << 14), &chunk)); + ASSERT(chunk.getMin().woCompare(BSON("a" << 14)) == 0); + ASSERT(chunk.getMax().woCompare(BSON("a" << 16)) == 0); + + ASSERT(cloned->getNextChunk(BSON("a" << 16), &chunk)); + ASSERT(chunk.getMin().woCompare(BSON("a" << 16)) == 0); + ASSERT(chunk.getMax().woCompare(BSON("a" << 20)) == 0); + + ASSERT_FALSE(cloned->getNextChunk(BSON("a" << 20), &chunk)); +} + +TEST_F(SingleChunkFixture, SplitChunkWithPending) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 20)); + chunk.setMax(BSON("a" << 30)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(cloned->keyIsPending(BSON("a" << 25))); + ASSERT(!cloned->keyIsPending(BSON("a" << 35))); + + chunk.setMin(BSON("a" << 10)); + chunk.setMax(BSON("a" << 20)); + + vector splitPoints; + splitPoints.push_back(BSON("a" << 14)); + splitPoints.push_back(BSON("a" << 16)); + + cloned.reset(cloned->cloneSplit(chunk, + splitPoints, + ChunkVersion(cloned->getCollVersion().majorVersion() + 1, + 0, + cloned->getCollVersion().epoch()), + &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + + ASSERT(cloned->keyIsPending(BSON("a" << 25))); + ASSERT(!cloned->keyIsPending(BSON("a" << 35))); +} + + +TEST_F(SingleChunkFixture, MergeChunkSingle) { + string errMsg; + unique_ptr cloned; + + cloned.reset(getCollMetadata().cloneMerge( + BSON("a" << 10), BSON("a" << 20), ChunkVersion(2, 0, OID::gen()), &errMsg)); + + ASSERT_NOT_EQUALS(errMsg, ""); + ASSERT(cloned == NULL); +} + +TEST_F(SingleChunkFixture, ChunkOrphanedDataRanges) { + KeyRange keyRange; + ASSERT(getCollMetadata().getNextOrphanRange(getCollMetadata().getMinKey(), &keyRange)); + ASSERT(keyRange.minKey.woCompare(getCollMetadata().getMinKey()) == 0); + ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10)) == 0); + ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); + + ASSERT(getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); + ASSERT(keyRange.minKey.woCompare(BSON("a" << 20)) == 0); + ASSERT(keyRange.maxKey.woCompare(getCollMetadata().getMaxKey()) == 0); + ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); + + ASSERT(!getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); +} + +/** + * Fixture with single chunk containing: + * [(min, min)->(max, max)) + */ +class SingleChunkMinMaxCompoundKeyFixture : public mongo::unittest::Test { +protected: + void setUp() { + _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(_dummyConfig.get()); + + OID epoch = OID::gen(); + ChunkVersion chunkVersion = ChunkVersion(1, 0, epoch); + + CollectionType collType; + collType.setNs(NamespaceString{"test.foo"}); + collType.setKeyPattern(BSON("a" << 1)); + collType.setUnique(false); + collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + collType.setEpoch(epoch); + _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); + + BSONObj fooSingle = BSON( + ChunkType::name("test.foo-a_MinKey") + << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << MINKEY << "b" << MINKEY)) + << ChunkType::max(BSON("a" << MAXKEY << "b" << MAXKEY)) + << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(chunkVersion.toLong())) + << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000")); + _dummyConfig->insert(ChunkType::ConfigNS, fooSingle); + + ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); + ASSERT(configLoc.isValid()); + CatalogManagerLegacy catalogManager; + catalogManager.init(configLoc); + + MetadataLoader loader; + Status status = loader.makeCollectionMetadata( + &catalogManager, "test.foo", "shard0000", NULL, &_metadata); + ASSERT_OK(status); + } + + void tearDown() { + MockConnRegistry::get()->clear(); + } + + const CollectionMetadata& getCollMetadata() const { + return _metadata; + } + +private: + unique_ptr _dummyConfig; + CollectionMetadata _metadata; +}; + +// Note: no tests for single key belongsToMe because they are not allowed +// if shard key is compound. + +TEST_F(SingleChunkMinMaxCompoundKeyFixture, CompoudKeyBelongsToMe) { + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY << "b" << MINKEY))); + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY << "b" << MAXKEY))); + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY << "b" << 10))); + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 10 << "b" << 20))); +} + +/** + * Fixture with chunks: + * [(10, 0)->(20, 0)), [(30, 0)->(40, 0)) + */ +class TwoChunksWithGapCompoundKeyFixture : public mongo::unittest::Test { +protected: + void setUp() { + _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(_dummyConfig.get()); + + OID epoch = OID::gen(); + ChunkVersion chunkVersion = ChunkVersion(1, 0, epoch); + + CollectionType collType; + collType.setNs(NamespaceString{"test.foo"}); + collType.setKeyPattern(BSON("a" << 1)); + collType.setUnique(false); + collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + collType.setEpoch(epoch); + _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); + + _dummyConfig->insert( + ChunkType::ConfigNS, + BSON(ChunkType::name("test.foo-a_10") + << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << 10 << "b" << 0)) + << ChunkType::max(BSON("a" << 20 << "b" << 0)) + << ChunkType::DEPRECATED_lastmod( + Date_t::fromMillisSinceEpoch(chunkVersion.toLong())) + << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000"))); + + _dummyConfig->insert( + ChunkType::ConfigNS, + BSON(ChunkType::name("test.foo-a_10") + << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << 30 << "b" << 0)) + << ChunkType::max(BSON("a" << 40 << "b" << 0)) + << ChunkType::DEPRECATED_lastmod( + Date_t::fromMillisSinceEpoch(chunkVersion.toLong())) + << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000"))); + + ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); + ASSERT(configLoc.isValid()); + CatalogManagerLegacy catalogManager; + catalogManager.init(configLoc); + + MetadataLoader loader; + Status status = loader.makeCollectionMetadata( + &catalogManager, "test.foo", "shard0000", NULL, &_metadata); + ASSERT_OK(status); + } + + void tearDown() { + MockConnRegistry::get()->clear(); + } + + const CollectionMetadata& getCollMetadata() const { + return _metadata; + } + +private: + unique_ptr _dummyConfig; + CollectionMetadata _metadata; +}; + +TEST_F(TwoChunksWithGapCompoundKeyFixture, ClonePlusBasic) { + ChunkType chunk; + chunk.setMin(BSON("a" << 40 << "b" << 0)); + chunk.setMax(BSON("a" << 50 << "b" << 0)); + + string errMsg; + ChunkVersion version(1, 0, getCollMetadata().getShardVersion().epoch()); + unique_ptr cloned( + getCollMetadata().clonePlusChunk(chunk, version, &errMsg)); + + ASSERT(errMsg.empty()); + ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); + ASSERT_EQUALS(3u, cloned->getNumChunks()); + + // TODO: test maxShardVersion, maxCollVersion + + ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 25 << "b" << 0))); + ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 29 << "b" << 0))); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 30 << "b" << 0))); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 45 << "b" << 0))); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 49 << "b" << 0))); + ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 50 << "b" << 0))); +} + +TEST_F(TwoChunksWithGapCompoundKeyFixture, ClonePlusOverlappingRange) { + ChunkType chunk; + chunk.setMin(BSON("a" << 15 << "b" << 0)); + chunk.setMax(BSON("a" << 25 << "b" << 0)); + + string errMsg; + unique_ptr cloned( + getCollMetadata().clonePlusChunk(chunk, ChunkVersion(1, 0, OID()), &errMsg)); + ASSERT(cloned == NULL); + ASSERT_FALSE(errMsg.empty()); + ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); +} + +TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneMinusBasic) { + ChunkType chunk; + chunk.setMin(BSON("a" << 10 << "b" << 0)); + chunk.setMax(BSON("a" << 20 << "b" << 0)); + + string errMsg; + ChunkVersion version(2, 0, OID()); + unique_ptr cloned(getCollMetadata().cloneMigrate(chunk, version, &errMsg)); + + ASSERT(errMsg.empty()); + ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); + ASSERT_EQUALS(1u, cloned->getNumChunks()); + + // TODO: test maxShardVersion, maxCollVersion + + ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 5 << "b" << 0))); + ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 15 << "b" << 0))); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 30 << "b" << 0))); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 35 << "b" << 0))); + ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 40 << "b" << 0))); +} + +TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneMinusNonExisting) { + ChunkType chunk; + chunk.setMin(BSON("a" << 25 << "b" << 0)); + chunk.setMax(BSON("a" << 28 << "b" << 0)); + + string errMsg; + unique_ptr cloned( + getCollMetadata().cloneMigrate(chunk, ChunkVersion(1, 0, OID()), &errMsg)); + ASSERT(cloned == NULL); + ASSERT_FALSE(errMsg.empty()); + ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); +} + +TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitBasic) { + const BSONObj min(BSON("a" << 10 << "b" << 0)); + const BSONObj max(BSON("a" << 20 << "b" << 0)); + + ChunkType chunk; + chunk.setMin(min); + chunk.setMax(max); + + const BSONObj split1(BSON("a" << 15 << "b" << 0)); + const BSONObj split2(BSON("a" << 18 << "b" << 0)); + vector splitKeys; + splitKeys.push_back(split1); + splitKeys.push_back(split2); + ChunkVersion version(1, 99, OID()); // first chunk 1|99 , second 1|100 + + string errMsg; + unique_ptr cloned( + getCollMetadata().cloneSplit(chunk, splitKeys, version, &errMsg)); + + version.incMinor(); /* second chunk 1|100, first split point */ + version.incMinor(); /* third chunk 1|101, second split point */ + ASSERT_EQUALS(cloned->getShardVersion().toLong(), version.toLong() /* 1|101 */); + ASSERT_EQUALS(cloned->getCollVersion().toLong(), version.toLong()); + ASSERT_EQUALS(getCollMetadata().getNumChunks(), 2u); + ASSERT_EQUALS(cloned->getNumChunks(), 4u); + ASSERT(cloned->keyBelongsToMe(min)); + ASSERT(cloned->keyBelongsToMe(split1)); + ASSERT(cloned->keyBelongsToMe(split2)); + ASSERT(!cloned->keyBelongsToMe(max)); +} + +TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitOutOfRangeSplitPoint) { + ChunkType chunk; + chunk.setMin(BSON("a" << 10 << "b" << 0)); + chunk.setMax(BSON("a" << 20 << "b" << 0)); + + vector splitKeys; + splitKeys.push_back(BSON("a" << 5 << "b" << 0)); + + string errMsg; + unique_ptr cloned( + getCollMetadata().cloneSplit(chunk, splitKeys, ChunkVersion(1, 0, OID()), &errMsg)); + + ASSERT(cloned == NULL); + ASSERT_FALSE(errMsg.empty()); + ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); +} + +TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitBadChunkRange) { + const BSONObj min(BSON("a" << 10 << "b" << 0)); + const BSONObj max(BSON("a" << 25 << "b" << 0)); + + ChunkType chunk; + chunk.setMin(BSON("a" << 10 << "b" << 0)); + chunk.setMax(BSON("a" << 25 << "b" << 0)); + + vector splitKeys; + splitKeys.push_back(BSON("a" << 15 << "b" << 0)); + + string errMsg; + unique_ptr cloned( + getCollMetadata().cloneSplit(chunk, splitKeys, ChunkVersion(1, 0, OID()), &errMsg)); + + ASSERT(cloned == NULL); + ASSERT_FALSE(errMsg.empty()); + ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); +} + +TEST_F(TwoChunksWithGapCompoundKeyFixture, ChunkGapOrphanedDataRanges) { + KeyRange keyRange; + ASSERT(getCollMetadata().getNextOrphanRange(getCollMetadata().getMinKey(), &keyRange)); + ASSERT(keyRange.minKey.woCompare(getCollMetadata().getMinKey()) == 0); + ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10 << "b" << 0)) == 0); + ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); + + ASSERT(getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); + ASSERT(keyRange.minKey.woCompare(BSON("a" << 20 << "b" << 0)) == 0); + ASSERT(keyRange.maxKey.woCompare(BSON("a" << 30 << "b" << 0)) == 0); + ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); + + ASSERT(getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); + ASSERT(keyRange.minKey.woCompare(BSON("a" << 40 << "b" << 0)) == 0); + ASSERT(keyRange.maxKey.woCompare(getCollMetadata().getMaxKey()) == 0); + ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); + + ASSERT(!getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); +} + +TEST_F(TwoChunksWithGapCompoundKeyFixture, ChunkGapAndPendingOrphanedDataRanges) { + string errMsg; + ChunkType chunk; + unique_ptr cloned; + + chunk.setMin(BSON("a" << 20 << "b" << 0)); + chunk.setMax(BSON("a" << 30 << "b" << 0)); + + cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); + ASSERT_EQUALS(errMsg, string("")); + ASSERT(cloned != NULL); + + KeyRange keyRange; + ASSERT(cloned->getNextOrphanRange(cloned->getMinKey(), &keyRange)); + ASSERT(keyRange.minKey.woCompare(cloned->getMinKey()) == 0); + ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10 << "b" << 0)) == 0); + ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0); + + ASSERT(cloned->getNextOrphanRange(keyRange.maxKey, &keyRange)); + ASSERT(keyRange.minKey.woCompare(BSON("a" << 40 << "b" << 0)) == 0); + ASSERT(keyRange.maxKey.woCompare(cloned->getMaxKey()) == 0); + ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0); + + ASSERT(!cloned->getNextOrphanRange(keyRange.maxKey, &keyRange)); +} + +/** + * Fixture with chunk containing: + * [min->10) , [10->20) , , [30->max) + */ +class ThreeChunkWithRangeGapFixture : public mongo::unittest::Test { +protected: + void setUp() { + _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(_dummyConfig.get()); + + OID epoch(OID::gen()); + + { + CollectionType collType; + collType.setNs(NamespaceString{"x.y"}); + collType.setKeyPattern(BSON("a" << 1)); + collType.setUnique(false); + collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + collType.setEpoch(epoch); + _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); + } + + { + ChunkVersion version(1, 1, epoch); + _dummyConfig->insert(ChunkType::ConfigNS, + BSON(ChunkType::name("x.y-a_MinKey") + << ChunkType::ns("x.y") << ChunkType::min(BSON("a" << MINKEY)) + << ChunkType::max(BSON("a" << 10)) + << ChunkType::DEPRECATED_lastmod( + Date_t::fromMillisSinceEpoch(version.toLong())) + << ChunkType::DEPRECATED_epoch(version.epoch()) + << ChunkType::shard("shard0000"))); + } + + { + ChunkVersion version(1, 3, epoch); + _dummyConfig->insert(ChunkType::ConfigNS, + BSON(ChunkType::name("x.y-a_10") + << ChunkType::ns("x.y") << ChunkType::min(BSON("a" << 10)) + << ChunkType::max(BSON("a" << 20)) + << ChunkType::DEPRECATED_lastmod( + Date_t::fromMillisSinceEpoch(version.toLong())) + << ChunkType::DEPRECATED_epoch(version.epoch()) + << ChunkType::shard("shard0000"))); + } + + { + ChunkVersion version(1, 2, epoch); + _dummyConfig->insert(ChunkType::ConfigNS, + BSON(ChunkType::name("x.y-a_30") + << ChunkType::ns("x.y") << ChunkType::min(BSON("a" << 30)) + << ChunkType::max(BSON("a" << MAXKEY)) + << ChunkType::DEPRECATED_lastmod( + Date_t::fromMillisSinceEpoch(version.toLong())) + << ChunkType::DEPRECATED_epoch(version.epoch()) + << ChunkType::shard("shard0000"))); + } + + ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); + ASSERT(configLoc.isValid()); + CatalogManagerLegacy catalogManager; + catalogManager.init(configLoc); + + MetadataLoader loader; + Status status = loader.makeCollectionMetadata( + &catalogManager, "test.foo", "shard0000", NULL, &_metadata); + ASSERT_OK(status); + } + + void tearDown() { + MockConnRegistry::get()->clear(); + } + + const CollectionMetadata& getCollMetadata() const { + return _metadata; + } + +private: + unique_ptr _dummyConfig; + CollectionMetadata _metadata; +}; + +TEST_F(ThreeChunkWithRangeGapFixture, ShardOwnsDoc) { + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 5))); + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 10))); + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 30))); + ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 40))); +} + +TEST_F(ThreeChunkWithRangeGapFixture, ShardDoesntOwnDoc) { + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 25))); + ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY))); +} + +TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromEmpty) { + ChunkType nextChunk; + ASSERT(getCollMetadata().getNextChunk(getCollMetadata().getMinKey(), &nextChunk)); + ASSERT_EQUALS(0, nextChunk.getMin().woCompare(BSON("a" << MINKEY))); + ASSERT_EQUALS(0, nextChunk.getMax().woCompare(BSON("a" << 10))); +} + +TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromMiddle) { + ChunkType nextChunk; + ASSERT(getCollMetadata().getNextChunk(BSON("a" << 20), &nextChunk)); + ASSERT_EQUALS(0, nextChunk.getMin().woCompare(BSON("a" << 30))); + ASSERT_EQUALS(0, nextChunk.getMax().woCompare(BSON("a" << MAXKEY))); +} + +TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromLast) { + ChunkType nextChunk; + ASSERT(getCollMetadata().getNextChunk(BSON("a" << 30), &nextChunk)); +} + +TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkHoleInRange) { + string errMsg; + unique_ptr cloned; + + // Try to merge with hole in range + ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); + cloned.reset(getCollMetadata().cloneMerge( + BSON("a" << 10), BSON("a" << MAXKEY), newShardVersion, &errMsg)); + + ASSERT_NOT_EQUALS(errMsg, ""); + ASSERT(cloned == NULL); +} + +TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkDiffEndKey) { + string errMsg; + unique_ptr cloned; + + // Try to merge with different end key + ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); + cloned.reset(getCollMetadata().cloneMerge( + BSON("a" << MINKEY), BSON("a" << 19), newShardVersion, &errMsg)); + + ASSERT_NOT_EQUALS(errMsg, ""); + ASSERT(cloned == NULL); +} + +TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMinKey) { + string errMsg; + unique_ptr cloned; + + ASSERT_EQUALS(getCollMetadata().getNumChunks(), 3u); + + // Try to merge lowest chunks together + ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); + cloned.reset(getCollMetadata().cloneMerge( + BSON("a" << MINKEY), BSON("a" << 20), newShardVersion, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 10))); + ASSERT_EQUALS(cloned->getNumChunks(), 2u); + ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 5); +} + +TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMaxKey) { + string errMsg; + unique_ptr cloned; + ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); + + // Add one chunk to complete the range + ChunkType chunk; + chunk.setMin(BSON("a" << 20)); + chunk.setMax(BSON("a" << 30)); + cloned.reset(getCollMetadata().clonePlusChunk(chunk, newShardVersion, &errMsg)); + ASSERT_EQUALS(errMsg, ""); + ASSERT_EQUALS(cloned->getNumChunks(), 4u); + ASSERT(cloned != NULL); + + // Try to merge highest chunks together + newShardVersion.incMajor(); + cloned.reset( + cloned->cloneMerge(BSON("a" << 20), BSON("a" << MAXKEY), newShardVersion, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 30))); + ASSERT_EQUALS(cloned->getNumChunks(), 3u); + ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 6); +} + +TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkFullRange) { + string errMsg; + unique_ptr cloned; + ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); + + // Add one chunk to complete the range + ChunkType chunk; + chunk.setMin(BSON("a" << 20)); + chunk.setMax(BSON("a" << 30)); + cloned.reset(getCollMetadata().clonePlusChunk(chunk, newShardVersion, &errMsg)); + ASSERT_EQUALS(errMsg, ""); + ASSERT_EQUALS(cloned->getNumChunks(), 4u); + ASSERT(cloned != NULL); + + // Try to merge all chunks together + newShardVersion.incMajor(); + cloned.reset( + cloned->cloneMerge(BSON("a" << MINKEY), BSON("a" << MAXKEY), newShardVersion, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 10))); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 30))); + ASSERT_EQUALS(cloned->getNumChunks(), 1u); + ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 6); +} + +TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMiddleRange) { + string errMsg; + unique_ptr cloned; + ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); + + // Add one chunk to complete the range + ChunkType chunk; + chunk.setMin(BSON("a" << 20)); + chunk.setMax(BSON("a" << 30)); + cloned.reset(getCollMetadata().clonePlusChunk(chunk, newShardVersion, &errMsg)); + ASSERT_EQUALS(errMsg, ""); + ASSERT_EQUALS(cloned->getNumChunks(), 4u); + ASSERT(cloned != NULL); + + // Try to merge middle two chunks + newShardVersion.incMajor(); + cloned.reset(cloned->cloneMerge(BSON("a" << 10), BSON("a" << 30), newShardVersion, &errMsg)); + + ASSERT_EQUALS(errMsg, ""); + ASSERT(cloned != NULL); + ASSERT(cloned->keyBelongsToMe(BSON("a" << 20))); + ASSERT_EQUALS(cloned->getNumChunks(), 3u); + ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 6); +} + +TEST_F(ThreeChunkWithRangeGapFixture, CannotMergeWithHole) { + string errMsg; + ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); + + // Try to merge middle two chunks with a hole in the middle. + newShardVersion.incMajor(); + CollectionMetadata* result = + getCollMetadata().cloneMerge(BSON("a" << 10), BSON("a" << 30), newShardVersion, &errMsg); + ASSERT(result == NULL); + ASSERT(!errMsg.empty()); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/s/metadata_loader.cpp b/src/mongo/db/s/metadata_loader.cpp new file mode 100644 index 00000000000..f6b72823cf2 --- /dev/null +++ b/src/mongo/db/s/metadata_loader.cpp @@ -0,0 +1,291 @@ +/** + * Copyright (C) 2012 10gen 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 . + * + * 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/db/s/metadata_loader.h" + +#include + +#include "mongo/db/s/collection_metadata.h" +#include "mongo/s/catalog/catalog_manager.h" +#include "mongo/s/catalog/type_chunk.h" +#include "mongo/s/catalog/type_collection.h" +#include "mongo/s/chunk_diff.h" +#include "mongo/s/chunk_version.h" +#include "mongo/util/log.h" + +namespace mongo { + +using std::unique_ptr; +using std::make_pair; +using std::map; +using std::pair; +using std::string; + +namespace { + +/** + * This is an adapter so we can use config diffs - mongos and mongod do them slightly + * differently. + * + * The mongod adapter here tracks only a single shard, and stores ranges by (min, max). + */ +class SCMConfigDiffTracker : public ConfigDiffTracker { +public: + SCMConfigDiffTracker(const string& currShard) : _currShard(currShard) {} + + virtual bool isTracked(const ChunkType& chunk) const { + return chunk.getShard() == _currShard; + } + + virtual pair rangeFor(const ChunkType& chunk) const { + return make_pair(chunk.getMin(), chunk.getMax()); + } + + virtual string shardFor(const string& name) const { + return name; + } + + virtual string nameFrom(const string& shard) const { + return shard; + } + +private: + const string _currShard; +}; + +} // namespace + +// +// MetadataLoader implementation +// + +MetadataLoader::MetadataLoader() = default; + +MetadataLoader::~MetadataLoader() = default; + +Status MetadataLoader::makeCollectionMetadata(CatalogManager* catalogManager, + const string& ns, + const string& shard, + const CollectionMetadata* oldMetadata, + CollectionMetadata* metadata) const { + Status status = _initCollection(catalogManager, ns, shard, metadata); + if (!status.isOK() || metadata->getKeyPattern().isEmpty()) { + return status; + } + + return initChunks(catalogManager, ns, shard, oldMetadata, metadata); +} + +Status MetadataLoader::_initCollection(CatalogManager* catalogManager, + const string& ns, + const string& shard, + CollectionMetadata* metadata) const { + auto coll = catalogManager->getCollection(ns); + if (!coll.isOK()) { + return coll.getStatus(); + } + + CollectionType collInfo = coll.getValue(); + if (collInfo.getDropped()) { + return Status(ErrorCodes::NamespaceNotFound, + str::stream() << "could not load metadata, collection " << ns + << " was dropped"); + } + + metadata->_keyPattern = collInfo.getKeyPattern().toBSON(); + metadata->fillKeyPatternFields(); + metadata->_shardVersion = ChunkVersion(0, 0, collInfo.getEpoch()); + metadata->_collVersion = ChunkVersion(0, 0, collInfo.getEpoch()); + + return Status::OK(); +} + +Status MetadataLoader::initChunks(CatalogManager* catalogManager, + const string& ns, + const string& shard, + const CollectionMetadata* oldMetadata, + CollectionMetadata* metadata) const { + map versionMap; + + // Preserve the epoch + versionMap[shard] = metadata->_shardVersion; + OID epoch = metadata->getCollVersion().epoch(); + bool fullReload = true; + + // Check to see if we should use the old version or not. + if (oldMetadata) { + // If our epochs are compatible, it's useful to use the old metadata for diffs + if (oldMetadata->getCollVersion().hasEqualEpoch(epoch)) { + fullReload = false; + invariant(oldMetadata->isValid()); + + versionMap[shard] = oldMetadata->_shardVersion; + metadata->_collVersion = oldMetadata->_collVersion; + + // TODO: This could be made more efficient if copying not required, but + // not as frequently reloaded as in mongos. + metadata->_chunksMap = oldMetadata->_chunksMap; + + LOG(2) << "loading new chunks for collection " << ns + << " using old metadata w/ version " << oldMetadata->getShardVersion() << " and " + << metadata->_chunksMap.size() << " chunks"; + } else { + warning() << "reloading collection metadata for " << ns << " with new epoch " + << epoch.toString() << ", the current epoch is " + << oldMetadata->getCollVersion().epoch().toString(); + } + } + + + // Exposes the new metadata's range map and version to the "differ," who + // would ultimately be responsible of filling them up. + SCMConfigDiffTracker differ(shard); + differ.attach(ns, metadata->_chunksMap, metadata->_collVersion, versionMap); + + try { + std::vector chunks; + const auto diffQuery = differ.configDiffQuery(); + Status status = + catalogManager->getChunks(diffQuery.query, diffQuery.sort, boost::none, &chunks); + if (!status.isOK()) { + if (status == ErrorCodes::HostUnreachable) { + // Make our metadata invalid + metadata->_collVersion = ChunkVersion(0, 0, OID()); + metadata->_chunksMap.clear(); + } + return status; + } + + // + // The diff tracker should always find at least one chunk (the highest chunk we saw + // last time). If not, something has changed on the config server (potentially between + // when we read the collection data and when we read the chunks data). + // + int diffsApplied = differ.calculateConfigDiff(chunks); + if (diffsApplied > 0) { + // Chunks found, return ok + LOG(2) << "loaded " << diffsApplied << " chunks into new metadata for " << ns + << " with version " << metadata->_collVersion; + + metadata->_shardVersion = versionMap[shard]; + metadata->fillRanges(); + + invariant(metadata->isValid()); + return Status::OK(); + } else if (diffsApplied == 0) { + // No chunks found, the collection is dropping or we're confused + // If this is a full reload, assume it is a drop for backwards compatibility + // TODO: drop the config.collections entry *before* the chunks and eliminate this + // ambiguity + + string errMsg = str::stream() + << "no chunks found when reloading " << ns << ", previous version was " + << metadata->_collVersion.toString() << (fullReload ? ", this is a drop" : ""); + + warning() << errMsg; + + metadata->_collVersion = ChunkVersion(0, 0, OID()); + metadata->_chunksMap.clear(); + + return fullReload ? Status(ErrorCodes::NamespaceNotFound, errMsg) + : Status(ErrorCodes::RemoteChangeDetected, errMsg); + } else { + // Invalid chunks found, our epoch may have changed because we dropped/recreated + // the collection. + string errMsg = str::stream() + << "invalid chunks found when reloading " << ns << ", previous version was " + << metadata->_collVersion.toString() << ", this should be rare"; + warning() << errMsg; + + metadata->_collVersion = ChunkVersion(0, 0, OID()); + metadata->_chunksMap.clear(); + + return Status(ErrorCodes::RemoteChangeDetected, errMsg); + } + } catch (const DBException& e) { + // We deliberately do not return connPtr to the pool, since it was involved with the + // error here. + return Status(ErrorCodes::HostUnreachable, + str::stream() << "problem querying chunks metadata" << causedBy(e)); + } +} + +Status MetadataLoader::promotePendingChunks(const CollectionMetadata* afterMetadata, + CollectionMetadata* remoteMetadata) const { + // Ensure pending chunks are applicable + bool notApplicable = (NULL == afterMetadata || NULL == remoteMetadata) || + (afterMetadata->getShardVersion() > remoteMetadata->getShardVersion()) || + (afterMetadata->getShardVersion().epoch() != remoteMetadata->getShardVersion().epoch()); + if (notApplicable) + return Status::OK(); + + // The chunks from remoteMetadata are the latest version, and the pending chunks + // from afterMetadata are the latest version. If no trickery is afoot, pending chunks + // should match exactly zero or one loaded chunk. + + remoteMetadata->_pendingMap = afterMetadata->_pendingMap; + + // Resolve our pending chunks against the chunks we've loaded + for (RangeMap::iterator it = remoteMetadata->_pendingMap.begin(); + it != remoteMetadata->_pendingMap.end();) { + if (!rangeMapOverlaps(remoteMetadata->_chunksMap, it->first, it->second)) { + ++it; + continue; + } + + // Our pending range overlaps at least one chunk + + if (rangeMapContains(remoteMetadata->_chunksMap, it->first, it->second)) { + // Chunk was promoted from pending, successful migration + LOG(2) << "verified chunk " << rangeToString(it->first, it->second) + << " was migrated earlier to this shard"; + + remoteMetadata->_pendingMap.erase(it++); + } else { + // Something strange happened, maybe manual editing of config? + RangeVector overlap; + getRangeMapOverlap(remoteMetadata->_chunksMap, it->first, it->second, &overlap); + + string errMsg = str::stream() + << "the remote metadata changed unexpectedly, pending range " + << rangeToString(it->first, it->second) + << " does not exactly overlap loaded chunks " << overlapToString(overlap); + + return Status(ErrorCodes::RemoteChangeDetected, errMsg); + } + } + + return Status::OK(); +} + + +} // namespace mongo diff --git a/src/mongo/db/s/metadata_loader.h b/src/mongo/db/s/metadata_loader.h new file mode 100644 index 00000000000..15ca227926e --- /dev/null +++ b/src/mongo/db/s/metadata_loader.h @@ -0,0 +1,159 @@ +/** + * Copyright (C) 2012 10gen 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 . + * + * 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 + +#include "mongo/base/status.h" +#include "mongo/client/dbclientinterface.h" +#include "mongo/db/jsobj.h" + +namespace mongo { + +class CatalogManager; +class CollectionMetadata; +class CollectionType; +class DBClientCursor; + +/** + * The MetadataLoader is responsible for interfacing with the config servers and previous + * metadata to build new instances of CollectionMetadata. MetadataLoader is the "builder" + * class for metadata. + * + * CollectionMetadata has both persisted and volatile state (for now) - the persisted + * config server chunk state and the volatile pending state which is only tracked locally + * while a server is the primary. This requires a two-step loading process - the persisted + * chunk state *cannot* be loaded in a DBLock lock while the pending chunk state *must* be. + * + * Example usage: + * beforeMetadata = ; + * remoteMetadata = makeCollectionMetadata( beforeMetadata, remoteMetadata ); + * DBLock lock(txn, dbname, MODE_X); + * afterMetadata = ; + * promotePendingChunks( afterMetadata, remoteMetadata ); + * + * The loader will go out of its way to try to fetch the smaller amount possible of data + * from the config server without sacrificing the freshness and accuracy of the metadata it + * builds. (See ConfigDiffTracker class.) + * + * The class is not thread safe. + */ +class MetadataLoader { +public: + explicit MetadataLoader(); + + ~MetadataLoader(); + + /** + * Fills a new metadata instance representing the chunkset of the collection 'ns' + * (or its entirety, if not sharded) that lives on 'shard' with data from the config server. + * Optionally, uses an 'oldMetadata' for the same 'ns'/'shard'; the contents of + * 'oldMetadata' can help reducing the amount of data read from the config servers. + * + * Locking note: + * + Must not be called in a DBLock, since this loads over the network + * + * OK on success. + * + * Failure return values: + * Abnormal: + * @return FailedToParse if there was an error parsing the remote config data + * Normal: + * @return NamespaceNotFound if the collection no longer exists + * @return HostUnreachable if there was an error contacting the config servers + * @return RemoteChangeDetected if the data loaded was modified by another operation + */ + Status makeCollectionMetadata(CatalogManager* catalogManager, + const std::string& ns, + const std::string& shard, + const CollectionMetadata* oldMetadata, + CollectionMetadata* metadata) const; + + /** + * Replaces the pending chunks of the remote metadata with the more up-to-date pending + * chunks of the 'after' metadata (metadata from after the remote load), and removes pending + * chunks which are now regular chunks. + * + * Pending chunks should always correspond to one or zero chunks in the remoteMetadata + * if the epochs are the same and the remote version is the same or higher, otherwise they + * are not applicable. + * + * Locking note: + * + Must be called in a DBLock, to ensure validity of afterMetadata + * + * Returns OK if pending chunks correctly follow the rule above or are not applicable + * Returns RemoteChangeDetected if pending chunks do not follow the rule above, indicating + * either the config server or us has changed unexpectedly. + * This should only occur with manual editing of the config + * server. + * + * TODO: This is a bit ugly but necessary for now. If/when pending chunk info is stored on + * the config server, this should go away. + */ + Status promotePendingChunks(const CollectionMetadata* afterMetadata, + CollectionMetadata* remoteMetadata) const; + +private: + /** + * Returns OK and fills in the internal state of 'metadata' with general collection + * information, not including chunks. + * + * If information about the collection can be accessed or is invalid, returns: + * @return NamespaceNotFound if the collection no longer exists + * @return FailedToParse if there was an error parsing the remote config data + * @return HostUnreachable if there was an error contacting the config servers + * @return RemoteChangeDetected if the collection doc loaded is unexpectedly different + * + */ + Status _initCollection(CatalogManager* catalogManager, + const std::string& ns, + const std::string& shard, + CollectionMetadata* metadata) const; + + /** + * Returns OK and fills in the chunk state of 'metadata' to portray the chunks of the + * collection 'ns' that sit in 'shard'. If provided, uses the contents of 'oldMetadata' + * as a base (see description in initCollection above). + * + * If information about the chunks can be accessed or is invalid, returns: + * @return HostUnreachable if there was an error contacting the config servers + * @return RemoteChangeDetected if the chunks loaded are unexpectedly different + * + * For backwards compatibility, + * @return NamespaceNotFound if there are no chunks loaded and an epoch change is detected + * TODO: @return FailedToParse + */ + Status initChunks(CatalogManager* catalogManager, + const std::string& ns, + const std::string& shard, + const CollectionMetadata* oldMetadata, + CollectionMetadata* metadata) const; +}; + +} // namespace mongo diff --git a/src/mongo/db/s/metadata_loader_test.cpp b/src/mongo/db/s/metadata_loader_test.cpp new file mode 100644 index 00000000000..2d2adb706f2 --- /dev/null +++ b/src/mongo/db/s/metadata_loader_test.cpp @@ -0,0 +1,924 @@ +/** + * Copyright (C) 2012 10gen 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 . + * + * 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 + +#include "mongo/base/status.h" +#include "mongo/base/owned_pointer_vector.h" +#include "mongo/client/connpool.h" +#include "mongo/client/dbclientinterface.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/s/collection_metadata.h" +#include "mongo/db/s/metadata_loader.h" +#include "mongo/dbtests/mock/mock_conn_registry.h" +#include "mongo/dbtests/mock/mock_remote_db_server.h" +#include "mongo/s/catalog/legacy/catalog_manager_legacy.h" +#include "mongo/s/catalog/type_chunk.h" +#include "mongo/s/catalog/type_collection.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/net/hostandport.h" + +namespace mongo { +namespace { + +using std::unique_ptr; +using std::string; +using std::vector; + +const std::string CONFIG_HOST_PORT = "$dummy_config:27017"; + +// TODO: Test config server down +// TODO: Test read of chunks with new epoch +// TODO: Test that you can properly load config using format with deprecated fields? + +class MetadataLoaderFixture : public mongo::unittest::Test { +public: + void setUp() { + ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); + ASSERT(configLoc.isValid()); + ASSERT_OK(_catalogManager.init(configLoc)); + } + +protected: + CatalogManager* catalogManager() { + return &_catalogManager; + } + +private: + CatalogManagerLegacy _catalogManager; +}; + + +TEST_F(MetadataLoaderFixture, DroppedColl) { + MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(&dummyConfig); + + CollectionType collInfo; + collInfo.setNs(NamespaceString{"test.foo"}); + collInfo.setKeyPattern(BSON("a" << 1)); + collInfo.setUpdatedAt(Date_t()); + collInfo.setEpoch(OID()); + collInfo.setDropped(true); + ASSERT_OK(collInfo.validate()); + + dummyConfig.insert(CollectionType::ConfigNS, collInfo.toBSON()); + + MetadataLoader loader; + + string errmsg; + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + + ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound); + + MockConnRegistry::get()->clear(); + ScopedDbConnection::clearPool(); +} + +TEST_F(MetadataLoaderFixture, EmptyColl) { + MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(&dummyConfig); + + MetadataLoader loader; + + string errmsg; + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + + ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound); + + MockConnRegistry::get()->clear(); + ScopedDbConnection::clearPool(); +} + +TEST_F(MetadataLoaderFixture, BadColl) { + MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(&dummyConfig); + + dummyConfig.insert(CollectionType::ConfigNS, BSON(CollectionType::fullNs("test.foo"))); + + MetadataLoader loader; + + string errmsg; + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + + ASSERT_EQUALS(status.code(), ErrorCodes::NoSuchKey); + + MockConnRegistry::get()->clear(); + ScopedDbConnection::clearPool(); +} + +TEST_F(MetadataLoaderFixture, BadChunk) { + MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(&dummyConfig); + + CollectionType collInfo; + collInfo.setNs(NamespaceString{"test.foo"}); + collInfo.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + collInfo.setKeyPattern(BSON("a" << 1)); + collInfo.setEpoch(OID::gen()); + ASSERT_OK(collInfo.validate()); + + dummyConfig.insert(CollectionType::ConfigNS, collInfo.toBSON()); + + ChunkType chunkInfo; + chunkInfo.setNS(NamespaceString{"test.foo"}.ns()); + chunkInfo.setVersion(ChunkVersion(1, 0, collInfo.getEpoch())); + ASSERT(!chunkInfo.validate().isOK()); + + dummyConfig.insert(ChunkType::ConfigNS, chunkInfo.toBSON()); + + MetadataLoader loader; + + string errmsg; + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + + ASSERT_EQUALS(status.code(), ErrorCodes::FailedToParse); + + MockConnRegistry::get()->clear(); + ScopedDbConnection::clearPool(); +} + +class NoChunkFixture : public MetadataLoaderFixture { +protected: + void setUp() { + MetadataLoaderFixture::setUp(); + + _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(_dummyConfig.get()); + + OID epoch = OID::gen(); + + CollectionType collType; + collType.setNs(NamespaceString{"test.foo"}); + collType.setKeyPattern(BSON("a" << 1)); + collType.setUnique(false); + collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + collType.setEpoch(epoch); + _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); + } + + void tearDown() { + MockConnRegistry::get()->clear(); + ScopedDbConnection::clearPool(); + } + +private: + unique_ptr _dummyConfig; +}; + +TEST_F(NoChunkFixture, NoChunksIsDropped) { + MetadataLoader loader; + + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + + // This is interpreted as a dropped ns, since we drop the chunks first + ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound); +} + +class NoChunkHereFixture : public MetadataLoaderFixture { +protected: + void setUp() { + MetadataLoaderFixture::setUp(); + + _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(_dummyConfig.get()); + + OID epoch = OID::gen(); + + CollectionType collType; + collType.setNs(NamespaceString{"test.foo"}); + collType.setKeyPattern(BSON("a" << 1)); + collType.setUnique(false); + collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + collType.setEpoch(epoch); + ASSERT_OK(collType.validate()); + + _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); + + // Need a chunk on another shard, otherwise the chunks are invalid in general and we + // can't load metadata + ChunkType chunkType; + chunkType.setNS("test.foo"); + chunkType.setShard("shard0001"); + chunkType.setMin(BSON("a" << MINKEY)); + chunkType.setMax(BSON("a" << MAXKEY)); + chunkType.setVersion(ChunkVersion(1, 0, epoch)); + chunkType.setName(OID::gen().toString()); + ASSERT(chunkType.validate().isOK()); + + _dummyConfig->insert(ChunkType::ConfigNS, chunkType.toBSON()); + } + + MockRemoteDBServer* getDummyConfig() { + return _dummyConfig.get(); + } + + void tearDown() { + MockConnRegistry::get()->clear(); + ScopedDbConnection::clearPool(); + } + +private: + unique_ptr _dummyConfig; +}; + +TEST_F(NoChunkHereFixture, CheckNumChunk) { + MetadataLoader loader; + + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + + ASSERT(status.isOK()); + ASSERT_EQUALS(0U, metadata.getNumChunks()); + ASSERT_EQUALS(1, metadata.getCollVersion().majorVersion()); + ASSERT_EQUALS(0, metadata.getShardVersion().majorVersion()); + ASSERT_NOT_EQUALS(OID(), metadata.getCollVersion().epoch()); + ASSERT_NOT_EQUALS(OID(), metadata.getShardVersion().epoch()); +} + +class ConfigServerFixture : public MetadataLoaderFixture { +protected: + void setUp() { + MetadataLoaderFixture::setUp(); + + _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(_dummyConfig.get()); + + OID epoch = OID::gen(); + _maxCollVersion = ChunkVersion(1, 0, epoch); + + CollectionType collType; + collType.setNs(NamespaceString{"test.foo"}); + collType.setKeyPattern(BSON("a" << 1)); + collType.setUnique(false); + collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + collType.setEpoch(epoch); + _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); + + BSONObj fooSingle = BSON( + ChunkType::name("test.foo-a_MinKey") + << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << MINKEY)) + << ChunkType::max(BSON("a" << MAXKEY)) + << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(_maxCollVersion.toLong())) + << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000")); + _dummyConfig->insert(ChunkType::ConfigNS, fooSingle); + } + + void tearDown() { + MockConnRegistry::get()->clear(); + } + + ChunkVersion getMaxCollVersion() const { + return _maxCollVersion; + } + + ChunkVersion getMaxShardVersion() const { + return _maxCollVersion; + } + + MockRemoteDBServer* getConfigServer() const { + return _dummyConfig.get(); + } + +private: + unique_ptr _dummyConfig; + ChunkVersion _maxCollVersion; +}; + +TEST_F(ConfigServerFixture, SingleChunkCheckNumChunk) { + MetadataLoader loader; + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + ASSERT(status.isOK()); + ASSERT_EQUALS(1U, metadata.getNumChunks()); +} + +TEST_F(ConfigServerFixture, SingleChunkGetNext) { + MetadataLoader loader; + CollectionMetadata metadata; + loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + ChunkType chunkInfo; + ASSERT_TRUE(metadata.getNextChunk(metadata.getMinKey(), &chunkInfo)); +} + +TEST_F(ConfigServerFixture, SingleChunkGetShardKey) { + MetadataLoader loader; + CollectionMetadata metadata; + loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + ASSERT_TRUE(metadata.getKeyPattern().equal(BSON("a" << 1))); +} + +TEST_F(ConfigServerFixture, SingleChunkGetMaxCollVersion) { + MetadataLoader loader; + CollectionMetadata metadata; + loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + + ASSERT_TRUE(getMaxCollVersion().equals(metadata.getCollVersion())); +} + +TEST_F(ConfigServerFixture, SingleChunkGetMaxShardVersion) { + MetadataLoader loader; + CollectionMetadata metadata; + loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + + ASSERT_TRUE(getMaxShardVersion().equals(metadata.getShardVersion())); +} + +TEST_F(ConfigServerFixture, NoChunks) { + getConfigServer()->remove(ChunkType::ConfigNS, BSONObj()); + + MetadataLoader loader; + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata(catalogManager(), + "test.foo", + "shard0000", + NULL, /* no old metadata */ + &metadata); + + // NSNotFound because we're reloading with no old metadata + ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound); +} + +class MultipleMetadataFixture : public MetadataLoaderFixture { +protected: + void setUp() { + MetadataLoaderFixture::setUp(); + + _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); + mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); + MockConnRegistry::get()->addServer(_dummyConfig.get()); + + ConnectionString confServerStr((HostAndPort(CONFIG_HOST_PORT))); + ConnectionString configLoc(confServerStr); + _loader.reset(new MetadataLoader); + } + + MetadataLoader& loader() { + return *_loader; + } + + void getMetadataFor(const OwnedPointerVector& chunks, CollectionMetadata* metadata) { + // Infer namespace, shard, epoch, keypattern from first chunk + const ChunkType* firstChunk = *(chunks.vector().begin()); + + const string ns = firstChunk->getNS(); + const string shardName = firstChunk->getShard(); + const OID epoch = firstChunk->getVersion().epoch(); + + BSONObjBuilder keyPatternB; + BSONObjIterator keyPatternIt(firstChunk->getMin()); + while (keyPatternIt.more()) + keyPatternB.append(keyPatternIt.next().fieldName(), 1); + BSONObj keyPattern = keyPatternB.obj(); + + _dummyConfig->remove(CollectionType::ConfigNS, BSONObj()); + _dummyConfig->remove(ChunkType::ConfigNS, BSONObj()); + + CollectionType coll; + coll.setNs(NamespaceString{ns}); + coll.setKeyPattern(BSON("a" << 1)); + coll.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); + coll.setEpoch(epoch); + ASSERT_OK(coll.validate()); + + _dummyConfig->insert(CollectionType::ConfigNS, coll.toBSON()); + + ChunkVersion version(1, 0, epoch); + for (const auto chunkVal : chunks.vector()) { + ChunkType chunk(*chunkVal); + + chunk.setName(OID::gen().toString()); + if (!chunk.isVersionSet()) { + chunk.setVersion(version); + version.incMajor(); + } + + ASSERT(chunk.validate().isOK()); + + _dummyConfig->insert(ChunkType::ConfigNS, chunk.toBSON()); + } + + Status status = + loader().makeCollectionMetadata(catalogManager(), ns, shardName, NULL, metadata); + ASSERT(status.isOK()); + } + + void tearDown() { + MockConnRegistry::get()->clear(); + } + +private: + unique_ptr _dummyConfig; + unique_ptr _loader; +}; + +TEST_F(MultipleMetadataFixture, PromotePendingNA) { + unique_ptr chunk(new ChunkType()); + chunk->setNS("foo.bar"); + chunk->setShard("shard0000"); + chunk->setMin(BSON("x" << MINKEY)); + chunk->setMax(BSON("x" << 0)); + chunk->setVersion(ChunkVersion(1, 0, OID::gen())); + + OwnedPointerVector chunks; + chunks.mutableVector().push_back(chunk.release()); + + CollectionMetadata afterMetadata; + getMetadataFor(chunks, &afterMetadata); + + // Metadata of different epoch + (*chunks.vector().begin())->setVersion(ChunkVersion(1, 0, OID::gen())); + + CollectionMetadata remoteMetadata; + getMetadataFor(chunks, &remoteMetadata); + + Status status = loader().promotePendingChunks(&afterMetadata, &remoteMetadata); + ASSERT(status.isOK()); + + string errMsg; + ChunkType pending; + pending.setMin(BSON("x" << 0)); + pending.setMax(BSON("x" << 10)); + + unique_ptr cloned(afterMetadata.clonePlusPending(pending, &errMsg)); + ASSERT(cloned != NULL); + + status = loader().promotePendingChunks(cloned.get(), &remoteMetadata); + ASSERT(status.isOK()); + ASSERT_EQUALS(remoteMetadata.getNumPending(), 0u); +} + +TEST_F(MultipleMetadataFixture, PromotePendingNAVersion) { + OID epoch = OID::gen(); + + unique_ptr chunk(new ChunkType()); + chunk->setNS("foo.bar"); + chunk->setShard("shard0000"); + chunk->setMin(BSON("x" << MINKEY)); + chunk->setMax(BSON("x" << 0)); + chunk->setVersion(ChunkVersion(1, 1, epoch)); + + OwnedPointerVector chunks; + chunks.mutableVector().push_back(chunk.release()); + + CollectionMetadata afterMetadata; + getMetadataFor(chunks, &afterMetadata); + + // Metadata of same epoch, but lower version + (*chunks.vector().begin())->setVersion(ChunkVersion(1, 0, epoch)); + + CollectionMetadata remoteMetadata; + getMetadataFor(chunks, &remoteMetadata); + + Status status = loader().promotePendingChunks(&afterMetadata, &remoteMetadata); + ASSERT(status.isOK()); + + string errMsg; + ChunkType pending; + pending.setMin(BSON("x" << 0)); + pending.setMax(BSON("x" << 10)); + + unique_ptr cloned(afterMetadata.clonePlusPending(pending, &errMsg)); + ASSERT(cloned != NULL); + + status = loader().promotePendingChunks(cloned.get(), &remoteMetadata); + ASSERT(status.isOK()); + ASSERT_EQUALS(remoteMetadata.getNumPending(), 0u); +} + +TEST_F(MultipleMetadataFixture, PromotePendingGoodOverlap) { + OID epoch = OID::gen(); + + // + // Setup chunk range for remote metadata + // + + OwnedPointerVector chunks; + + unique_ptr chunk(new ChunkType()); + chunk->setNS("foo.bar"); + chunk->setShard("shard0000"); + chunk->setMin(BSON("x" << MINKEY)); + chunk->setMax(BSON("x" << 0)); + chunk->setVersion(ChunkVersion(1, 0, epoch)); + chunks.mutableVector().push_back(chunk.release()); + + chunk.reset(new ChunkType()); + chunk->setNS("foo.bar"); + chunk->setShard("shard0000"); + chunk->setMin(BSON("x" << 10)); + chunk->setMax(BSON("x" << 20)); + chunks.mutableVector().push_back(chunk.release()); + + chunk.reset(new ChunkType()); + chunk->setNS("foo.bar"); + chunk->setShard("shard0000"); + chunk->setMin(BSON("x" << 30)); + chunk->setMax(BSON("x" << MAXKEY)); + chunks.mutableVector().push_back(chunk.release()); + + CollectionMetadata remoteMetadata; + getMetadataFor(chunks, &remoteMetadata); + + // + // Setup chunk and pending range for afterMetadata + // + + chunks.clear(); + + chunk.reset(new ChunkType()); + chunk->setNS("foo.bar"); + chunk->setShard("shard0000"); + chunk->setMin(BSON("x" << 0)); + chunk->setMax(BSON("x" << 10)); + chunk->setVersion(ChunkVersion(1, 0, epoch)); + + chunks.mutableVector().push_back(chunk.release()); + + CollectionMetadata afterMetadata; + getMetadataFor(chunks, &afterMetadata); + + string errMsg; + ChunkType pending; + pending.setMin(BSON("x" << MINKEY)); + pending.setMax(BSON("x" << 0)); + + unique_ptr cloned(afterMetadata.clonePlusPending(pending, &errMsg)); + ASSERT(cloned != NULL); + + pending.setMin(BSON("x" << 10)); + pending.setMax(BSON("x" << 20)); + + cloned.reset(cloned->clonePlusPending(pending, &errMsg)); + ASSERT(cloned != NULL); + + pending.setMin(BSON("x" << 20)); + pending.setMax(BSON("x" << 30)); + + cloned.reset(cloned->clonePlusPending(pending, &errMsg)); + ASSERT(cloned != NULL); + + pending.setMin(BSON("x" << 30)); + pending.setMax(BSON("x" << MAXKEY)); + + cloned.reset(cloned->clonePlusPending(pending, &errMsg)); + ASSERT(cloned != NULL); + + Status status = loader().promotePendingChunks(cloned.get(), &remoteMetadata); + ASSERT(status.isOK()); + + ASSERT_EQUALS(remoteMetadata.getNumPending(), 1u); + ASSERT(remoteMetadata.keyIsPending(BSON("x" << 25))); +} + +TEST_F(MultipleMetadataFixture, PromotePendingBadOverlap) { + OID epoch = OID::gen(); + + // + // Setup chunk range for remote metadata + // + + OwnedPointerVector chunks; + + unique_ptr chunk(new ChunkType()); + chunk->setNS("foo.bar"); + chunk->setShard("shard0000"); + chunk->setMin(BSON("x" << MINKEY)); + chunk->setMax(BSON("x" << 0)); + chunk->setVersion(ChunkVersion(1, 0, epoch)); + + chunks.mutableVector().push_back(chunk.release()); + + CollectionMetadata remoteMetadata; + getMetadataFor(chunks, &remoteMetadata); + + // + // Setup chunk and pending range for afterMetadata + // + + chunks.clear(); + + chunk.reset(new ChunkType()); + chunk->setNS("foo.bar"); + chunk->setShard("shard0000"); + chunk->setMin(BSON("x" << 15)); + chunk->setMax(BSON("x" << MAXKEY)); + chunk->setVersion(ChunkVersion(1, 0, epoch)); + + chunks.mutableVector().push_back(chunk.release()); + + CollectionMetadata afterMetadata; + getMetadataFor(chunks, &afterMetadata); + + string errMsg; + ChunkType pending; + pending.setMin(BSON("x" << MINKEY)); + pending.setMax(BSON("x" << 1)); + + unique_ptr cloned(afterMetadata.clonePlusPending(pending, &errMsg)); + ASSERT(cloned != NULL); + + cloned.reset(cloned->clonePlusPending(pending, &errMsg)); + ASSERT(cloned != NULL); + + Status status = loader().promotePendingChunks(cloned.get(), &remoteMetadata); + ASSERT_EQUALS(status.code(), ErrorCodes::RemoteChangeDetected); +} + +#if 0 + // TODO: MockServer functionality does not support selective query - consider + // inserting nothing at all to chunk/collections collection + TEST_F(ConfigServerFixture, EmptyDataForNS) { + MetadataLoader loader; + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata( "not.sharded", + "shard0000", + NULL, /* no old metadata */ + &metadata ); + ASSERT( !status.isOK() ); + } +#endif + +#if 0 + // TODO: d_chunk_manager_test has no tests for passing old ShardChunkManager + class TwoChunkFixture : public mongo::unittest::Test { + protected: + void setUp() { + _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) ); + mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() ); + MockConnRegistry::get()->addServer( _dummyConfig.get() ); + + OID epoch = OID::gen(); + _maxCollVersion = ChunkVersion( 1, 0, epoch ); + + BSONObj collFoo = BSON(CollectionType::ns("test.foo") << + CollectionType::keyPattern(BSON("a" << 1)) << + CollectionType::unique(false) << + CollectionType::updatedAt(1ULL) << + CollectionType::epoch(epoch)); + _dummyConfig->insert( CollectionType::ConfigNS, collFoo ); + + BSONObj fooSingle = BSON(ChunkType::name("test.foo-a_MinKey") << + ChunkType::ns("test.foo") << + ChunkType::min(BSON("a" << MINKEY)) << + ChunkType::max(BSON("a" << MAXKEY)) << + ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << + ChunkType::DEPRECATED_epoch(epoch) << + ChunkType::shard("shard0000")); + _dummyConfig->insert( ChunkType::ConfigNS, fooSingle ); + + MetadataLoader loader; + Status status = loader.makeCollectionMetadata( "not.sharded", + "shard0000", + NULL, /* no old metadata */ + &_oldMetadata ); + ASSERT( status.isOK() ); + + // Needs to delete the collection and rebuild because the mock server + // not support updates. + _dummyConfig->remove( CollectionType::ConfigNS, BSONObj() ); + _dummyConfig->remove( ChunkType::ConfigNS, BSONObj() ); + + OID epoch2 = OID::gen(); + _maxCollVersion = ChunkVersion( 2, 0, epoch2 ); + + BSONObj collFoo = BSON(CollectionType::ns("test.foo") << + CollectionType::keyPattern(BSON("a" << 1)) << + CollectionType::unique(false) << + CollectionType::updatedAt(2ULL) << + CollectionType::epoch(epoch2)); + _dummyConfig->insert( CollectionType::ConfigNS, collFoo ); + + BSONObj chunk1 = BSON(ChunkType::name("test.foo-a_MinKey") << + ChunkType::ns("test.foo") << + ChunkType::min(BSON("a" << MINKEY)) << + ChunkType::max(BSON("a" << 100)) << + ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << + ChunkType::DEPRECATED_epoch(epoch2) << + ChunkType::shard("shard0000")); + _dummyConfig->insert( ChunkType::ConfigNS, chunk1 ); + + BSONObj chunk2 = BSON(ChunkType::name("test.foo-a_100") << + ChunkType::ns("test.foo") << + ChunkType::min(BSON("a" << 100)) << + ChunkType::max(BSON("a" << MAXKEY)) << + ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << + ChunkType::DEPRECATED_epoch(epoch2) << + ChunkType::shard("shard0000")); + _dummyConfig->insert( ChunkType::ConfigNS, chunk2 ); + } + + void tearDown() { + MockConnRegistry::get()->removeServer( _dummyConfig->getServerAddress() ); + } + + ChunkVersion getCollVersion() const { + return _maxCollVersion; + } + + const ChunkVersion& getShardVersion( size_t shard ) const { + return _maxCollVersion; + } + + const CollectionMetadata* getOldMetadata() const { + return _oldMetadata; + } + + private: + unique_ptr _dummyConfig; + CollectionMetadata _oldMetadata; + + ChunkVersion _maxCollVersion; + }; +#endif + +#if 0 + + // TODO: MockServer functionality does not support selective query + class ThreeChunkTwoShardFixture : public mongo::unittest::Test { + protected: + void setUp() { + _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) ); + mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() ); + MockConnRegistry::get()->addServer( _dummyConfig.get() ); + + OID epoch = OID::gen(); + _maxCollVersion = ChunkVersion( 1, 0, epoch ); + + BSONObj collFoo = BSON(CollectionType::ns("test.foo") << + CollectionType::keyPattern(BSON("a" << 1)) << + CollectionType::unique(false) << + CollectionType::updatedAt(1ULL) << + CollectionType::epoch(epoch)); + _dummyConfig->insert( CollectionType::ConfigNS, collFoo ); + + BSONObj fooSingle = BSON(ChunkType::name("test.foo-a_MinKey") << + ChunkType::ns("test.foo") << + ChunkType::min(BSON("a" << MINKEY)) << + ChunkType::max(BSON("a" << MAXKEY)) << + ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << + ChunkType::DEPRECATED_epoch(epoch) << + ChunkType::shard("shard0000")); + _dummyConfig->insert( ChunkType::ConfigNS, fooSingle ); + + MetadataLoader loader; + CollectionMetadata metadata; + Status status = loader.makeCollectionMetadata( "not.sharded", + "shard0000", + NULL, /* no old metadata */ + &metadata ); + ASSERT( status.isOK() ); + + // Needs to delete the collection and rebuild because the mock server + // not support updates. + _dummyConfig->remove( CollectionType::ConfigNS, BSONObj() ); + _dummyConfig->remove( ChunkType::ConfigNS, BSONObj() ); + + OID epoch2 = OID::gen(); + _maxCollVersion = ChunkVersion( 2, 0, epoch2 ); + _maxShardVersion.push_back( _maxCollVersion ); + + BSONObj collFoo = BSON(CollectionType::ns("test.foo") << + CollectionType::keyPattern(BSON("a" << 1)) << + CollectionType::unique(false) << + CollectionType::updatedAt(2ULL) << + CollectionType::epoch(epoch2)); + _dummyConfig->insert( CollectionType::ConfigNS, collFoo ); + + BSONObj chunk1 = BSON(ChunkType::name("test.foo-a_MinKey") << + ChunkType::ns("test.foo") << + ChunkType::min(BSON("a" << MINKEY)) << + ChunkType::max(BSON("a" << 10)) << + ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << + ChunkType::DEPRECATED_epoch(epoch2) << + ChunkType::shard("shard0000")); + _dummyConfig->insert( ChunkType::ConfigNS, chunk1 ); + + OID epoch3 = OID::gen(); + _maxCollVersion = ChunkVersion( 2, 0, epoch3 ); + _maxShardVersion.push_back( _maxCollVersion ); + + BSONObj chunk2 = BSON(ChunkType::name("test.foo-a_10") << + ChunkType::ns("test.foo") << + ChunkType::min(BSON("a" << 10)) << + ChunkType::max(BSON("a" << 100)) << + ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << + ChunkType::DEPRECATED_epoch(epoch3) << + ChunkType::shard("shard0001")); + _dummyConfig->insert( ChunkType::ConfigNS, chunk2 ); + + BSONObj chunk3 = BSON(ChunkType::name("test.foo-a_100") << + ChunkType::ns("test.foo") << + ChunkType::min(BSON("a" << 100)) << + ChunkType::max(BSON("a" << MAXKEY)) << + ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << + ChunkType::DEPRECATED_epoch(epoch3) << + ChunkType::shard("shard0001")); + _dummyConfig->insert( ChunkType::ConfigNS, chunk3 ); + } + + void tearDown() { + MockConnRegistry::get()->removeServer( _dummyConfig->getServerAddress() ); + } + + ChunkVersion getCollVersion() const { + return _maxCollVersion; + } + + const ChunkVersion& getShardVersion( size_t shard ) const { + return _maxShardVersion[shard]; + } + + private: + unique_ptr _dummyConfig; + CollectionMetadata _oldMetadata; + + ChunkVersion _maxCollVersion; + vector _maxShardVersion; + }; +#endif + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/s/sharding_state.cpp b/src/mongo/db/s/sharding_state.cpp index f6442c69744..dbcabce83c1 100644 --- a/src/mongo/db/s/sharding_state.cpp +++ b/src/mongo/db/s/sharding_state.cpp @@ -37,16 +37,17 @@ #include "mongo/db/concurrency/lock_state.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/replication_executor.h" +#include "mongo/db/s/collection_metadata.h" +#include "mongo/db/s/metadata_loader.h" #include "mongo/db/s/sharded_connection_info.h" #include "mongo/executor/network_interface_factory.h" #include "mongo/executor/task_executor.h" #include "mongo/s/catalog/catalog_manager.h" +#include "mongo/s/catalog/type_chunk.h" #include "mongo/s/catalog/legacy/catalog_manager_legacy.h" #include "mongo/s/client/shard_registry.h" #include "mongo/s/chunk_version.h" -#include "mongo/s/collection_metadata.h" #include "mongo/s/grid.h" -#include "mongo/s/metadata_loader.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/net/sock.h" @@ -60,6 +61,10 @@ using std::vector; // Global sharding state instance ShardingState shardingState; +bool isMongos() { + return false; +} + ShardingState::ShardingState() : _enabled(false), _configServerTickets(3 /* max number of concurrent config server refresh threads */) {} diff --git a/src/mongo/dbtests/dbhelper_tests.cpp b/src/mongo/dbtests/dbhelper_tests.cpp index 18dc87428ce..ca4280ada04 100644 --- a/src/mongo/dbtests/dbhelper_tests.cpp +++ b/src/mongo/dbtests/dbhelper_tests.cpp @@ -26,6 +26,8 @@ * then also delete it in the license file. */ +#include "mongo/platform/basic.h" + #include "mongo/client/dbclientcursor.h" #include "mongo/db/catalog/collection.h" #include "mongo/db/catalog/database_holder.h" @@ -33,6 +35,7 @@ #include "mongo/db/dbdirectclient.h" #include "mongo/db/dbhelpers.h" #include "mongo/db/operation_context_impl.h" +#include "mongo/db/range_arithmetic.h" #include "mongo/db/write_concern_options.h" #include "mongo/dbtests/dbtests.h" #include "mongo/unittest/unittest.h" diff --git a/src/mongo/dbtests/merge_chunk_tests.cpp b/src/mongo/dbtests/merge_chunk_tests.cpp index 16e010edf27..a7b51248418 100644 --- a/src/mongo/dbtests/merge_chunk_tests.cpp +++ b/src/mongo/dbtests/merge_chunk_tests.cpp @@ -29,13 +29,13 @@ #include "mongo/platform/basic.h" #include "mongo/db/range_arithmetic.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharding_state.h" #include "mongo/dbtests/config_server_fixture.h" #include "mongo/s/catalog/type_chunk.h" #include "mongo/s/catalog/type_collection.h" -#include "mongo/s/chunk.h" // for genID +#include "mongo/s/chunk.h" #include "mongo/s/chunk_version.h" -#include "mongo/s/collection_metadata.h" #include "mongo/s/d_merge.h" #include "mongo/unittest/unittest.h" diff --git a/src/mongo/dbtests/sharding.cpp b/src/mongo/dbtests/sharding.cpp index 4b15938625a..f5cfb7a8476 100644 --- a/src/mongo/dbtests/sharding.cpp +++ b/src/mongo/dbtests/sharding.cpp @@ -30,18 +30,14 @@ #include "mongo/platform/basic.h" -#include "mongo/client/parallel.h" -#include "mongo/db/dbdirectclient.h" #include "mongo/db/operation_context_impl.h" #include "mongo/dbtests/config_server_fixture.h" #include "mongo/dbtests/dbtests.h" #include "mongo/s/catalog/type_chunk.h" #include "mongo/s/catalog/type_collection.h" #include "mongo/s/catalog/type_shard.h" -#include "mongo/s/chunk_diff.h" +#include "mongo/s/client/shard_connection.h" #include "mongo/s/chunk_manager.h" -#include "mongo/s/chunk_version.h" -#include "mongo/s/config.h" namespace { @@ -67,18 +63,6 @@ static int rand(int max = -1) { return max > 0 ? r % max : r; } -// -// Converts array of raw BSONObj chunks to a vector of ChunkType -// -void convertBSONArrayToChunkTypes(const BSONArray& chunksArray, - std::vector* chunksVector) { - for (const BSONElement& obj : chunksArray) { - auto chunkTypeRes = ChunkType::fromBSON(obj.Obj()); - ASSERT(chunkTypeRes.isOK()); - chunksVector->push_back(chunkTypeRes.getValue()); - } -} - // // Sets up a basic environment for loading chunks to/from the direct database connection // Redirects connections to the direct database for the duration of the test. @@ -283,381 +267,6 @@ public: } }; -class ChunkDiffUnitTest { -public: - bool _inverse; - - typedef map RangeMap; - typedef map VersionMap; - - ChunkDiffUnitTest(bool inverse) : _inverse(inverse) {} - - // The default pass-through adapter for using config diffs - class DefaultDiffAdapter : public ConfigDiffTracker { - public: - DefaultDiffAdapter() {} - virtual ~DefaultDiffAdapter() {} - - virtual bool isTracked(const ChunkType& chunk) const { - return true; - } - - virtual pair rangeFor(const ChunkType& chunk) const { - return make_pair(chunk.getMin(), chunk.getMax()); - } - - virtual string shardFor(const string& name) const { - return name; - } - }; - - // Inverts the storage order for chunks from min to max - class InverseDiffAdapter : public DefaultDiffAdapter { - public: - InverseDiffAdapter() {} - virtual ~InverseDiffAdapter() {} - - virtual bool isMinKeyIndexed() const { - return false; - } - - virtual pair rangeFor(const ChunkType& chunk) const { - return make_pair(chunk.getMax(), chunk.getMin()); - } - }; - - // Allow validating with and without ranges (b/c our splits won't actually be updated by the - // diffs) - void validate(const std::vector& chunks, - ChunkVersion maxVersion, - const VersionMap& maxShardVersions) { - validate(chunks, NULL, maxVersion, maxShardVersions); - } - - void validate(const std::vector& chunks, - const RangeMap& ranges, - ChunkVersion maxVersion, - const VersionMap& maxShardVersions) { - validate(chunks, (RangeMap*)&ranges, maxVersion, maxShardVersions); - } - - // Validates that the ranges and versions are valid given the chunks - void validate(const std::vector& chunks, - RangeMap* ranges, - ChunkVersion maxVersion, - const VersionMap& maxShardVersions) { - int chunkCount = chunks.size(); - ChunkVersion foundMaxVersion; - VersionMap foundMaxShardVersions; - - // - // Validate that all the chunks are there and collect versions - // - - for (const ChunkType& chunk : chunks) { - if (ranges != NULL) { - // log() << "Validating chunk " << chunkDoc << " size : " << ranges->size() << " vs - // " << chunkCount << endl; - - RangeMap::iterator chunkRange = - ranges->find(_inverse ? chunk.getMax() : chunk.getMin()); - - ASSERT(chunkRange != ranges->end()); - ASSERT(chunkRange->second.woCompare(_inverse ? chunk.getMin() : chunk.getMax()) == - 0); - } - - ChunkVersion version = - ChunkVersion::fromBSON(chunk.toBSON()[ChunkType::DEPRECATED_lastmod()]); - if (version > foundMaxVersion) - foundMaxVersion = version; - - ChunkVersion shardMaxVersion = foundMaxShardVersions[chunk.getShard()]; - if (version > shardMaxVersion) { - foundMaxShardVersions[chunk.getShard()] = version; - } - } - - // Make sure all chunks are accounted for - if (ranges != NULL) - ASSERT(chunkCount == (int)ranges->size()); - - // log() << "Validating that all shard versions are up to date..." << endl; - - // Validate that all the versions are the same - ASSERT(foundMaxVersion.equals(maxVersion)); - - for (VersionMap::iterator it = foundMaxShardVersions.begin(); - it != foundMaxShardVersions.end(); - it++) { - ChunkVersion foundVersion = it->second; - VersionMap::const_iterator maxIt = maxShardVersions.find(it->first); - - ASSERT(maxIt != maxShardVersions.end()); - ASSERT(foundVersion.equals(maxIt->second)); - } - // Make sure all shards are accounted for - ASSERT(foundMaxShardVersions.size() == maxShardVersions.size()); - } - - void run() { - int numShards = 10; - int numInitialChunks = 5; - int maxChunks = 100000; // Needed to not overflow the BSONArray's max bytes - int keySize = 2; - - BSONArrayBuilder chunksB; - - BSONObj lastSplitPt; - ChunkVersion version(1, 0, OID()); - - // - // Generate numChunks with a given key size over numShards - // All chunks have double key values, so we can split them a bunch - // - - for (int i = -1; i < numInitialChunks; i++) { - BSONObjBuilder splitPtB; - for (int k = 0; k < keySize; k++) { - string field = string("k") + string(1, (char)('0' + k)); - if (i < 0) - splitPtB.appendMinKey(field); - else if (i < numInitialChunks - 1) - splitPtB.append(field, (double)i); - else - splitPtB.appendMaxKey(field); - } - BSONObj splitPt = splitPtB.obj(); - - if (i >= 0) { - BSONObjBuilder chunkB; - - chunkB.append(ChunkType::name(), "$dummyname"); - chunkB.append(ChunkType::ns(), "$dummyns"); - - chunkB.append(ChunkType::min(), lastSplitPt); - chunkB.append(ChunkType::max(), splitPt); - - int shardNum = rand(numShards); - chunkB.append(ChunkType::shard(), "shard" + string(1, (char)('A' + shardNum))); - - rand(2) ? version.incMajor() : version.incMinor(); - version.addToBSON(chunkB, ChunkType::DEPRECATED_lastmod()); - - chunksB.append(chunkB.obj()); - } - - lastSplitPt = splitPt; - } - - BSONArray chunks = chunksB.arr(); - - // log() << "Chunks generated : " << chunks << endl; - - // Setup the empty ranges and versions first - RangeMap ranges; - ChunkVersion maxVersion = ChunkVersion(0, 0, OID()); - VersionMap maxShardVersions; - - // Create a differ which will track our progress - std::shared_ptr differ(_inverse ? new InverseDiffAdapter() - : new DefaultDiffAdapter()); - differ->attach("test", ranges, maxVersion, maxShardVersions); - - std::vector chunksVector; - convertBSONArrayToChunkTypes(chunks, &chunksVector); - - // Validate initial load - differ->calculateConfigDiff(chunksVector); - validate(chunksVector, ranges, maxVersion, maxShardVersions); - - // Generate a lot of diffs, and keep validating that updating from the diffs always - // gives us the right ranges and versions - - int numDiffs = 135; // Makes about 100000 chunks overall - int numChunks = numInitialChunks; - for (int i = 0; i < numDiffs; i++) { - // log() << "Generating new diff... " << i << endl; - - BSONArrayBuilder diffsB; - BSONArrayBuilder newChunksB; - BSONObjIterator chunksIt(chunks); - - while (chunksIt.more()) { - BSONObj chunk = chunksIt.next().Obj(); - - int randChoice = rand(10); - - if (randChoice < 2 && numChunks < maxChunks) { - // Simulate a split - - // log() << " ...starting a split with chunk " << chunk << endl; - - BSONObjBuilder leftB; - BSONObjBuilder rightB; - BSONObjBuilder midB; - - for (int k = 0; k < keySize; k++) { - string field = string("k") + string(1, (char)('0' + k)); - - BSONType maxType = chunk[ChunkType::max()].Obj()[field].type(); - double max = - maxType == NumberDouble ? chunk["max"].Obj()[field].Number() : 0.0; - BSONType minType = chunk[ChunkType::min()].Obj()[field].type(); - double min = minType == NumberDouble - ? chunk[ChunkType::min()].Obj()[field].Number() - : 0.0; - - if (minType == MinKey) { - midB.append(field, max - 1.0); - } else if (maxType == MaxKey) { - midB.append(field, min + 1.0); - } else { - midB.append(field, (max + min) / 2.0); - } - } - - BSONObj midPt = midB.obj(); - // Only happens if we can't split the min chunk - if (midPt.isEmpty()) - continue; - - leftB.append(chunk[ChunkType::min()]); - leftB.append(ChunkType::max(), midPt); - rightB.append(ChunkType::min(), midPt); - rightB.append(chunk[ChunkType::max()]); - - // add required fields for ChunkType - leftB.append(chunk[ChunkType::name()]); - leftB.append(chunk[ChunkType::ns()]); - rightB.append(chunk[ChunkType::name()]); - rightB.append(chunk[ChunkType::ns()]); - - leftB.append(chunk[ChunkType::shard()]); - rightB.append(chunk[ChunkType::shard()]); - - version.incMajor(); - version._minor = 0; - version.addToBSON(leftB, ChunkType::DEPRECATED_lastmod()); - version.incMinor(); - version.addToBSON(rightB, ChunkType::DEPRECATED_lastmod()); - - BSONObj left = leftB.obj(); - BSONObj right = rightB.obj(); - - // log() << " ... split into " << left << " and " << right << endl; - - newChunksB.append(left); - newChunksB.append(right); - - diffsB.append(right); - diffsB.append(left); - - numChunks++; - } else if (randChoice < 4 && chunksIt.more()) { - // Simulate a migrate - - // log() << " ...starting a migrate with chunk " << chunk << endl; - - BSONObj prevShardChunk; - while (chunksIt.more()) { - prevShardChunk = chunksIt.next().Obj(); - if (prevShardChunk[ChunkType::shard()].String() == - chunk[ChunkType::shard()].String()) - break; - - // log() << "... appending chunk from diff shard: " << prevShardChunk << - // endl; - newChunksB.append(prevShardChunk); - - prevShardChunk = BSONObj(); - } - - // We need to move between different shards, hence the weirdness in logic here - if (!prevShardChunk.isEmpty()) { - BSONObjBuilder newShardB; - BSONObjBuilder prevShardB; - - newShardB.append(chunk[ChunkType::min()]); - newShardB.append(chunk[ChunkType::max()]); - prevShardB.append(prevShardChunk[ChunkType::min()]); - prevShardB.append(prevShardChunk[ChunkType::max()]); - - // add required fields for ChunkType - newShardB.append(chunk[ChunkType::name()]); - newShardB.append(chunk[ChunkType::ns()]); - prevShardB.append(chunk[ChunkType::name()]); - prevShardB.append(chunk[ChunkType::ns()]); - - int shardNum = rand(numShards); - newShardB.append(ChunkType::shard(), - "shard" + string(1, (char)('A' + shardNum))); - prevShardB.append(prevShardChunk[ChunkType::shard()]); - - version.incMajor(); - version._minor = 0; - version.addToBSON(newShardB, ChunkType::DEPRECATED_lastmod()); - version.incMinor(); - version.addToBSON(prevShardB, ChunkType::DEPRECATED_lastmod()); - - BSONObj newShard = newShardB.obj(); - BSONObj prevShard = prevShardB.obj(); - - // log() << " ... migrated to " << newShard << " and updated " << prevShard - // << endl; - - newChunksB.append(newShard); - newChunksB.append(prevShard); - - diffsB.append(newShard); - diffsB.append(prevShard); - - } else { - // log() << "... appending chunk, no more left: " << chunk << endl; - newChunksB.append(chunk); - } - } else { - // log() << "Appending chunk : " << chunk << endl; - newChunksB.append(chunk); - } - } - - BSONArray diffs = diffsB.arr(); - chunks = newChunksB.arr(); - - // log() << "Diffs generated : " << diffs << endl; - // log() << "All chunks : " << chunks << endl; - - // Rarely entirely clear out our data - if (rand(10) < 1) { - diffs = chunks; - ranges.clear(); - maxVersion = ChunkVersion(0, 0, OID()); - maxShardVersions.clear(); - } - - // log() << "Total number of chunks : " << numChunks << " iteration " << i << endl; - - std::vector chunksVector; - convertBSONArrayToChunkTypes(chunks, &chunksVector); - - differ->calculateConfigDiff(chunksVector); - - validate(chunksVector, ranges, maxVersion, maxShardVersions); - } - } -}; - -class ChunkDiffUnitTestNormal : public ChunkDiffUnitTest { -public: - ChunkDiffUnitTestNormal() : ChunkDiffUnitTest(false) {} -}; - -class ChunkDiffUnitTestInverse : public ChunkDiffUnitTest { -public: - ChunkDiffUnitTestInverse() : ChunkDiffUnitTest(true) {} -}; - class All : public Suite { public: All() : Suite("sharding") {} @@ -666,8 +275,6 @@ public: add(); add(); add(); - add(); - add(); } }; diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript index 1c054ceb35c..8cd2540322a 100644 --- a/src/mongo/s/SConscript +++ b/src/mongo/s/SConscript @@ -11,6 +11,17 @@ env.SConscript( ], ) +# Functionality shared between mongod and mongos +env.Library( + target='common', + source=[ + 'chunk_diff.cpp', + ], + LIBDEPS=[ + 'catalog/catalog_types', + ] +) + env.Library( target='shard_util', source=[ @@ -21,45 +32,25 @@ env.Library( ] ) -env.CppUnitTest('chunk_version_test', 'chunk_version_test.cpp', - LIBDEPS=['$BUILD_DIR/mongo/db/common']) - -# -# Support for maintaining persistent sharding state and data. -# - -env.Library( - target='metadata', +env.CppUnitTest( + target='chunk_version_test', source=[ - 'collection_metadata.cpp', - 'metadata_loader.cpp', + 'chunk_version_test.cpp', ], LIBDEPS=[ - 'catalog/catalog_types', - '$BUILD_DIR/mongo/bson/bson', - '$BUILD_DIR/mongo/base/base', - '$BUILD_DIR/mongo/client/clientdriver', - '$BUILD_DIR/mongo/db/range_arithmetic', + '$BUILD_DIR/mongo/db/common', ] ) env.CppUnitTest( - target='metadata_test', + target='chunk_diff_test', source=[ 'chunk_diff_test.cpp', - 'metadata_loader_test.cpp', - 'collection_metadata_test.cpp', ], LIBDEPS=[ - 'catalog/legacy/catalog_manager_legacy', - 'coreshard', - 'metadata', - 'mongoscore', - '$BUILD_DIR/mongo/db/coredb', - '$BUILD_DIR/mongo/db/auth/authorization_manager_mock_init', - '$BUILD_DIR/mongo/dbtests/mocklib', - '$BUILD_DIR/mongo/db/common', - ]) + 'common', + ] +) # # Write Operations @@ -211,7 +202,6 @@ env.Library( # This is only here temporarily for auto-split logic in chunk.cpp. 'balancer_policy.cpp', 'chunk.cpp', - 'chunk_diff.cpp', 'chunk_manager.cpp', 'config.cpp', 'grid.cpp', @@ -223,6 +213,7 @@ env.Library( 'catalog/catalog_types', 'client/sharding_client', 'cluster_ops_impl', + 'common', 'shard_util', ] ) diff --git a/src/mongo/s/chunk_diff.cpp b/src/mongo/s/chunk_diff.cpp index 3d6901aa8c2..e3b0f9fa2f0 100644 --- a/src/mongo/s/chunk_diff.cpp +++ b/src/mongo/s/chunk_diff.cpp @@ -32,17 +32,15 @@ #include "mongo/s/chunk_diff.h" -#include "mongo/client/dbclientinterface.h" #include "mongo/s/catalog/type_chunk.h" -#include "mongo/s/catalog/catalog_manager.h" -#include "mongo/s/chunk.h" #include "mongo/s/chunk_version.h" #include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" namespace mongo { -template -ConfigDiffTracker::ConfigDiffTracker() { +template +ConfigDiffTracker::ConfigDiffTracker() { _ns.clear(); _currMap = NULL; _maxVersion = NULL; @@ -50,14 +48,14 @@ ConfigDiffTracker::ConfigDiffTracker() { _validDiffs = 0; } -template -ConfigDiffTracker::~ConfigDiffTracker() = default; +template +ConfigDiffTracker::~ConfigDiffTracker() = default; -template -void ConfigDiffTracker::attach(const std::string& ns, - RangeMap& currMap, - ChunkVersion& maxVersion, - MaxChunkVersionMap& maxShardVersions) { +template +void ConfigDiffTracker::attach(const std::string& ns, + RangeMap& currMap, + ChunkVersion& maxVersion, + MaxChunkVersionMap& maxShardVersions) { _ns = ns; _currMap = &currMap; _maxVersion = &maxVersion; @@ -65,16 +63,15 @@ void ConfigDiffTracker::attach(const std::string& ns, _validDiffs = 0; } -template -bool ConfigDiffTracker::isOverlapping(const BSONObj& min, const BSONObj& max) { +template +bool ConfigDiffTracker::isOverlapping(const BSONObj& min, const BSONObj& max) { RangeOverlap overlap = overlappingRange(min, max); return overlap.first != overlap.second; } -template -void ConfigDiffTracker::removeOverlapping(const BSONObj& min, - const BSONObj& max) { +template +void ConfigDiffTracker::removeOverlapping(const BSONObj& min, const BSONObj& max) { _assertAttached(); RangeOverlap overlap = overlappingRange(min, max); @@ -82,9 +79,9 @@ void ConfigDiffTracker::removeOverlapping(const BSONObj& min _currMap->erase(overlap.first, overlap.second); } -template -typename ConfigDiffTracker::RangeOverlap -ConfigDiffTracker::overlappingRange(const BSONObj& min, const BSONObj& max) { +template +typename ConfigDiffTracker::RangeOverlap ConfigDiffTracker::overlappingRange( + const BSONObj& min, const BSONObj& max) { _assertAttached(); typename RangeMap::iterator low; @@ -111,29 +108,8 @@ ConfigDiffTracker::overlappingRange(const BSONObj& min, cons return RangeOverlap(low, high); } -template -int ConfigDiffTracker::calculateConfigDiff(CatalogManager* catalogManager) { - _assertAttached(); - - // Get the diff query required - Query diffQuery = configDiffQuery(); - - try { - std::vector chunks; - uassertStatusOK(catalogManager->getChunks( - diffQuery.getFilter(), diffQuery.getSort(), boost::none, &chunks)); - - return calculateConfigDiff(chunks); - } catch (DBException& e) { - // Should only happen on connection errors - e.addContext(str::stream() << "could not calculate config difference for ns " << _ns); - throw; - } -} - -template -int ConfigDiffTracker::calculateConfigDiff( - const std::vector& chunks) { +template +int ConfigDiffTracker::calculateConfigDiff(const std::vector& chunks) { _assertAttached(); // Apply the chunk changes to the ranges and versions @@ -173,7 +149,7 @@ int ConfigDiffTracker::calculateConfigDiff( } // Chunk version changes - ShardType shard = shardFor(chunk.getShard()); + ShardId shard = shardFor(chunk.getShard()); typename MaxChunkVersionMap::const_iterator shardVersionIt = _maxShardVersions->find(shard); if (shardVersionIt == _maxShardVersions->end() || shardVersionIt->second < chunkVersion) { @@ -211,12 +187,13 @@ int ConfigDiffTracker::calculateConfigDiff( return _validDiffs; } -template -Query ConfigDiffTracker::configDiffQuery() const { +template +typename ConfigDiffTracker::QueryAndSort ConfigDiffTracker::configDiffQuery() + const { _assertAttached(); - // Basic idea behind the query is to find all the chunks $gte the current max version. - // Currently, any splits and merges will increment the current max version. + // The query has to find all the chunks $gte the current max version. Currently, any splits and + // merges will increment the current max version. BSONObjBuilder queryB; queryB.append(ChunkType::ns(), _ns); @@ -226,14 +203,13 @@ Query ConfigDiffTracker::configDiffQuery() const { tsBuilder.done(); } - // NOTE: IT IS IMPORTANT FOR CONSISTENCY THAT WE SORT BY ASC VERSION, IN ORDER TO HANDLE - // CURSOR YIELDING BETWEEN CHUNKS BEING MIGRATED. + // NOTE: IT IS IMPORTANT FOR CONSISTENCY THAT WE SORT BY ASC VERSION, IN ORDER TO HANDLE CURSOR + // YIELDING BETWEEN CHUNKS BEING MIGRATED. // - // This ensures that changes to chunk version (which will always be higher) will always - // come *after* our current position in the chunk cursor. + // This ensures that changes to chunk version (which will always be higher) will always come + // *after* our current position in the chunk cursor. - Query queryObj(queryB.obj()); - queryObj.sort(BSON("lastmod" << 1)); + QueryAndSort queryObj(queryB.obj(), BSON("lastmod" << 1)); LOG(2) << "major version query from " << *_maxVersion << " and over " << _maxShardVersions->size() << " shards is " << queryObj; @@ -241,16 +217,21 @@ Query ConfigDiffTracker::configDiffQuery() const { return queryObj; } -template -void ConfigDiffTracker::_assertAttached() const { +template +void ConfigDiffTracker::_assertAttached() const { invariant(_currMap); invariant(_maxVersion); invariant(_maxShardVersions); } +std::string ConfigDiffTrackerBase::QueryAndSort::toString() const { + return str::stream() << "query: " << query << ", sort: " << sort; +} // Ensures that these instances of the template are compiled -template class ConfigDiffTracker; -template class ConfigDiffTracker, std::string>; +class Chunk; + +template class ConfigDiffTracker; +template class ConfigDiffTracker>; } // namespace mongo diff --git a/src/mongo/s/chunk_diff.h b/src/mongo/s/chunk_diff.h index 00d713addd5..ce53a95a043 100644 --- a/src/mongo/s/chunk_diff.h +++ b/src/mongo/s/chunk_diff.h @@ -32,29 +32,43 @@ #include "mongo/bson/bsonmisc.h" #include "mongo/bson/bsonobj.h" +#include "mongo/s/client/shard.h" namespace mongo { class ChunkType; struct ChunkVersion; -class CatalogManager; -class Query; + +class ConfigDiffTrackerBase { +public: + /** + * Structure repsenting the generated query and sort order for a chunk diffing operation. + */ + struct QueryAndSort { + QueryAndSort(BSONObj inQuery, BSONObj inSort) : query(inQuery), sort(inSort) {} + + std::string toString() const; + + const BSONObj query; + const BSONObj sort; + }; +}; /** - * This class manages and applies diffs from partial config server data reloads. Because the - * config data can be large, we want to update it in small parts, not all-at-once. Once a - * ConfigDiffTracker is created, the current config data is *attached* to it, and it is then - * able to modify the data. + * This class manages and applies diffs from partial config server data reloads. Because the config + * data can be large, we want to update it in small parts, not all-at-once. Once a + * ConfigDiffTracker is created, the current config data is *attached* to it, and it is then able + * to modify the data. * - * The current form is templated b/c the overall algorithm is identical between mongos and - * mongod, but the actual chunk maps used differ in implementation. We don't want to copy the - * implementation, because the logic is identical, or the chunk data, because that would be - * slow for big clusters, so this is the alternative for now. + * The current form is templated b/c the overall algorithm is identical between mongos and mongod, + * but the actual chunk maps used differ in implementation. We don't want to copy the + * implementation, because the logic is identical, or the chunk data, because that would be slow + * for big clusters, so this is the alternative for now. * * TODO: Standardize between mongos and mongod and convert template parameters to types. */ -template -class ConfigDiffTracker { +template +class ConfigDiffTracker : public ConfigDiffTrackerBase { public: // Stores ranges indexed by max or min key typedef typename std::map RangeMap; @@ -64,8 +78,7 @@ public: typename std::pair RangeOverlap; // Map of shard identifiers to the maximum chunk version on that shard - typedef typename std::map MaxChunkVersionMap; - + typedef typename std::map MaxChunkVersionMap; ConfigDiffTracker(); virtual ~ConfigDiffTracker(); @@ -97,35 +110,32 @@ public: // Returns a subset of ranges overlapping the region min/max RangeOverlap overlappingRange(const BSONObj& min, const BSONObj& max); - // Finds and applies the changes to a collection from the config servers via - // the catalog manager. - // Also includes minor version changes for particular major-version chunks if explicitly - // specified. - // Returns the number of diffs processed, or -1 if the diffs were inconsistent - // Throws a DBException on connection errors - int calculateConfigDiff(CatalogManager* catalogManager); - - // Applies changes to the config data from a vector of chunks passed in - // Returns the number of diffs processed, or -1 if the diffs were inconsistent - // Throws a DBException on connection errors + // Applies changes to the config data from a vector of chunks passed in. Also includes minor + // version changes for particular major-version chunks if explicitly specified. + // Returns the number of diffs processed, or -1 if the diffs were inconsistent. int calculateConfigDiff(const std::vector& chunks); // Returns the query needed to find new changes to a collection from the config server // Needed only if a custom connection is required to the config server - Query configDiffQuery() const; + QueryAndSort configDiffQuery() const; protected: - // Determines which chunks are actually being remembered by our RangeMap + /** + * Determines which chunks are actually being remembered by our RangeMap. Allows individual + * shards to filter out results, which belong to the local shard only. + */ virtual bool isTracked(const ChunkType& chunk) const = 0; - // Whether or not our RangeMap uses min or max keys + /** + * Whether or not our RangeMap uses min or max keys + */ virtual bool isMinKeyIndexed() const { return true; } virtual std::pair rangeFor(const ChunkType& chunk) const = 0; - virtual ShardType shardFor(const std::string& name) const = 0; + virtual ShardId shardFor(const std::string& name) const = 0; private: void _assertAttached() const; diff --git a/src/mongo/s/chunk_diff_test.cpp b/src/mongo/s/chunk_diff_test.cpp index 067b80ac0f9..b6ecd3f84f4 100644 --- a/src/mongo/s/chunk_diff_test.cpp +++ b/src/mongo/s/chunk_diff_test.cpp @@ -26,32 +26,34 @@ * then also delete it in the license file. */ +#include "mongo/platform/basic.h" + #include "mongo/s/chunk_diff.h" #include +#include #include #include "mongo/db/jsobj.h" +#include "mongo/platform/random.h" #include "mongo/s/catalog/type_chunk.h" #include "mongo/unittest/unittest.h" +namespace mongo { namespace { -using mongo::BSONObj; -using mongo::ChunkType; -using mongo::ConfigDiffTracker; using std::string; using std::pair; using std::make_pair; +using std::map; -// XXX -// We'd move ChunkDiffUnitTest here -// We can check the queries it generates. -// We can check if is populating the attaching structures properly -// +// Generates pseudorandom values +PseudoRandom rand(1); -// The default pass-through adapter for using config diffs. -class DefaultDiffAdapter : public ConfigDiffTracker { +/** + * The default pass-through adapter for using config diffs. + */ +class DefaultDiffAdapter : public ConfigDiffTracker { public: DefaultDiffAdapter() {} virtual ~DefaultDiffAdapter() {} @@ -64,14 +66,363 @@ public: return make_pair(chunk.getMin(), chunk.getMax()); } - virtual string shardFor(const string& name) const { + virtual ShardId shardFor(const string& name) const { return name; } }; -TEST(Basics, Simple) { - DefaultDiffAdapter differ; - ASSERT_TRUE(true); +/** + * Inverts the storage order for chunks from min to max. + */ +class InverseDiffAdapter : public DefaultDiffAdapter { +public: + InverseDiffAdapter() {} + virtual ~InverseDiffAdapter() {} + + virtual bool isMinKeyIndexed() const { + return false; + } + + virtual pair rangeFor(const ChunkType& chunk) const { + return make_pair(chunk.getMax(), chunk.getMin()); + } +}; + +/** + * Converts array of raw BSONObj chunks to a vector of ChunkType. + */ +void convertBSONArrayToChunkTypes(const BSONArray& chunksArray, + std::vector* chunksVector) { + for (const BSONElement& obj : chunksArray) { + auto chunkTypeRes = ChunkType::fromBSON(obj.Obj()); + ASSERT(chunkTypeRes.isOK()); + chunksVector->push_back(chunkTypeRes.getValue()); + } +} + +class ChunkDiffUnitTest : public mongo::unittest::Test { +protected: + typedef map RangeMap; + typedef map VersionMap; + + ChunkDiffUnitTest() = default; + ~ChunkDiffUnitTest() = default; + + void runTest(bool isInverse) { + int numShards = 10; + int numInitialChunks = 5; + + // Needed to not overflow the BSONArray's max bytes + int maxChunks = 100000; + int keySize = 2; + + BSONArrayBuilder chunksB; + + BSONObj lastSplitPt; + ChunkVersion version(1, 0, OID()); + + // Generate numChunks with a given key size over numShards. All chunks have double key + // values, so we can split them a bunch. + + for (int i = -1; i < numInitialChunks; i++) { + BSONObjBuilder splitPtB; + for (int k = 0; k < keySize; k++) { + string field = string("k") + string(1, (char)('0' + k)); + if (i < 0) + splitPtB.appendMinKey(field); + else if (i < numInitialChunks - 1) + splitPtB.append(field, (double)i); + else + splitPtB.appendMaxKey(field); + } + BSONObj splitPt = splitPtB.obj(); + + if (i >= 0) { + BSONObjBuilder chunkB; + + chunkB.append(ChunkType::name(), "$dummyname"); + chunkB.append(ChunkType::ns(), "$dummyns"); + + chunkB.append(ChunkType::min(), lastSplitPt); + chunkB.append(ChunkType::max(), splitPt); + + int shardNum = rand(numShards); + chunkB.append(ChunkType::shard(), "shard" + string(1, (char)('A' + shardNum))); + + rand(2) ? version.incMajor() : version.incMinor(); + version.addToBSON(chunkB, ChunkType::DEPRECATED_lastmod()); + + chunksB.append(chunkB.obj()); + } + + lastSplitPt = splitPt; + } + + BSONArray chunks = chunksB.arr(); + + // Setup the empty ranges and versions first + RangeMap ranges; + ChunkVersion maxVersion = ChunkVersion(0, 0, OID()); + VersionMap maxShardVersions; + + // Create a differ which will track our progress + std::shared_ptr differ(isInverse ? new InverseDiffAdapter() + : new DefaultDiffAdapter()); + differ->attach("test", ranges, maxVersion, maxShardVersions); + + std::vector chunksVector; + convertBSONArrayToChunkTypes(chunks, &chunksVector); + + // Validate initial load + differ->calculateConfigDiff(chunksVector); + validate(isInverse, chunksVector, ranges, maxVersion, maxShardVersions); + + // Generate a lot of diffs, and keep validating that updating from the diffs always gives us + // the right ranges and versions + + // Makes about 100000 chunks overall + int numDiffs = 135; + int numChunks = numInitialChunks; + + for (int i = 0; i < numDiffs; i++) { + BSONArrayBuilder diffsB; + BSONArrayBuilder newChunksB; + BSONObjIterator chunksIt(chunks); + + while (chunksIt.more()) { + BSONObj chunk = chunksIt.next().Obj(); + + int randChoice = rand(10); + + if (randChoice < 2 && numChunks < maxChunks) { + // Simulate a split + BSONObjBuilder leftB; + BSONObjBuilder rightB; + BSONObjBuilder midB; + + for (int k = 0; k < keySize; k++) { + string field = string("k") + string(1, (char)('0' + k)); + + BSONType maxType = chunk[ChunkType::max()].Obj()[field].type(); + double max = + maxType == NumberDouble ? chunk["max"].Obj()[field].Number() : 0.0; + BSONType minType = chunk[ChunkType::min()].Obj()[field].type(); + double min = minType == NumberDouble + ? chunk[ChunkType::min()].Obj()[field].Number() + : 0.0; + + if (minType == MinKey) { + midB.append(field, max - 1.0); + } else if (maxType == MaxKey) { + midB.append(field, min + 1.0); + } else { + midB.append(field, (max + min) / 2.0); + } + } + + BSONObj midPt = midB.obj(); + + // Only happens if we can't split the min chunk + if (midPt.isEmpty()) { + continue; + } + + leftB.append(chunk[ChunkType::min()]); + leftB.append(ChunkType::max(), midPt); + rightB.append(ChunkType::min(), midPt); + rightB.append(chunk[ChunkType::max()]); + + // Add required fields for ChunkType + leftB.append(chunk[ChunkType::name()]); + leftB.append(chunk[ChunkType::ns()]); + rightB.append(chunk[ChunkType::name()]); + rightB.append(chunk[ChunkType::ns()]); + + leftB.append(chunk[ChunkType::shard()]); + rightB.append(chunk[ChunkType::shard()]); + + version.incMajor(); + version._minor = 0; + version.addToBSON(leftB, ChunkType::DEPRECATED_lastmod()); + version.incMinor(); + version.addToBSON(rightB, ChunkType::DEPRECATED_lastmod()); + + BSONObj left = leftB.obj(); + BSONObj right = rightB.obj(); + + newChunksB.append(left); + newChunksB.append(right); + + diffsB.append(right); + diffsB.append(left); + + numChunks++; + } else if (randChoice < 4 && chunksIt.more()) { + // Simulate a migrate + BSONObj prevShardChunk; + while (chunksIt.more()) { + prevShardChunk = chunksIt.next().Obj(); + + if (prevShardChunk[ChunkType::shard()].String() == + chunk[ChunkType::shard()].String()) { + break; + } + + newChunksB.append(prevShardChunk); + + prevShardChunk = BSONObj(); + } + + // We need to move between different shards, hence the weirdness in logic here + if (!prevShardChunk.isEmpty()) { + BSONObjBuilder newShardB; + BSONObjBuilder prevShardB; + + newShardB.append(chunk[ChunkType::min()]); + newShardB.append(chunk[ChunkType::max()]); + prevShardB.append(prevShardChunk[ChunkType::min()]); + prevShardB.append(prevShardChunk[ChunkType::max()]); + + // add required fields for ChunkType + newShardB.append(chunk[ChunkType::name()]); + newShardB.append(chunk[ChunkType::ns()]); + prevShardB.append(chunk[ChunkType::name()]); + prevShardB.append(chunk[ChunkType::ns()]); + + int shardNum = rand(numShards); + newShardB.append(ChunkType::shard(), + "shard" + string(1, (char)('A' + shardNum))); + prevShardB.append(prevShardChunk[ChunkType::shard()]); + + version.incMajor(); + version._minor = 0; + version.addToBSON(newShardB, ChunkType::DEPRECATED_lastmod()); + version.incMinor(); + version.addToBSON(prevShardB, ChunkType::DEPRECATED_lastmod()); + + BSONObj newShard = newShardB.obj(); + BSONObj prevShard = prevShardB.obj(); + + newChunksB.append(newShard); + newChunksB.append(prevShard); + + diffsB.append(newShard); + diffsB.append(prevShard); + + } else { + newChunksB.append(chunk); + } + } else { + newChunksB.append(chunk); + } + } + + BSONArray diffs = diffsB.arr(); + chunks = newChunksB.arr(); + + // Rarely entirely clear out our data + if (rand(10) < 1) { + diffs = chunks; + ranges.clear(); + maxVersion = ChunkVersion(0, 0, OID()); + maxShardVersions.clear(); + } + + std::vector chunksVector; + convertBSONArrayToChunkTypes(chunks, &chunksVector); + + differ->calculateConfigDiff(chunksVector); + + validate(isInverse, chunksVector, ranges, maxVersion, maxShardVersions); + } + } + +private: + // Allow validating with and without ranges (b/c our splits won't actually be updated by the + // diffs) + void validate(bool isInverse, + const std::vector& chunks, + ChunkVersion maxVersion, + const VersionMap& maxShardVersions) { + validate(isInverse, chunks, NULL, maxVersion, maxShardVersions); + } + + void validate(bool isInverse, + const std::vector& chunks, + const RangeMap& ranges, + ChunkVersion maxVersion, + const VersionMap& maxShardVersions) { + validate(isInverse, chunks, (RangeMap*)&ranges, maxVersion, maxShardVersions); + } + + // Validates that the ranges and versions are valid given the chunks + void validate(bool isInverse, + const std::vector& chunks, + RangeMap* ranges, + ChunkVersion maxVersion, + const VersionMap& maxShardVersions) { + int chunkCount = chunks.size(); + ChunkVersion foundMaxVersion; + VersionMap foundMaxShardVersions; + + // + // Validate that all the chunks are there and collect versions + // + + for (const ChunkType& chunk : chunks) { + if (ranges != NULL) { + // log() << "Validating chunk " << chunkDoc << " size : " << ranges->size() << " vs + // " << chunkCount << endl; + + RangeMap::iterator chunkRange = + ranges->find(isInverse ? chunk.getMax() : chunk.getMin()); + + ASSERT(chunkRange != ranges->end()); + ASSERT(chunkRange->second.woCompare(isInverse ? chunk.getMin() : chunk.getMax()) == + 0); + } + + ChunkVersion version = + ChunkVersion::fromBSON(chunk.toBSON()[ChunkType::DEPRECATED_lastmod()]); + if (version > foundMaxVersion) + foundMaxVersion = version; + + ChunkVersion shardMaxVersion = foundMaxShardVersions[chunk.getShard()]; + if (version > shardMaxVersion) { + foundMaxShardVersions[chunk.getShard()] = version; + } + } + + // Make sure all chunks are accounted for + if (ranges != NULL) + ASSERT(chunkCount == (int)ranges->size()); + + // log() << "Validating that all shard versions are up to date..." << endl; + + // Validate that all the versions are the same + ASSERT(foundMaxVersion.equals(maxVersion)); + + for (VersionMap::iterator it = foundMaxShardVersions.begin(); + it != foundMaxShardVersions.end(); + it++) { + ChunkVersion foundVersion = it->second; + VersionMap::const_iterator maxIt = maxShardVersions.find(it->first); + + ASSERT(maxIt != maxShardVersions.end()); + ASSERT(foundVersion.equals(maxIt->second)); + } + // Make sure all shards are accounted for + ASSERT(foundMaxShardVersions.size() == maxShardVersions.size()); + } +}; + +TEST_F(ChunkDiffUnitTest, Normal) { + runTest(false); +} + +TEST_F(ChunkDiffUnitTest, Inverse) { + runTest(true); } -} // unnamed namespace +} // namespace +} // namespace mongo diff --git a/src/mongo/s/chunk_manager.cpp b/src/mongo/s/chunk_manager.cpp index 65bd31a8628..5f578a27da4 100644 --- a/src/mongo/s/chunk_manager.cpp +++ b/src/mongo/s/chunk_manager.cpp @@ -73,7 +73,7 @@ namespace { * * The mongos adapter here tracks all shards, and stores ranges by (max, Chunk) in the map. */ -class CMConfigDiffTracker : public ConfigDiffTracker, string> { +class CMConfigDiffTracker : public ConfigDiffTracker> { public: CMConfigDiffTracker(ChunkManager* manager) : _manager(manager) {} @@ -254,7 +254,14 @@ bool ChunkManager::_load(ChunkMap& chunkMap, differ.attach(_ns, chunkMap, _version, *shardVersions); // Diff tracker should *always* find at least one chunk if collection exists - int diffsApplied = differ.calculateConfigDiff(grid.catalogManager()); + // Get the diff query required + auto diffQuery = differ.configDiffQuery(); + + std::vector chunks; + uassertStatusOK( + grid.catalogManager()->getChunks(diffQuery.query, diffQuery.sort, boost::none, &chunks)); + + int diffsApplied = differ.calculateConfigDiff(chunks); if (diffsApplied > 0) { LOG(2) << "loaded " << diffsApplied << " chunks into new chunk manager for " << _ns << " with version " << _version; diff --git a/src/mongo/s/collection_metadata.cpp b/src/mongo/s/collection_metadata.cpp deleted file mode 100644 index 05d448dc239..00000000000 --- a/src/mongo/s/collection_metadata.cpp +++ /dev/null @@ -1,728 +0,0 @@ -/** - * Copyright (C) 2012 10gen 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 . - * - * 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/collection_metadata.h" - -#include "mongo/bson/util/builder.h" -#include "mongo/util/log.h" -#include "mongo/util/mongoutils/str.h" - -namespace mongo { - -using std::unique_ptr; -using std::endl; -using std::make_pair; -using std::string; -using std::vector; - -using mongoutils::str::stream; - -CollectionMetadata::CollectionMetadata() {} - -CollectionMetadata::~CollectionMetadata() {} - -CollectionMetadata* CollectionMetadata::cloneMigrate(const ChunkType& chunk, - const ChunkVersion& newShardVersion, - string* errMsg) const { - // The error message string is optional. - string dummy; - if (errMsg == NULL) { - errMsg = &dummy; - } - - // Check that we have the exact chunk that will be subtracted. - if (!rangeMapContains(_chunksMap, chunk.getMin(), chunk.getMax())) { - *errMsg = stream() << "cannot remove chunk " - << rangeToString(chunk.getMin(), chunk.getMax()) - << ", this shard does not contain the chunk"; - - if (rangeMapOverlaps(_chunksMap, chunk.getMin(), chunk.getMax())) { - RangeVector overlap; - getRangeMapOverlap(_chunksMap, chunk.getMin(), chunk.getMax(), &overlap); - - *errMsg += stream() << " and it overlaps " << overlapToString(overlap); - } - - warning() << *errMsg << endl; - return NULL; - } - - // If left with no chunks, check that the version is zero. - if (_chunksMap.size() == 1) { - if (newShardVersion.isSet()) { - *errMsg = stream() << "cannot set shard version to non-zero value " - << newShardVersion.toString() << " when removing last chunk " - << rangeToString(chunk.getMin(), chunk.getMax()); - - warning() << *errMsg << endl; - return NULL; - } - } - // Can't move version backwards when subtracting chunks. This is what guarantees that - // no read or write would be taken once we subtract data from the current shard. - else if (newShardVersion <= _shardVersion) { - *errMsg = stream() << "cannot remove chunk " - << rangeToString(chunk.getMin(), chunk.getMax()) - << " because the new shard version " << newShardVersion.toString() - << " is not greater than the current shard version " - << _shardVersion.toString(); - - warning() << *errMsg << endl; - return NULL; - } - - unique_ptr metadata(new CollectionMetadata); - metadata->_keyPattern = this->_keyPattern; - metadata->_keyPattern.getOwned(); - metadata->fillKeyPatternFields(); - metadata->_pendingMap = this->_pendingMap; - metadata->_chunksMap = this->_chunksMap; - metadata->_chunksMap.erase(chunk.getMin()); - metadata->_shardVersion = newShardVersion; - metadata->_collVersion = newShardVersion > _collVersion ? newShardVersion : this->_collVersion; - metadata->fillRanges(); - - invariant(metadata->isValid()); - return metadata.release(); -} - -CollectionMetadata* CollectionMetadata::clonePlusChunk(const ChunkType& chunk, - const ChunkVersion& newShardVersion, - string* errMsg) const { - // The error message string is optional. - string dummy; - if (errMsg == NULL) { - errMsg = &dummy; - } - - // It is acceptable to move version backwards (e.g., undoing a migration that went bad - // during commit) but only cloning away the last chunk may reset the version to 0. - if (!newShardVersion.isSet()) { - *errMsg = stream() << "cannot add chunk " << rangeToString(chunk.getMin(), chunk.getMax()) - << " with zero shard version"; - - warning() << *errMsg << endl; - return NULL; - } - - invariant(chunk.getMin().woCompare(chunk.getMax()) < 0); - - // Check that there isn't any chunk on the interval to be added. - if (rangeMapOverlaps(_chunksMap, chunk.getMin(), chunk.getMax())) { - RangeVector overlap; - getRangeMapOverlap(_chunksMap, chunk.getMin(), chunk.getMax(), &overlap); - - *errMsg = stream() << "cannot add chunk " << rangeToString(chunk.getMin(), chunk.getMax()) - << " because the chunk overlaps " << overlapToString(overlap); - - warning() << *errMsg << endl; - return NULL; - } - - unique_ptr metadata(new CollectionMetadata); - metadata->_keyPattern = this->_keyPattern; - metadata->_keyPattern.getOwned(); - metadata->fillKeyPatternFields(); - metadata->_pendingMap = this->_pendingMap; - metadata->_chunksMap = this->_chunksMap; - metadata->_chunksMap.insert(make_pair(chunk.getMin().getOwned(), chunk.getMax().getOwned())); - metadata->_shardVersion = newShardVersion; - metadata->_collVersion = newShardVersion > _collVersion ? newShardVersion : this->_collVersion; - metadata->fillRanges(); - - invariant(metadata->isValid()); - return metadata.release(); -} - -CollectionMetadata* CollectionMetadata::cloneMinusPending(const ChunkType& pending, - string* errMsg) const { - // The error message string is optional. - string dummy; - if (errMsg == NULL) { - errMsg = &dummy; - } - - // Check that we have the exact chunk that will be subtracted. - if (!rangeMapContains(_pendingMap, pending.getMin(), pending.getMax())) { - *errMsg = stream() << "cannot remove pending chunk " - << rangeToString(pending.getMin(), pending.getMax()) - << ", this shard does not contain the chunk"; - - if (rangeMapOverlaps(_pendingMap, pending.getMin(), pending.getMax())) { - RangeVector overlap; - getRangeMapOverlap(_pendingMap, pending.getMin(), pending.getMax(), &overlap); - - *errMsg += stream() << " and it overlaps " << overlapToString(overlap); - } - - warning() << *errMsg << endl; - return NULL; - } - - unique_ptr metadata(new CollectionMetadata); - metadata->_keyPattern = this->_keyPattern; - metadata->_keyPattern.getOwned(); - metadata->fillKeyPatternFields(); - metadata->_pendingMap = this->_pendingMap; - metadata->_pendingMap.erase(pending.getMin()); - metadata->_chunksMap = this->_chunksMap; - metadata->_rangesMap = this->_rangesMap; - metadata->_shardVersion = _shardVersion; - metadata->_collVersion = _collVersion; - - invariant(metadata->isValid()); - return metadata.release(); -} - -CollectionMetadata* CollectionMetadata::clonePlusPending(const ChunkType& pending, - string* errMsg) const { - // The error message string is optional. - string dummy; - if (errMsg == NULL) { - errMsg = &dummy; - } - - if (rangeMapOverlaps(_chunksMap, pending.getMin(), pending.getMax())) { - RangeVector overlap; - getRangeMapOverlap(_chunksMap, pending.getMin(), pending.getMax(), &overlap); - - *errMsg = stream() << "cannot add pending chunk " - << rangeToString(pending.getMin(), pending.getMax()) - << " because the chunk overlaps " << overlapToString(overlap); - - warning() << *errMsg << endl; - return NULL; - } - - unique_ptr metadata(new CollectionMetadata); - metadata->_keyPattern = this->_keyPattern; - metadata->_keyPattern.getOwned(); - metadata->fillKeyPatternFields(); - metadata->_pendingMap = this->_pendingMap; - metadata->_chunksMap = this->_chunksMap; - metadata->_rangesMap = this->_rangesMap; - metadata->_shardVersion = _shardVersion; - metadata->_collVersion = _collVersion; - - // If there are any pending chunks on the interval to be added this is ok, since pending - // chunks aren't officially tracked yet and something may have changed on servers we do not - // see yet. - // We remove any chunks we overlap, the remote request starting a chunk migration must have - // been authoritative. - - if (rangeMapOverlaps(_pendingMap, pending.getMin(), pending.getMax())) { - RangeVector pendingOverlap; - getRangeMapOverlap(_pendingMap, pending.getMin(), pending.getMax(), &pendingOverlap); - - warning() << "new pending chunk " << rangeToString(pending.getMin(), pending.getMax()) - << " overlaps existing pending chunks " << overlapToString(pendingOverlap) - << ", a migration may not have completed" << endl; - - for (RangeVector::iterator it = pendingOverlap.begin(); it != pendingOverlap.end(); ++it) { - metadata->_pendingMap.erase(it->first); - } - } - - metadata->_pendingMap.insert(make_pair(pending.getMin(), pending.getMax())); - - invariant(metadata->isValid()); - return metadata.release(); -} - -CollectionMetadata* CollectionMetadata::cloneSplit(const ChunkType& chunk, - const vector& splitKeys, - const ChunkVersion& newShardVersion, - string* errMsg) const { - // The error message string is optional. - string dummy; - if (errMsg == NULL) { - errMsg = &dummy; - } - - // The version required in both resulting chunks could be simply an increment in the - // minor portion of the current version. However, we are enforcing uniqueness over the - // attributes of the configdb collection 'chunks'. So in practice, a - // migrate somewhere may force this split to pick up a version that has the major - // portion higher than the one that this shard has been using. - // - // TODO drop the uniqueness constraint and tighten the check below so that only the - // minor portion of version changes - if (newShardVersion <= _shardVersion) { - *errMsg = stream() << "cannot split chunk " << rangeToString(chunk.getMin(), chunk.getMax()) - << ", new shard version " << newShardVersion.toString() - << " is not greater than current version " << _shardVersion.toString(); - - warning() << *errMsg << endl; - return NULL; - } - - // Check that we have the exact chunk that will be subtracted. - if (!rangeMapContains(_chunksMap, chunk.getMin(), chunk.getMax())) { - *errMsg = stream() << "cannot split chunk " << rangeToString(chunk.getMin(), chunk.getMax()) - << ", this shard does not contain the chunk"; - - if (rangeMapOverlaps(_chunksMap, chunk.getMin(), chunk.getMax())) { - RangeVector overlap; - getRangeMapOverlap(_chunksMap, chunk.getMin(), chunk.getMax(), &overlap); - - *errMsg += stream() << " and it overlaps " << overlapToString(overlap); - } - - warning() << *errMsg << endl; - return NULL; - } - - // Check that the split key is valid - for (vector::const_iterator it = splitKeys.begin(); it != splitKeys.end(); ++it) { - if (!rangeContains(chunk.getMin(), chunk.getMax(), *it)) { - *errMsg = stream() << "cannot split chunk " - << rangeToString(chunk.getMin(), chunk.getMax()) << " at key " - << *it; - - warning() << *errMsg << endl; - return NULL; - } - } - - unique_ptr metadata(new CollectionMetadata); - metadata->_keyPattern = this->_keyPattern; - metadata->_keyPattern.getOwned(); - metadata->fillKeyPatternFields(); - metadata->_pendingMap = this->_pendingMap; - metadata->_chunksMap = this->_chunksMap; - metadata->_shardVersion = newShardVersion; // will increment 2nd, 3rd,... chunks below - - BSONObj startKey = chunk.getMin(); - for (vector::const_iterator it = splitKeys.begin(); it != splitKeys.end(); ++it) { - BSONObj split = *it; - invariant(split.woCompare(startKey) > 0); - metadata->_chunksMap[startKey] = split.getOwned(); - metadata->_chunksMap.insert(make_pair(split.getOwned(), chunk.getMax().getOwned())); - metadata->_shardVersion.incMinor(); - startKey = split; - } - - metadata->_collVersion = - metadata->_shardVersion > _collVersion ? metadata->_shardVersion : _collVersion; - metadata->fillRanges(); - - invariant(metadata->isValid()); - return metadata.release(); -} - -CollectionMetadata* CollectionMetadata::cloneMerge(const BSONObj& minKey, - const BSONObj& maxKey, - const ChunkVersion& newShardVersion, - string* errMsg) const { - if (newShardVersion <= _shardVersion) { - *errMsg = stream() << "cannot merge range " << rangeToString(minKey, maxKey) - << ", new shard version " << newShardVersion.toString() - << " is not greater than current version " << _shardVersion.toString(); - - warning() << *errMsg << endl; - return NULL; - } - - RangeVector overlap; - getRangeMapOverlap(_chunksMap, minKey, maxKey, &overlap); - - if (overlap.empty() || overlap.size() == 1) { - *errMsg = stream() << "cannot merge range " << rangeToString(minKey, maxKey) - << (overlap.empty() ? ", no chunks found in this range" - : ", only one chunk found in this range"); - - warning() << *errMsg << endl; - return NULL; - } - - bool validStartEnd = true; - bool validNoHoles = true; - if (overlap.begin()->first.woCompare(minKey) != 0) { - // First chunk doesn't start with minKey - validStartEnd = false; - } else if (overlap.rbegin()->second.woCompare(maxKey) != 0) { - // Last chunk doesn't end with maxKey - validStartEnd = false; - } else { - // Check that there are no holes - BSONObj prevMaxKey = minKey; - for (RangeVector::iterator it = overlap.begin(); it != overlap.end(); ++it) { - if (it->first.woCompare(prevMaxKey) != 0) { - validNoHoles = false; - break; - } - prevMaxKey = it->second; - } - } - - if (!validStartEnd || !validNoHoles) { - *errMsg = stream() << "cannot merge range " << rangeToString(minKey, maxKey) - << ", overlapping chunks " << overlapToString(overlap) - << (!validStartEnd ? " do not have the same min and max key" - : " are not all adjacent"); - - warning() << *errMsg << endl; - return NULL; - } - - unique_ptr metadata(new CollectionMetadata); - metadata->_keyPattern = this->_keyPattern; - metadata->_keyPattern.getOwned(); - metadata->fillKeyPatternFields(); - metadata->_pendingMap = this->_pendingMap; - metadata->_chunksMap = this->_chunksMap; - metadata->_rangesMap = this->_rangesMap; - metadata->_shardVersion = newShardVersion; - metadata->_collVersion = newShardVersion > _collVersion ? newShardVersion : this->_collVersion; - - for (RangeVector::iterator it = overlap.begin(); it != overlap.end(); ++it) { - metadata->_chunksMap.erase(it->first); - } - - metadata->_chunksMap.insert(make_pair(minKey, maxKey)); - - invariant(metadata->isValid()); - return metadata.release(); -} - -bool CollectionMetadata::keyBelongsToMe(const BSONObj& key) const { - // For now, collections don't move. So if the collection is not sharded, assume - // the document with the given key can be accessed. - if (_keyPattern.isEmpty()) { - return true; - } - - if (_rangesMap.size() <= 0) { - return false; - } - - RangeMap::const_iterator it = _rangesMap.upper_bound(key); - if (it != _rangesMap.begin()) - it--; - - bool good = rangeContains(it->first, it->second, key); - -#if 0 - // DISABLED because of SERVER-11175 - huge amount of logging - // Logs if the point doesn't belong here. - if ( !good ) { - log() << "bad: " << key << " " << it->first << " " << key.woCompare( it->first ) << " " - << key.woCompare( it->second ) << endl; - - for ( RangeMap::const_iterator i = _rangesMap.begin(); i != _rangesMap.end(); ++i ) { - log() << "\t" << i->first << "\t" << i->second << "\t" << endl; - } - } -#endif - - return good; -} - -bool CollectionMetadata::keyIsPending(const BSONObj& key) const { - // If we aren't sharded, then the key is never pending (though it belongs-to-me) - if (_keyPattern.isEmpty()) { - return false; - } - - if (_pendingMap.size() <= 0) { - return false; - } - - RangeMap::const_iterator it = _pendingMap.upper_bound(key); - if (it != _pendingMap.begin()) - it--; - - bool isPending = rangeContains(it->first, it->second, key); - return isPending; -} - -bool CollectionMetadata::getNextChunk(const BSONObj& lookupKey, ChunkType* chunk) const { - RangeMap::const_iterator upperChunkIt = _chunksMap.upper_bound(lookupKey); - RangeMap::const_iterator lowerChunkIt = upperChunkIt; - - if (upperChunkIt != _chunksMap.begin()) { - --lowerChunkIt; - } else { - lowerChunkIt = _chunksMap.end(); - } - - if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(lookupKey) > 0) { - chunk->setMin(lowerChunkIt->first); - chunk->setMax(lowerChunkIt->second); - return true; - } - - if (upperChunkIt != _chunksMap.end()) { - chunk->setMin(upperChunkIt->first); - chunk->setMax(upperChunkIt->second); - return true; - } - - return false; -} - -BSONObj CollectionMetadata::toBSON() const { - BSONObjBuilder bb; - toBSON(bb); - return bb.obj(); -} - -void CollectionMetadata::toBSONChunks(BSONArrayBuilder& bb) const { - if (_chunksMap.empty()) - return; - - for (RangeMap::const_iterator it = _chunksMap.begin(); it != _chunksMap.end(); ++it) { - BSONArrayBuilder chunkBB(bb.subarrayStart()); - chunkBB.append(it->first); - chunkBB.append(it->second); - chunkBB.done(); - } -} - -void CollectionMetadata::toBSONPending(BSONArrayBuilder& bb) const { - if (_pendingMap.empty()) - return; - - for (RangeMap::const_iterator it = _pendingMap.begin(); it != _pendingMap.end(); ++it) { - BSONArrayBuilder pendingBB(bb.subarrayStart()); - pendingBB.append(it->first); - pendingBB.append(it->second); - pendingBB.done(); - } -} - -void CollectionMetadata::toBSON(BSONObjBuilder& bb) const { - _collVersion.addToBSON(bb, "collVersion"); - _shardVersion.addToBSON(bb, "shardVersion"); - bb.append("keyPattern", _keyPattern); - - BSONArrayBuilder chunksBB(bb.subarrayStart("chunks")); - toBSONChunks(chunksBB); - chunksBB.done(); - - BSONArrayBuilder pendingBB(bb.subarrayStart("pending")); - toBSONPending(pendingBB); - pendingBB.done(); -} - -bool CollectionMetadata::getNextOrphanRange(const BSONObj& origLookupKey, KeyRange* range) const { - if (_keyPattern.isEmpty()) - return false; - - BSONObj lookupKey = origLookupKey; - BSONObj maxKey = getMaxKey(); // so we don't keep rebuilding - while (lookupKey.woCompare(maxKey) < 0) { - RangeMap::const_iterator lowerChunkIt = _chunksMap.end(); - RangeMap::const_iterator upperChunkIt = _chunksMap.end(); - - if (!_chunksMap.empty()) { - upperChunkIt = _chunksMap.upper_bound(lookupKey); - lowerChunkIt = upperChunkIt; - if (upperChunkIt != _chunksMap.begin()) - --lowerChunkIt; - else - lowerChunkIt = _chunksMap.end(); - } - - // If we overlap, continue after the overlap - // TODO: Could optimize slightly by finding next non-contiguous chunk - if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(lookupKey) > 0) { - lookupKey = lowerChunkIt->second; - continue; - } - - RangeMap::const_iterator lowerPendingIt = _pendingMap.end(); - RangeMap::const_iterator upperPendingIt = _pendingMap.end(); - - if (!_pendingMap.empty()) { - upperPendingIt = _pendingMap.upper_bound(lookupKey); - lowerPendingIt = upperPendingIt; - if (upperPendingIt != _pendingMap.begin()) - --lowerPendingIt; - else - lowerPendingIt = _pendingMap.end(); - } - - // If we overlap, continue after the overlap - // TODO: Could optimize slightly by finding next non-contiguous chunk - if (lowerPendingIt != _pendingMap.end() && - lowerPendingIt->second.woCompare(lookupKey) > 0) { - lookupKey = lowerPendingIt->second; - continue; - } - - // - // We know that the lookup key is not covered by a chunk or pending range, and where the - // previous chunk and pending chunks are. Now we fill in the bounds as the closest - // bounds of the surrounding ranges in both maps. - // - - range->keyPattern = _keyPattern; - range->minKey = getMinKey(); - range->maxKey = maxKey; - - if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(range->minKey) > 0) { - range->minKey = lowerChunkIt->second; - } - - if (upperChunkIt != _chunksMap.end() && upperChunkIt->first.woCompare(range->maxKey) < 0) { - range->maxKey = upperChunkIt->first; - } - - if (lowerPendingIt != _pendingMap.end() && - lowerPendingIt->second.woCompare(range->minKey) > 0) { - range->minKey = lowerPendingIt->second; - } - - if (upperPendingIt != _pendingMap.end() && - upperPendingIt->first.woCompare(range->maxKey) < 0) { - range->maxKey = upperPendingIt->first; - } - - return true; - } - - return false; -} - -string CollectionMetadata::toString() const { - StringBuilder ss; - ss << " CollectionManager version: " << _shardVersion.toString() << " key: " << _keyPattern; - if (_rangesMap.empty()) { - return ss.str(); - } - - RangeMap::const_iterator it = _rangesMap.begin(); - ss << it->first << " -> " << it->second; - while (it != _rangesMap.end()) { - ss << ", " << it->first << " -> " << it->second; - } - return ss.str(); -} - -BSONObj CollectionMetadata::getMinKey() const { - BSONObjIterator it(_keyPattern); - BSONObjBuilder minKeyB; - while (it.more()) - minKeyB << it.next().fieldName() << MINKEY; - return minKeyB.obj(); -} - -BSONObj CollectionMetadata::getMaxKey() const { - BSONObjIterator it(_keyPattern); - BSONObjBuilder maxKeyB; - while (it.more()) - maxKeyB << it.next().fieldName() << MAXKEY; - return maxKeyB.obj(); -} - -bool CollectionMetadata::isValid() const { - if (_shardVersion > _collVersion) - return false; - if (_collVersion.majorVersion() == 0) - return false; - if (_collVersion.epoch() != _shardVersion.epoch()) - return false; - - if (_shardVersion.majorVersion() > 0) { - // Must be chunks - if (_rangesMap.size() == 0 || _chunksMap.size() == 0) - return false; - } else { - // No chunks - if (_shardVersion.minorVersion() > 0) - return false; - if (_rangesMap.size() > 0 || _chunksMap.size() > 0) - return false; - } - - return true; -} - -bool CollectionMetadata::isValidKey(const BSONObj& key) const { - BSONObjIterator it(_keyPattern); - while (it.more()) { - BSONElement next = it.next(); - if (!key.hasField(next.fieldName())) - return false; - } - return key.nFields() == _keyPattern.nFields(); -} - -void CollectionMetadata::fillRanges() { - if (_chunksMap.empty()) - return; - - // Load the chunk information, coallesceing their ranges. The version for this shard - // would be the highest version for any of the chunks. - RangeMap::const_iterator it = _chunksMap.begin(); - BSONObj min, max; - while (it != _chunksMap.end()) { - BSONObj currMin = it->first; - BSONObj currMax = it->second; - ++it; - - // coalesce the chunk's bounds in ranges if they are adjacent chunks - if (min.isEmpty()) { - min = currMin; - max = currMax; - continue; - } - if (max == currMin) { - max = currMax; - continue; - } - - _rangesMap.insert(make_pair(min, max)); - - min = currMin; - max = currMax; - } - dassert(!min.isEmpty()); - - _rangesMap.insert(make_pair(min, max)); -} - -void CollectionMetadata::fillKeyPatternFields() { - // Parse the shard keys into the states 'keys' and 'keySet' members. - BSONObjIterator patternIter = _keyPattern.begin(); - while (patternIter.more()) { - BSONElement current = patternIter.next(); - - _keyFields.mutableVector().push_back(new FieldRef); - FieldRef* const newFieldRef = _keyFields.mutableVector().back(); - newFieldRef->parse(current.fieldNameStringData()); - } -} - - -} // namespace mongo diff --git a/src/mongo/s/collection_metadata.h b/src/mongo/s/collection_metadata.h deleted file mode 100644 index 5f3ae419dbd..00000000000 --- a/src/mongo/s/collection_metadata.h +++ /dev/null @@ -1,318 +0,0 @@ -/** - * Copyright (C) 2012 10gen 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 . - * - * 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/disallow_copying.h" -#include "mongo/base/owned_pointer_vector.h" -#include "mongo/db/field_ref_set.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/range_arithmetic.h" -#include "mongo/s/catalog/type_chunk.h" -#include "mongo/s/chunk_version.h" - -namespace mongo { - -class MetadataLoader; -class CollectionMetadata; - -typedef std::shared_ptr CollectionMetadataPtr; - -/** - * The collection metadata has metadata information about a collection, in particular the - * sharding information. It's main goal in life is to be capable of answering if a certain - * document belongs to it or not. (In some scenarios such as chunk migration, a given - * document is in a shard but cannot be accessed.) - * - * To build a collection from config data, please check the MetadataLoader. The methods - * here allow building a new incarnation of a collection's metadata based on an existing - * one (e.g, we're splitting in a given collection.). - * - * This class is immutable once constructed. - */ -class CollectionMetadata { - MONGO_DISALLOW_COPYING(CollectionMetadata); - -public: - ~CollectionMetadata(); - - // - // cloning support - // - - /** - * Returns a new metadata's instance based on 'this's state by removing a 'pending' chunk. - * - * The shard and collection version of the new metadata are unaffected. The caller owns the - * new metadata. - * - * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was - * provided. - */ - CollectionMetadata* cloneMinusPending(const ChunkType& pending, std::string* errMsg) const; - - /** - * Returns a new metadata's instance based on 'this's state by adding a 'pending' chunk. - * - * The shard and collection version of the new metadata are unaffected. The caller owns the - * new metadata. - * - * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was - * provided. - */ - CollectionMetadata* clonePlusPending(const ChunkType& pending, std::string* errMsg) const; - - /** - * Returns a new metadata's instance based on 'this's state by removing 'chunk'. - * When cloning away the last chunk, 'newShardVersion' must be zero. In any case, - * the caller owns the new metadata when the cloning is successful. - * - * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was - * provided. - */ - CollectionMetadata* cloneMigrate(const ChunkType& chunk, - const ChunkVersion& newShardVersion, - std::string* errMsg) const; - - /** - * Returns a new metadata's instance by splitting an existing 'chunk' at the points - * described by 'splitKeys'. The first resulting chunk will have 'newShardVersion' and - * subsequent one would have that with the minor version incremented at each chunk. The - * caller owns the metadata. - * - * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was - * provided. - * - * Note: 'splitKeys' must be sorted in ascending order. - */ - CollectionMetadata* cloneSplit(const ChunkType& chunk, - const std::vector& splitKeys, - const ChunkVersion& newShardVersion, - std::string* errMsg) const; - - /** - * Returns a new metadata instance by merging a key range which starts and ends at existing - * chunks into a single chunk. The range may not have holes. The resulting metadata will - * have the 'newShardVersion'. The caller owns the new metadata. - * - * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was - * provided. - */ - CollectionMetadata* cloneMerge(const BSONObj& minKey, - const BSONObj& maxKey, - const ChunkVersion& newShardVersion, - std::string* errMsg) const; - - // - // verification logic - // - - /** - * Returns true if the document key 'key' is a valid instance of a shard key for this - * metadata. The 'key' must contain exactly the same fields as the shard key pattern. - */ - bool isValidKey(const BSONObj& key) const; - - /** - * Returns true if the document key 'key' belongs to this chunkset. Recall that documents of - * an in-flight chunk migration may be present and should not be considered part of the - * collection / chunkset yet. Key must be the full shard key. - */ - bool keyBelongsToMe(const BSONObj& key) const; - - /** - * Returns true if the document key 'key' is or has been migrated to this shard, and may - * belong to us after a subsequent config reload. Key must be the full shard key. - */ - bool keyIsPending(const BSONObj& key) const; - - /** - * Given a key 'lookupKey' in the shard key range, get the next chunk which overlaps or is - * greater than this key. Returns true if a chunk exists, false otherwise. - * - * Passing a key that is not a valid shard key for this range results in undefined behavior. - */ - bool getNextChunk(const BSONObj& lookupKey, ChunkType* chunk) const; - - /** - * Given a key in the shard key range, get the next range which overlaps or is greater than - * this key. - * - * This allows us to do the following to iterate over all orphan ranges: - * - * KeyRange range; - * BSONObj lookupKey = metadata->getMinKey(); - * while( metadata->getNextOrphanRange( lookupKey, &orphanRange ) ) { - * // Do stuff with range - * lookupKey = orphanRange.maxKey; - * } - * - * @param lookupKey passing a key that does not belong to this metadata is undefined. - * @param orphanRange the output range. Note that the NS is not set. - */ - bool getNextOrphanRange(const BSONObj& lookupKey, KeyRange* orphanRange) const; - - // - // accessors - // - - ChunkVersion getCollVersion() const { - return _collVersion; - } - - ChunkVersion getShardVersion() const { - return _shardVersion; - } - - BSONObj getKeyPattern() const { - return _keyPattern; - } - - const std::vector& getKeyPatternFields() const { - return _keyFields.vector(); - } - - BSONObj getMinKey() const; - - BSONObj getMaxKey() const; - - std::size_t getNumChunks() const { - return _chunksMap.size(); - } - - std::size_t getNumPending() const { - return _pendingMap.size(); - } - - // - // reporting - // - - /** - * BSON output of the metadata information. - */ - BSONObj toBSON() const; - - /** - * BSON output of the metadata information, into a builder. - */ - void toBSON(BSONObjBuilder& bb) const; - - /** - * BSON output of the chunks metadata into a BSONArray - */ - void toBSONChunks(BSONArrayBuilder& bb) const; - - /** - * BSON output of the pending metadata into a BSONArray - */ - void toBSONPending(BSONArrayBuilder& bb) const; - - /** - * std::string output of the metadata information. - */ - std::string toString() const; - - /** - * Use the MetadataLoader to fill the empty metadata from the config server, or use - * clone*() methods to use existing metadatas to build new ones. - * - * Unless you are the MetadataLoader or a test you should probably not be using this - * directly. - */ - CollectionMetadata(); - - /** - * TESTING ONLY - * - * Returns a new metadata's instance based on 'this's state by adding 'chunk'. The new - * metadata can never be zero, though (see cloneMinus). The caller owns the new metadata. - * - * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was - * provided. - */ - CollectionMetadata* clonePlusChunk(const ChunkType& chunk, - const ChunkVersion& newShardVersion, - std::string* errMsg) const; - -private: - // Effectively, the MetadataLoader is this class's builder. So we open an exception - // and grant it friendship. - friend class MetadataLoader; - - // a version for this collection that identifies the collection incarnation (ie, a - // dropped and recreated collection with the same name would have a different version) - ChunkVersion _collVersion; - - // - // sharded state below, for when the collection gets sharded - // - - // highest ChunkVersion for which this metadata's information is accurate - ChunkVersion _shardVersion; - - // key pattern for chunks under this range - BSONObj _keyPattern; - - // A vector owning the FieldRefs parsed from the shard-key pattern of field names. - OwnedPointerVector _keyFields; - - // - // RangeMaps represent chunks by mapping the min key to the chunk's max key, allowing - // efficient lookup and intersection. - // - - // Map of ranges of chunks that are migrating but have not been confirmed added yet - RangeMap _pendingMap; - - // Map of chunks tracked by this shard - RangeMap _chunksMap; - - // A second map from a min key into a range or contiguous chunks. The map is redundant - // w.r.t. _chunkMap but we expect high chunk contiguity, especially in small - // installations. - RangeMap _rangesMap; - - /** - * Returns true if this metadata was loaded with all necessary information. - */ - bool isValid() const; - - /** - * Try to find chunks that are adjacent and record these intervals in the _rangesMap - */ - void fillRanges(); - - /** - * Creates the _keyField* local data - */ - void fillKeyPatternFields(); -}; - -} // namespace mongo diff --git a/src/mongo/s/collection_metadata_test.cpp b/src/mongo/s/collection_metadata_test.cpp deleted file mode 100644 index 8fe16a8d5f5..00000000000 --- a/src/mongo/s/collection_metadata_test.cpp +++ /dev/null @@ -1,1321 +0,0 @@ -/** - * Copyright (C) 2012 10gen 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 . - * - * 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 -#include - -#include "mongo/db/jsobj.h" -#include "mongo/dbtests/mock/mock_conn_registry.h" -#include "mongo/dbtests/mock/mock_remote_db_server.h" -#include "mongo/s/catalog/legacy/catalog_manager_legacy.h" -#include "mongo/s/catalog/type_chunk.h" -#include "mongo/s/catalog/type_collection.h" -#include "mongo/s/chunk_version.h" -#include "mongo/s/collection_metadata.h" -#include "mongo/s/metadata_loader.h" -#include "mongo/unittest/unittest.h" -#include "mongo/util/net/hostandport.h" - -namespace { - -using namespace mongo; - -using std::make_pair; -using std::string; -using std::unique_ptr; -using std::vector; - -const std::string CONFIG_HOST_PORT = "$dummy_config:27017"; - -class NoChunkFixture : public mongo::unittest::Test { -protected: - void setUp() { - _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(_dummyConfig.get()); - - OID epoch = OID::gen(); - - CollectionType collType; - collType.setNs(NamespaceString{"test.foo"}); - collType.setKeyPattern(BSON("a" << 1)); - collType.setUnique(false); - collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - collType.setEpoch(epoch); - ASSERT_OK(collType.validate()); - - _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); - - // Need a chunk on another shard, otherwise the chunks are invalid in general and we - // can't load metadata - ChunkType chunkType; - chunkType.setNS(NamespaceString{"test.foo"}.ns()); - chunkType.setShard("shard0001"); - chunkType.setMin(BSON("a" << MINKEY)); - chunkType.setMax(BSON("a" << MAXKEY)); - chunkType.setVersion(ChunkVersion(1, 0, epoch)); - chunkType.setName(OID::gen().toString()); - ASSERT_OK(collType.validate()); - - _dummyConfig->insert(ChunkType::ConfigNS, chunkType.toBSON()); - - ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); - ASSERT(configLoc.isValid()); - CatalogManagerLegacy catalogManager; - catalogManager.init(configLoc); - - MetadataLoader loader; - Status status = loader.makeCollectionMetadata( - &catalogManager, "test.foo", "shard0000", NULL, &_metadata); - ASSERT_OK(status); - ASSERT_EQUALS(0u, _metadata.getNumChunks()); - } - - void tearDown() { - MockConnRegistry::get()->clear(); - } - - const CollectionMetadata& getCollMetadata() const { - return _metadata; - } - -private: - unique_ptr _dummyConfig; - CollectionMetadata _metadata; -}; - -TEST_F(NoChunkFixture, BasicBelongsToMe) { - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY))); - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 10))); -} - -TEST_F(NoChunkFixture, CompoundKeyBelongsToMe) { - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 1 << "b" << 2))); -} - -TEST_F(NoChunkFixture, IsKeyValid) { - ASSERT_TRUE(getCollMetadata().isValidKey(BSON("a" - << "abcde"))); - ASSERT_TRUE(getCollMetadata().isValidKey(BSON("a" << 3))); - ASSERT_FALSE(getCollMetadata().isValidKey(BSON("a" - << "abcde" - << "b" << 1))); - ASSERT_FALSE(getCollMetadata().isValidKey(BSON("c" - << "abcde"))); -} - -TEST_F(NoChunkFixture, getNextFromEmpty) { - ChunkType nextChunk; - ASSERT(!getCollMetadata().getNextChunk(getCollMetadata().getMinKey(), &nextChunk)); -} - -TEST_F(NoChunkFixture, FirstChunkClonePlus) { - ChunkType chunk; - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - string errMsg; - const ChunkVersion version(99, 0, OID()); - unique_ptr cloned( - getCollMetadata().clonePlusChunk(chunk, version, &errMsg)); - - ASSERT(errMsg.empty()); - ASSERT_EQUALS(1u, cloned->getNumChunks()); - ASSERT_EQUALS(cloned->getShardVersion().toLong(), version.toLong()); - ASSERT_EQUALS(cloned->getCollVersion().toLong(), version.toLong()); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 15))); -} - -TEST_F(NoChunkFixture, MustHaveVersionForFirstChunk) { - ChunkType chunk; - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - string errMsg; - unique_ptr cloned( - getCollMetadata() // br - .clonePlusChunk(chunk, ChunkVersion(0, 0, OID()), &errMsg)); - - ASSERT(cloned == NULL); - ASSERT_FALSE(errMsg.empty()); -} - -TEST_F(NoChunkFixture, NoPendingChunks) { - ASSERT(!getCollMetadata().keyIsPending(BSON("a" << 15))); - ASSERT(!getCollMetadata().keyIsPending(BSON("a" << 25))); -} - -TEST_F(NoChunkFixture, FirstPendingChunk) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(cloned->keyIsPending(BSON("a" << 15))); - ASSERT(!cloned->keyIsPending(BSON("a" << 25))); - ASSERT(cloned->keyIsPending(BSON("a" << 10))); - ASSERT(!cloned->keyIsPending(BSON("a" << 20))); -} - -TEST_F(NoChunkFixture, EmptyMultiPendingChunk) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - chunk.setMin(BSON("a" << 40)); - chunk.setMax(BSON("a" << 50)); - - cloned.reset(cloned->clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(cloned->keyIsPending(BSON("a" << 15))); - ASSERT(!cloned->keyIsPending(BSON("a" << 25))); - ASSERT(cloned->keyIsPending(BSON("a" << 45))); - ASSERT(!cloned->keyIsPending(BSON("a" << 55))); -} - -TEST_F(NoChunkFixture, MinusPendingChunk) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - cloned.reset(cloned->cloneMinusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(!cloned->keyIsPending(BSON("a" << 15))); - ASSERT(!cloned->keyIsPending(BSON("a" << 25))); -} - -TEST_F(NoChunkFixture, OverlappingPendingChunk) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 30)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - chunk.setMin(BSON("a" << 20)); - chunk.setMax(BSON("a" << 40)); - - cloned.reset(cloned->clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(!cloned->keyIsPending(BSON("a" << 15))); - ASSERT(cloned->keyIsPending(BSON("a" << 25))); - ASSERT(cloned->keyIsPending(BSON("a" << 35))); - ASSERT(!cloned->keyIsPending(BSON("a" << 45))); -} - -TEST_F(NoChunkFixture, OverlappingPendingChunks) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 30)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - chunk.setMin(BSON("a" << 30)); - chunk.setMax(BSON("a" << 50)); - - cloned.reset(cloned->clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - chunk.setMin(BSON("a" << 20)); - chunk.setMax(BSON("a" << 40)); - - cloned.reset(cloned->clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(!cloned->keyIsPending(BSON("a" << 15))); - ASSERT(cloned->keyIsPending(BSON("a" << 25))); - ASSERT(cloned->keyIsPending(BSON("a" << 35))); - ASSERT(!cloned->keyIsPending(BSON("a" << 45))); -} - -TEST_F(NoChunkFixture, MinusInvalidPendingChunk) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 30)); - - cloned.reset(getCollMetadata().cloneMinusPending(chunk, &errMsg)); - - ASSERT_NOT_EQUALS(errMsg, ""); - ASSERT(cloned == NULL); -} - -TEST_F(NoChunkFixture, MinusOverlappingPendingChunk) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 30)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - chunk.setMin(BSON("a" << 15)); - chunk.setMax(BSON("a" << 35)); - - cloned.reset(cloned->cloneMinusPending(chunk, &errMsg)); - - ASSERT_NOT_EQUALS(errMsg, ""); - ASSERT(cloned == NULL); -} - -TEST_F(NoChunkFixture, PlusChunkWithPending) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(cloned->keyIsPending(BSON("a" << 15))); - ASSERT(!cloned->keyIsPending(BSON("a" << 25))); - - chunk.setMin(BSON("a" << 20)); - chunk.setMax(BSON("a" << 30)); - - cloned.reset(cloned->clonePlusChunk( - chunk, ChunkVersion(1, 0, cloned->getCollVersion().epoch()), &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(cloned->keyIsPending(BSON("a" << 15))); - ASSERT(!cloned->keyIsPending(BSON("a" << 25))); -} - -TEST_F(NoChunkFixture, MergeChunkEmpty) { - string errMsg; - unique_ptr cloned; - - cloned.reset(getCollMetadata().cloneMerge( - BSON("a" << 15), BSON("a" << 25), ChunkVersion(1, 0, OID::gen()), &errMsg)); - - ASSERT_NOT_EQUALS(errMsg, ""); - ASSERT(cloned == NULL); -} - -TEST_F(NoChunkFixture, OrphanedDataRangeBegin) { - const CollectionMetadata& metadata = getCollMetadata(); - - KeyRange keyRange; - BSONObj lookupKey = metadata.getMinKey(); - ASSERT(metadata.getNextOrphanRange(lookupKey, &keyRange)); - - ASSERT(keyRange.minKey.woCompare(metadata.getMinKey()) == 0); - ASSERT(keyRange.maxKey.woCompare(metadata.getMaxKey()) == 0); - - // Make sure we don't have any more ranges - ASSERT(!metadata.getNextOrphanRange(keyRange.maxKey, &keyRange)); -} - -TEST_F(NoChunkFixture, OrphanedDataRangeMiddle) { - const CollectionMetadata& metadata = getCollMetadata(); - - KeyRange keyRange; - BSONObj lookupKey = BSON("a" << 20); - ASSERT(metadata.getNextOrphanRange(lookupKey, &keyRange)); - - ASSERT(keyRange.minKey.woCompare(metadata.getMinKey()) == 0); - ASSERT(keyRange.maxKey.woCompare(metadata.getMaxKey()) == 0); - ASSERT(keyRange.keyPattern.woCompare(metadata.getKeyPattern()) == 0); - - // Make sure we don't have any more ranges - ASSERT(!metadata.getNextOrphanRange(keyRange.maxKey, &keyRange)); -} - -TEST_F(NoChunkFixture, OrphanedDataRangeEnd) { - const CollectionMetadata& metadata = getCollMetadata(); - - KeyRange keyRange; - ASSERT(!metadata.getNextOrphanRange(metadata.getMaxKey(), &keyRange)); -} - -TEST_F(NoChunkFixture, PendingOrphanedDataRanges) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - ASSERT_EQUALS(errMsg, string("")); - ASSERT(cloned != NULL); - - KeyRange keyRange; - ASSERT(cloned->getNextOrphanRange(cloned->getMinKey(), &keyRange)); - ASSERT(keyRange.minKey.woCompare(cloned->getMinKey()) == 0); - ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10)) == 0); - ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0); - - ASSERT(cloned->getNextOrphanRange(keyRange.maxKey, &keyRange)); - ASSERT(keyRange.minKey.woCompare(BSON("a" << 20)) == 0); - ASSERT(keyRange.maxKey.woCompare(cloned->getMaxKey()) == 0); - ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0); - - ASSERT(!cloned->getNextOrphanRange(keyRange.maxKey, &keyRange)); -} - -/** - * Fixture with single chunk containing: - * [10->20) - */ -class SingleChunkFixture : public mongo::unittest::Test { -protected: - void setUp() { - _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(_dummyConfig.get()); - - OID epoch = OID::gen(); - ChunkVersion chunkVersion = ChunkVersion(1, 0, epoch); - - CollectionType collType; - collType.setNs(NamespaceString{"test.foo"}); - collType.setKeyPattern(BSON("a" << 1)); - collType.setUnique(false); - collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - collType.setEpoch(epoch); - _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); - - BSONObj fooSingle = BSON( - ChunkType::name("test.foo-a_10") - << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << 10)) - << ChunkType::max(BSON("a" << 20)) - << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(chunkVersion.toLong())) - << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000")); - _dummyConfig->insert(ChunkType::ConfigNS, fooSingle); - - ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); - ASSERT(configLoc.isValid()); - CatalogManagerLegacy catalogManager; - catalogManager.init(configLoc); - - MetadataLoader loader; - Status status = loader.makeCollectionMetadata( - &catalogManager, "test.foo", "shard0000", NULL, &_metadata); - ASSERT_OK(status); - } - - void tearDown() { - MockConnRegistry::get()->clear(); - } - - const CollectionMetadata& getCollMetadata() const { - return _metadata; - } - -private: - unique_ptr _dummyConfig; - CollectionMetadata _metadata; -}; - -TEST_F(SingleChunkFixture, BasicBelongsToMe) { - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 10))); - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 15))); - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 19))); -} - -TEST_F(SingleChunkFixture, DoesntBelongsToMe) { - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 0))); - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 9))); - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 20))); - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 1234))); - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY))); - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY))); -} - -TEST_F(SingleChunkFixture, CompoudKeyBelongsToMe) { - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 15 << "a" << 14))); -} - -TEST_F(SingleChunkFixture, getNextFromEmpty) { - ChunkType nextChunk; - ASSERT(getCollMetadata().getNextChunk(getCollMetadata().getMinKey(), &nextChunk)); - ASSERT_EQUALS(0, nextChunk.getMin().woCompare(BSON("a" << 10))); - ASSERT_EQUALS(0, nextChunk.getMax().woCompare(BSON("a" << 20))); -} - -TEST_F(SingleChunkFixture, GetLastChunkIsFalse) { - ChunkType nextChunk; - ASSERT(!getCollMetadata().getNextChunk(getCollMetadata().getMaxKey(), &nextChunk)); -} - -TEST_F(SingleChunkFixture, LastChunkCloneMinus) { - ChunkType chunk; - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - string errMsg; - const ChunkVersion zeroVersion(0, 0, getCollMetadata().getShardVersion().epoch()); - unique_ptr cloned( - getCollMetadata().cloneMigrate(chunk, zeroVersion, &errMsg)); - - ASSERT(errMsg.empty()); - ASSERT_EQUALS(0u, cloned->getNumChunks()); - ASSERT_EQUALS(cloned->getShardVersion().toLong(), zeroVersion.toLong()); - ASSERT_EQUALS(cloned->getCollVersion().toLong(), getCollMetadata().getCollVersion().toLong()); - ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 15))); -} - -TEST_F(SingleChunkFixture, LastChunkMinusCantHaveNonZeroVersion) { - ChunkType chunk; - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - string errMsg; - ChunkVersion version(99, 0, OID()); - unique_ptr cloned(getCollMetadata().cloneMigrate(chunk, version, &errMsg)); - - ASSERT(cloned == NULL); - ASSERT_FALSE(errMsg.empty()); -} - -TEST_F(SingleChunkFixture, PlusPendingChunk) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 20)); - chunk.setMax(BSON("a" << 30)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(cloned->keyBelongsToMe(BSON("a" << 15))); - ASSERT(!cloned->keyBelongsToMe(BSON("a" << 25))); - ASSERT(!cloned->keyIsPending(BSON("a" << 15))); - ASSERT(cloned->keyIsPending(BSON("a" << 25))); -} - -TEST_F(SingleChunkFixture, PlusOverlapPendingChunk) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_NOT_EQUALS(errMsg, ""); - ASSERT(cloned == NULL); -} - -TEST_F(SingleChunkFixture, MinusChunkWithPending) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 20)); - chunk.setMax(BSON("a" << 30)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(cloned->keyIsPending(BSON("a" << 25))); - ASSERT(!cloned->keyIsPending(BSON("a" << 35))); - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - cloned.reset( - cloned->cloneMigrate(chunk, ChunkVersion(0, 0, cloned->getCollVersion().epoch()), &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(cloned->keyIsPending(BSON("a" << 25))); - ASSERT(!cloned->keyIsPending(BSON("a" << 35))); -} - -TEST_F(SingleChunkFixture, SingleSplit) { - ChunkVersion version; - getCollMetadata().getCollVersion().cloneTo(&version); - version.incMinor(); - - ChunkType chunk; - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - vector splitPoints; - splitPoints.push_back(BSON("a" << 14)); - - string errMsg; - unique_ptr cloned( - getCollMetadata().cloneSplit(chunk, splitPoints, version, &errMsg)); - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ChunkVersion newVersion(cloned->getCollVersion()); - ASSERT_EQUALS(version.epoch(), newVersion.epoch()); - ASSERT_EQUALS(version.majorVersion(), newVersion.majorVersion()); - ASSERT_EQUALS(version.minorVersion() + 1, newVersion.minorVersion()); - - ASSERT(cloned->getNextChunk(BSON("a" << MINKEY), &chunk)); - ASSERT(chunk.getMin().woCompare(BSON("a" << 10)) == 0); - ASSERT(chunk.getMax().woCompare(BSON("a" << 14)) == 0); - - ASSERT(cloned->getNextChunk(BSON("a" << 14), &chunk)); - ASSERT(chunk.getMin().woCompare(BSON("a" << 14)) == 0); - ASSERT(chunk.getMax().woCompare(BSON("a" << 20)) == 0); - - ASSERT_FALSE(cloned->getNextChunk(BSON("a" << 20), &chunk)); -} - -TEST_F(SingleChunkFixture, MultiSplit) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - vector splitPoints; - splitPoints.push_back(BSON("a" << 14)); - splitPoints.push_back(BSON("a" << 16)); - - ChunkVersion version; - getCollMetadata().getCollVersion().cloneTo(&version); - version.incMinor(); - - cloned.reset(getCollMetadata().cloneSplit(chunk, splitPoints, version, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ChunkVersion newVersion(cloned->getCollVersion()); - ASSERT_EQUALS(version.epoch(), newVersion.epoch()); - ASSERT_EQUALS(version.majorVersion(), newVersion.majorVersion()); - ASSERT_EQUALS(version.minorVersion() + 2, newVersion.minorVersion()); - - ASSERT(cloned->getNextChunk(BSON("a" << MINKEY), &chunk)); - ASSERT(chunk.getMin().woCompare(BSON("a" << 10)) == 0); - ASSERT(chunk.getMax().woCompare(BSON("a" << 14)) == 0); - - ASSERT(cloned->getNextChunk(BSON("a" << 14), &chunk)); - ASSERT(chunk.getMin().woCompare(BSON("a" << 14)) == 0); - ASSERT(chunk.getMax().woCompare(BSON("a" << 16)) == 0); - - ASSERT(cloned->getNextChunk(BSON("a" << 16), &chunk)); - ASSERT(chunk.getMin().woCompare(BSON("a" << 16)) == 0); - ASSERT(chunk.getMax().woCompare(BSON("a" << 20)) == 0); - - ASSERT_FALSE(cloned->getNextChunk(BSON("a" << 20), &chunk)); -} - -TEST_F(SingleChunkFixture, SplitChunkWithPending) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 20)); - chunk.setMax(BSON("a" << 30)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(cloned->keyIsPending(BSON("a" << 25))); - ASSERT(!cloned->keyIsPending(BSON("a" << 35))); - - chunk.setMin(BSON("a" << 10)); - chunk.setMax(BSON("a" << 20)); - - vector splitPoints; - splitPoints.push_back(BSON("a" << 14)); - splitPoints.push_back(BSON("a" << 16)); - - cloned.reset(cloned->cloneSplit(chunk, - splitPoints, - ChunkVersion(cloned->getCollVersion().majorVersion() + 1, - 0, - cloned->getCollVersion().epoch()), - &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - - ASSERT(cloned->keyIsPending(BSON("a" << 25))); - ASSERT(!cloned->keyIsPending(BSON("a" << 35))); -} - - -TEST_F(SingleChunkFixture, MergeChunkSingle) { - string errMsg; - unique_ptr cloned; - - cloned.reset(getCollMetadata().cloneMerge( - BSON("a" << 10), BSON("a" << 20), ChunkVersion(2, 0, OID::gen()), &errMsg)); - - ASSERT_NOT_EQUALS(errMsg, ""); - ASSERT(cloned == NULL); -} - -TEST_F(SingleChunkFixture, ChunkOrphanedDataRanges) { - KeyRange keyRange; - ASSERT(getCollMetadata().getNextOrphanRange(getCollMetadata().getMinKey(), &keyRange)); - ASSERT(keyRange.minKey.woCompare(getCollMetadata().getMinKey()) == 0); - ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10)) == 0); - ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); - - ASSERT(getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); - ASSERT(keyRange.minKey.woCompare(BSON("a" << 20)) == 0); - ASSERT(keyRange.maxKey.woCompare(getCollMetadata().getMaxKey()) == 0); - ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); - - ASSERT(!getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); -} - -/** - * Fixture with single chunk containing: - * [(min, min)->(max, max)) - */ -class SingleChunkMinMaxCompoundKeyFixture : public mongo::unittest::Test { -protected: - void setUp() { - _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(_dummyConfig.get()); - - OID epoch = OID::gen(); - ChunkVersion chunkVersion = ChunkVersion(1, 0, epoch); - - CollectionType collType; - collType.setNs(NamespaceString{"test.foo"}); - collType.setKeyPattern(BSON("a" << 1)); - collType.setUnique(false); - collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - collType.setEpoch(epoch); - _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); - - BSONObj fooSingle = BSON( - ChunkType::name("test.foo-a_MinKey") - << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << MINKEY << "b" << MINKEY)) - << ChunkType::max(BSON("a" << MAXKEY << "b" << MAXKEY)) - << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(chunkVersion.toLong())) - << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000")); - _dummyConfig->insert(ChunkType::ConfigNS, fooSingle); - - ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); - ASSERT(configLoc.isValid()); - CatalogManagerLegacy catalogManager; - catalogManager.init(configLoc); - - MetadataLoader loader; - Status status = loader.makeCollectionMetadata( - &catalogManager, "test.foo", "shard0000", NULL, &_metadata); - ASSERT_OK(status); - } - - void tearDown() { - MockConnRegistry::get()->clear(); - } - - const CollectionMetadata& getCollMetadata() const { - return _metadata; - } - -private: - unique_ptr _dummyConfig; - CollectionMetadata _metadata; -}; - -// Note: no tests for single key belongsToMe because they are not allowed -// if shard key is compound. - -TEST_F(SingleChunkMinMaxCompoundKeyFixture, CompoudKeyBelongsToMe) { - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY << "b" << MINKEY))); - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY << "b" << MAXKEY))); - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY << "b" << 10))); - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 10 << "b" << 20))); -} - -/** - * Fixture with chunks: - * [(10, 0)->(20, 0)), [(30, 0)->(40, 0)) - */ -class TwoChunksWithGapCompoundKeyFixture : public mongo::unittest::Test { -protected: - void setUp() { - _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(_dummyConfig.get()); - - OID epoch = OID::gen(); - ChunkVersion chunkVersion = ChunkVersion(1, 0, epoch); - - CollectionType collType; - collType.setNs(NamespaceString{"test.foo"}); - collType.setKeyPattern(BSON("a" << 1)); - collType.setUnique(false); - collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - collType.setEpoch(epoch); - _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); - - _dummyConfig->insert( - ChunkType::ConfigNS, - BSON(ChunkType::name("test.foo-a_10") - << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << 10 << "b" << 0)) - << ChunkType::max(BSON("a" << 20 << "b" << 0)) - << ChunkType::DEPRECATED_lastmod( - Date_t::fromMillisSinceEpoch(chunkVersion.toLong())) - << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000"))); - - _dummyConfig->insert( - ChunkType::ConfigNS, - BSON(ChunkType::name("test.foo-a_10") - << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << 30 << "b" << 0)) - << ChunkType::max(BSON("a" << 40 << "b" << 0)) - << ChunkType::DEPRECATED_lastmod( - Date_t::fromMillisSinceEpoch(chunkVersion.toLong())) - << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000"))); - - ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); - ASSERT(configLoc.isValid()); - CatalogManagerLegacy catalogManager; - catalogManager.init(configLoc); - - MetadataLoader loader; - Status status = loader.makeCollectionMetadata( - &catalogManager, "test.foo", "shard0000", NULL, &_metadata); - ASSERT_OK(status); - } - - void tearDown() { - MockConnRegistry::get()->clear(); - } - - const CollectionMetadata& getCollMetadata() const { - return _metadata; - } - -private: - unique_ptr _dummyConfig; - CollectionMetadata _metadata; -}; - -TEST_F(TwoChunksWithGapCompoundKeyFixture, ClonePlusBasic) { - ChunkType chunk; - chunk.setMin(BSON("a" << 40 << "b" << 0)); - chunk.setMax(BSON("a" << 50 << "b" << 0)); - - string errMsg; - ChunkVersion version(1, 0, getCollMetadata().getShardVersion().epoch()); - unique_ptr cloned( - getCollMetadata().clonePlusChunk(chunk, version, &errMsg)); - - ASSERT(errMsg.empty()); - ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); - ASSERT_EQUALS(3u, cloned->getNumChunks()); - - // TODO: test maxShardVersion, maxCollVersion - - ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 25 << "b" << 0))); - ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 29 << "b" << 0))); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 30 << "b" << 0))); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 45 << "b" << 0))); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 49 << "b" << 0))); - ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 50 << "b" << 0))); -} - -TEST_F(TwoChunksWithGapCompoundKeyFixture, ClonePlusOverlappingRange) { - ChunkType chunk; - chunk.setMin(BSON("a" << 15 << "b" << 0)); - chunk.setMax(BSON("a" << 25 << "b" << 0)); - - string errMsg; - unique_ptr cloned( - getCollMetadata().clonePlusChunk(chunk, ChunkVersion(1, 0, OID()), &errMsg)); - ASSERT(cloned == NULL); - ASSERT_FALSE(errMsg.empty()); - ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); -} - -TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneMinusBasic) { - ChunkType chunk; - chunk.setMin(BSON("a" << 10 << "b" << 0)); - chunk.setMax(BSON("a" << 20 << "b" << 0)); - - string errMsg; - ChunkVersion version(2, 0, OID()); - unique_ptr cloned(getCollMetadata().cloneMigrate(chunk, version, &errMsg)); - - ASSERT(errMsg.empty()); - ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); - ASSERT_EQUALS(1u, cloned->getNumChunks()); - - // TODO: test maxShardVersion, maxCollVersion - - ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 5 << "b" << 0))); - ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 15 << "b" << 0))); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 30 << "b" << 0))); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 35 << "b" << 0))); - ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 40 << "b" << 0))); -} - -TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneMinusNonExisting) { - ChunkType chunk; - chunk.setMin(BSON("a" << 25 << "b" << 0)); - chunk.setMax(BSON("a" << 28 << "b" << 0)); - - string errMsg; - unique_ptr cloned( - getCollMetadata().cloneMigrate(chunk, ChunkVersion(1, 0, OID()), &errMsg)); - ASSERT(cloned == NULL); - ASSERT_FALSE(errMsg.empty()); - ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); -} - -TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitBasic) { - const BSONObj min(BSON("a" << 10 << "b" << 0)); - const BSONObj max(BSON("a" << 20 << "b" << 0)); - - ChunkType chunk; - chunk.setMin(min); - chunk.setMax(max); - - const BSONObj split1(BSON("a" << 15 << "b" << 0)); - const BSONObj split2(BSON("a" << 18 << "b" << 0)); - vector splitKeys; - splitKeys.push_back(split1); - splitKeys.push_back(split2); - ChunkVersion version(1, 99, OID()); // first chunk 1|99 , second 1|100 - - string errMsg; - unique_ptr cloned( - getCollMetadata().cloneSplit(chunk, splitKeys, version, &errMsg)); - - version.incMinor(); /* second chunk 1|100, first split point */ - version.incMinor(); /* third chunk 1|101, second split point */ - ASSERT_EQUALS(cloned->getShardVersion().toLong(), version.toLong() /* 1|101 */); - ASSERT_EQUALS(cloned->getCollVersion().toLong(), version.toLong()); - ASSERT_EQUALS(getCollMetadata().getNumChunks(), 2u); - ASSERT_EQUALS(cloned->getNumChunks(), 4u); - ASSERT(cloned->keyBelongsToMe(min)); - ASSERT(cloned->keyBelongsToMe(split1)); - ASSERT(cloned->keyBelongsToMe(split2)); - ASSERT(!cloned->keyBelongsToMe(max)); -} - -TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitOutOfRangeSplitPoint) { - ChunkType chunk; - chunk.setMin(BSON("a" << 10 << "b" << 0)); - chunk.setMax(BSON("a" << 20 << "b" << 0)); - - vector splitKeys; - splitKeys.push_back(BSON("a" << 5 << "b" << 0)); - - string errMsg; - unique_ptr cloned( - getCollMetadata().cloneSplit(chunk, splitKeys, ChunkVersion(1, 0, OID()), &errMsg)); - - ASSERT(cloned == NULL); - ASSERT_FALSE(errMsg.empty()); - ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); -} - -TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitBadChunkRange) { - const BSONObj min(BSON("a" << 10 << "b" << 0)); - const BSONObj max(BSON("a" << 25 << "b" << 0)); - - ChunkType chunk; - chunk.setMin(BSON("a" << 10 << "b" << 0)); - chunk.setMax(BSON("a" << 25 << "b" << 0)); - - vector splitKeys; - splitKeys.push_back(BSON("a" << 15 << "b" << 0)); - - string errMsg; - unique_ptr cloned( - getCollMetadata().cloneSplit(chunk, splitKeys, ChunkVersion(1, 0, OID()), &errMsg)); - - ASSERT(cloned == NULL); - ASSERT_FALSE(errMsg.empty()); - ASSERT_EQUALS(2u, getCollMetadata().getNumChunks()); -} - -TEST_F(TwoChunksWithGapCompoundKeyFixture, ChunkGapOrphanedDataRanges) { - KeyRange keyRange; - ASSERT(getCollMetadata().getNextOrphanRange(getCollMetadata().getMinKey(), &keyRange)); - ASSERT(keyRange.minKey.woCompare(getCollMetadata().getMinKey()) == 0); - ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10 << "b" << 0)) == 0); - ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); - - ASSERT(getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); - ASSERT(keyRange.minKey.woCompare(BSON("a" << 20 << "b" << 0)) == 0); - ASSERT(keyRange.maxKey.woCompare(BSON("a" << 30 << "b" << 0)) == 0); - ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); - - ASSERT(getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); - ASSERT(keyRange.minKey.woCompare(BSON("a" << 40 << "b" << 0)) == 0); - ASSERT(keyRange.maxKey.woCompare(getCollMetadata().getMaxKey()) == 0); - ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0); - - ASSERT(!getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange)); -} - -TEST_F(TwoChunksWithGapCompoundKeyFixture, ChunkGapAndPendingOrphanedDataRanges) { - string errMsg; - ChunkType chunk; - unique_ptr cloned; - - chunk.setMin(BSON("a" << 20 << "b" << 0)); - chunk.setMax(BSON("a" << 30 << "b" << 0)); - - cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg)); - ASSERT_EQUALS(errMsg, string("")); - ASSERT(cloned != NULL); - - KeyRange keyRange; - ASSERT(cloned->getNextOrphanRange(cloned->getMinKey(), &keyRange)); - ASSERT(keyRange.minKey.woCompare(cloned->getMinKey()) == 0); - ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10 << "b" << 0)) == 0); - ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0); - - ASSERT(cloned->getNextOrphanRange(keyRange.maxKey, &keyRange)); - ASSERT(keyRange.minKey.woCompare(BSON("a" << 40 << "b" << 0)) == 0); - ASSERT(keyRange.maxKey.woCompare(cloned->getMaxKey()) == 0); - ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0); - - ASSERT(!cloned->getNextOrphanRange(keyRange.maxKey, &keyRange)); -} - -/** - * Fixture with chunk containing: - * [min->10) , [10->20) , , [30->max) - */ -class ThreeChunkWithRangeGapFixture : public mongo::unittest::Test { -protected: - void setUp() { - _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(_dummyConfig.get()); - - OID epoch(OID::gen()); - - { - CollectionType collType; - collType.setNs(NamespaceString{"x.y"}); - collType.setKeyPattern(BSON("a" << 1)); - collType.setUnique(false); - collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - collType.setEpoch(epoch); - _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); - } - - { - ChunkVersion version(1, 1, epoch); - _dummyConfig->insert(ChunkType::ConfigNS, - BSON(ChunkType::name("x.y-a_MinKey") - << ChunkType::ns("x.y") << ChunkType::min(BSON("a" << MINKEY)) - << ChunkType::max(BSON("a" << 10)) - << ChunkType::DEPRECATED_lastmod( - Date_t::fromMillisSinceEpoch(version.toLong())) - << ChunkType::DEPRECATED_epoch(version.epoch()) - << ChunkType::shard("shard0000"))); - } - - { - ChunkVersion version(1, 3, epoch); - _dummyConfig->insert(ChunkType::ConfigNS, - BSON(ChunkType::name("x.y-a_10") - << ChunkType::ns("x.y") << ChunkType::min(BSON("a" << 10)) - << ChunkType::max(BSON("a" << 20)) - << ChunkType::DEPRECATED_lastmod( - Date_t::fromMillisSinceEpoch(version.toLong())) - << ChunkType::DEPRECATED_epoch(version.epoch()) - << ChunkType::shard("shard0000"))); - } - - { - ChunkVersion version(1, 2, epoch); - _dummyConfig->insert(ChunkType::ConfigNS, - BSON(ChunkType::name("x.y-a_30") - << ChunkType::ns("x.y") << ChunkType::min(BSON("a" << 30)) - << ChunkType::max(BSON("a" << MAXKEY)) - << ChunkType::DEPRECATED_lastmod( - Date_t::fromMillisSinceEpoch(version.toLong())) - << ChunkType::DEPRECATED_epoch(version.epoch()) - << ChunkType::shard("shard0000"))); - } - - ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); - ASSERT(configLoc.isValid()); - CatalogManagerLegacy catalogManager; - catalogManager.init(configLoc); - - MetadataLoader loader; - Status status = loader.makeCollectionMetadata( - &catalogManager, "test.foo", "shard0000", NULL, &_metadata); - ASSERT_OK(status); - } - - void tearDown() { - MockConnRegistry::get()->clear(); - } - - const CollectionMetadata& getCollMetadata() const { - return _metadata; - } - -private: - unique_ptr _dummyConfig; - CollectionMetadata _metadata; -}; - -TEST_F(ThreeChunkWithRangeGapFixture, ShardOwnsDoc) { - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 5))); - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 10))); - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 30))); - ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 40))); -} - -TEST_F(ThreeChunkWithRangeGapFixture, ShardDoesntOwnDoc) { - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 25))); - ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY))); -} - -TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromEmpty) { - ChunkType nextChunk; - ASSERT(getCollMetadata().getNextChunk(getCollMetadata().getMinKey(), &nextChunk)); - ASSERT_EQUALS(0, nextChunk.getMin().woCompare(BSON("a" << MINKEY))); - ASSERT_EQUALS(0, nextChunk.getMax().woCompare(BSON("a" << 10))); -} - -TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromMiddle) { - ChunkType nextChunk; - ASSERT(getCollMetadata().getNextChunk(BSON("a" << 20), &nextChunk)); - ASSERT_EQUALS(0, nextChunk.getMin().woCompare(BSON("a" << 30))); - ASSERT_EQUALS(0, nextChunk.getMax().woCompare(BSON("a" << MAXKEY))); -} - -TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromLast) { - ChunkType nextChunk; - ASSERT(getCollMetadata().getNextChunk(BSON("a" << 30), &nextChunk)); -} - -TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkHoleInRange) { - string errMsg; - unique_ptr cloned; - - // Try to merge with hole in range - ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); - cloned.reset(getCollMetadata().cloneMerge( - BSON("a" << 10), BSON("a" << MAXKEY), newShardVersion, &errMsg)); - - ASSERT_NOT_EQUALS(errMsg, ""); - ASSERT(cloned == NULL); -} - -TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkDiffEndKey) { - string errMsg; - unique_ptr cloned; - - // Try to merge with different end key - ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); - cloned.reset(getCollMetadata().cloneMerge( - BSON("a" << MINKEY), BSON("a" << 19), newShardVersion, &errMsg)); - - ASSERT_NOT_EQUALS(errMsg, ""); - ASSERT(cloned == NULL); -} - -TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMinKey) { - string errMsg; - unique_ptr cloned; - - ASSERT_EQUALS(getCollMetadata().getNumChunks(), 3u); - - // Try to merge lowest chunks together - ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); - cloned.reset(getCollMetadata().cloneMerge( - BSON("a" << MINKEY), BSON("a" << 20), newShardVersion, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 10))); - ASSERT_EQUALS(cloned->getNumChunks(), 2u); - ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 5); -} - -TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMaxKey) { - string errMsg; - unique_ptr cloned; - ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); - - // Add one chunk to complete the range - ChunkType chunk; - chunk.setMin(BSON("a" << 20)); - chunk.setMax(BSON("a" << 30)); - cloned.reset(getCollMetadata().clonePlusChunk(chunk, newShardVersion, &errMsg)); - ASSERT_EQUALS(errMsg, ""); - ASSERT_EQUALS(cloned->getNumChunks(), 4u); - ASSERT(cloned != NULL); - - // Try to merge highest chunks together - newShardVersion.incMajor(); - cloned.reset( - cloned->cloneMerge(BSON("a" << 20), BSON("a" << MAXKEY), newShardVersion, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 30))); - ASSERT_EQUALS(cloned->getNumChunks(), 3u); - ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 6); -} - -TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkFullRange) { - string errMsg; - unique_ptr cloned; - ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); - - // Add one chunk to complete the range - ChunkType chunk; - chunk.setMin(BSON("a" << 20)); - chunk.setMax(BSON("a" << 30)); - cloned.reset(getCollMetadata().clonePlusChunk(chunk, newShardVersion, &errMsg)); - ASSERT_EQUALS(errMsg, ""); - ASSERT_EQUALS(cloned->getNumChunks(), 4u); - ASSERT(cloned != NULL); - - // Try to merge all chunks together - newShardVersion.incMajor(); - cloned.reset( - cloned->cloneMerge(BSON("a" << MINKEY), BSON("a" << MAXKEY), newShardVersion, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 10))); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 30))); - ASSERT_EQUALS(cloned->getNumChunks(), 1u); - ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 6); -} - -TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMiddleRange) { - string errMsg; - unique_ptr cloned; - ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); - - // Add one chunk to complete the range - ChunkType chunk; - chunk.setMin(BSON("a" << 20)); - chunk.setMax(BSON("a" << 30)); - cloned.reset(getCollMetadata().clonePlusChunk(chunk, newShardVersion, &errMsg)); - ASSERT_EQUALS(errMsg, ""); - ASSERT_EQUALS(cloned->getNumChunks(), 4u); - ASSERT(cloned != NULL); - - // Try to merge middle two chunks - newShardVersion.incMajor(); - cloned.reset(cloned->cloneMerge(BSON("a" << 10), BSON("a" << 30), newShardVersion, &errMsg)); - - ASSERT_EQUALS(errMsg, ""); - ASSERT(cloned != NULL); - ASSERT(cloned->keyBelongsToMe(BSON("a" << 20))); - ASSERT_EQUALS(cloned->getNumChunks(), 3u); - ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 6); -} - -TEST_F(ThreeChunkWithRangeGapFixture, CannotMergeWithHole) { - string errMsg; - ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch()); - - // Try to merge middle two chunks with a hole in the middle. - newShardVersion.incMajor(); - CollectionMetadata* result = - getCollMetadata().cloneMerge(BSON("a" << 10), BSON("a" << 30), newShardVersion, &errMsg); - ASSERT(result == NULL); - ASSERT(!errMsg.empty()); -} - -} // unnamed namespace diff --git a/src/mongo/s/d_merge.cpp b/src/mongo/s/d_merge.cpp index 8f96a6fce3b..7c40db13bcf 100644 --- a/src/mongo/s/d_merge.cpp +++ b/src/mongo/s/d_merge.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2013 10gen Inc. + * Copyright (C) 2013-2015 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, @@ -17,13 +17,13 @@ * 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. + * 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 @@ -37,10 +37,11 @@ #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharding_state.h" #include "mongo/s/catalog/catalog_manager.h" +#include "mongo/s/catalog/type_chunk.h" #include "mongo/s/chunk.h" -#include "mongo/s/collection_metadata.h" #include "mongo/s/config.h" #include "mongo/s/catalog/dist_lock_manager.h" #include "mongo/s/grid.h" diff --git a/src/mongo/s/d_migrate.cpp b/src/mongo/s/d_migrate.cpp index 5b143d59b32..82f432b8925 100644 --- a/src/mongo/s/d_migrate.cpp +++ b/src/mongo/s/d_migrate.cpp @@ -64,6 +64,7 @@ #include "mongo/db/repl/replication_coordinator_global.h" #include "mongo/db/service_context.h" #include "mongo/db/storage/mmap_v1/dur.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharded_connection_info.h" #include "mongo/db/s/sharding_state.h" #include "mongo/db/write_concern.h" @@ -1560,7 +1561,8 @@ public: // if we have chunks left on the FROM shard, update the version of one of them as // well. we can figure that out by grabbing the metadata installed on 5.a - const CollectionMetadataPtr bumpedCollMetadata(shardingState.getCollectionMetadata(ns)); + const std::shared_ptr bumpedCollMetadata( + shardingState.getCollectionMetadata(ns)); if (bumpedCollMetadata->getNumChunks() > 0) { // get another chunk on that shard ChunkType bumpChunk; diff --git a/src/mongo/s/d_split.cpp b/src/mongo/s/d_split.cpp index fba6f67f6f1..977fe575e55 100644 --- a/src/mongo/s/d_split.cpp +++ b/src/mongo/s/d_split.cpp @@ -1,32 +1,30 @@ -// @file d_split.cpp - /** -* Copyright (C) 2008-2014 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 . -* -* 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. -*/ + * Copyright (C) 2008-2014 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 . + * + * 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 @@ -53,6 +51,7 @@ #include "mongo/db/instance.h" #include "mongo/db/jsobj.h" #include "mongo/db/query/internal_plans.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharding_state.h" #include "mongo/s/catalog/catalog_manager.h" #include "mongo/s/catalog/type_chunk.h" @@ -696,7 +695,8 @@ public: } // Get collection metadata - const CollectionMetadataPtr collMetadata(shardingState.getCollectionMetadata(ns)); + const std::shared_ptr collMetadata( + shardingState.getCollectionMetadata(ns)); // With nonzero shard version, we must have metadata invariant(NULL != collMetadata); diff --git a/src/mongo/s/d_state.cpp b/src/mongo/s/d_state.cpp index e2c438a3b38..81422a68d7c 100644 --- a/src/mongo/s/d_state.cpp +++ b/src/mongo/s/d_state.cpp @@ -1,32 +1,30 @@ -// @file d_state.cpp - /** -* Copyright (C) 2008 10gen 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 . -* -* 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. -*/ + * Copyright (C) 2008-2015 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 . + * + * 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 @@ -49,10 +47,10 @@ #include "mongo/db/lasterror.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/s/collection_metadata.h" #include "mongo/db/s/sharded_connection_info.h" #include "mongo/db/s/sharding_state.h" #include "mongo/db/wire_version.h" -#include "mongo/s/collection_metadata.h" #include "mongo/s/config.h" #include "mongo/s/grid.h" #include "mongo/s/stale_exception.h" @@ -69,10 +67,6 @@ using std::string; using std::stringstream; using std::vector; -bool isMongos() { - return false; -} - ShardForceVersionOkModeBlock::ShardForceVersionOkModeBlock(Client* client) { info = ShardedConnectionInfo::get(client, false); if (info) diff --git a/src/mongo/s/metadata_loader.cpp b/src/mongo/s/metadata_loader.cpp deleted file mode 100644 index 4bb156f156d..00000000000 --- a/src/mongo/s/metadata_loader.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/** - * Copyright (C) 2012 10gen 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 . - * - * 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/metadata_loader.h" - -#include - -#include "mongo/s/catalog/catalog_manager.h" -#include "mongo/s/catalog/type_chunk.h" -#include "mongo/s/catalog/type_collection.h" -#include "mongo/s/chunk_diff.h" -#include "mongo/s/chunk_version.h" -#include "mongo/s/collection_metadata.h" -#include "mongo/util/log.h" - -namespace mongo { - -using std::unique_ptr; -using std::endl; -using std::make_pair; -using std::map; -using std::pair; -using std::string; - -/** - * This is an adapter so we can use config diffs - mongos and mongod do them slightly - * differently. - * - * The mongod adapter here tracks only a single shard, and stores ranges by (min, max). - */ -class SCMConfigDiffTracker : public ConfigDiffTracker { -public: - SCMConfigDiffTracker(const string& currShard) : _currShard(currShard) {} - - virtual bool isTracked(const ChunkType& chunk) const { - return chunk.getShard() == _currShard; - } - - virtual pair rangeFor(const ChunkType& chunk) const { - return make_pair(chunk.getMin(), chunk.getMax()); - } - - virtual string shardFor(const string& name) const { - return name; - } - - virtual string nameFrom(const string& shard) const { - return shard; - } - - string _currShard; -}; - -// -// MetadataLoader implementation -// - -MetadataLoader::MetadataLoader() {} - -MetadataLoader::~MetadataLoader() {} - -Status MetadataLoader::makeCollectionMetadata(CatalogManager* catalogManager, - const string& ns, - const string& shard, - const CollectionMetadata* oldMetadata, - CollectionMetadata* metadata) const { - Status status = _initCollection(catalogManager, ns, shard, metadata); - if (!status.isOK() || metadata->getKeyPattern().isEmpty()) { - return status; - } - - return initChunks(catalogManager, ns, shard, oldMetadata, metadata); -} - -Status MetadataLoader::_initCollection(CatalogManager* catalogManager, - const string& ns, - const string& shard, - CollectionMetadata* metadata) const { - auto coll = catalogManager->getCollection(ns); - if (!coll.isOK()) { - return coll.getStatus(); - } - - CollectionType collInfo = coll.getValue(); - if (collInfo.getDropped()) { - return Status(ErrorCodes::NamespaceNotFound, - str::stream() << "could not load metadata, collection " << ns - << " was dropped"); - } - - metadata->_keyPattern = collInfo.getKeyPattern().toBSON(); - metadata->fillKeyPatternFields(); - metadata->_shardVersion = ChunkVersion(0, 0, collInfo.getEpoch()); - metadata->_collVersion = ChunkVersion(0, 0, collInfo.getEpoch()); - - return Status::OK(); -} - -Status MetadataLoader::initChunks(CatalogManager* catalogManager, - const string& ns, - const string& shard, - const CollectionMetadata* oldMetadata, - CollectionMetadata* metadata) const { - map versionMap; - - // Preserve the epoch - versionMap[shard] = metadata->_shardVersion; - OID epoch = metadata->getCollVersion().epoch(); - bool fullReload = true; - - // Check to see if we should use the old version or not. - if (oldMetadata) { - // If our epochs are compatible, it's useful to use the old metadata for diffs - if (oldMetadata->getCollVersion().hasEqualEpoch(epoch)) { - fullReload = false; - invariant(oldMetadata->isValid()); - - versionMap[shard] = oldMetadata->_shardVersion; - metadata->_collVersion = oldMetadata->_collVersion; - - // TODO: This could be made more efficient if copying not required, but - // not as frequently reloaded as in mongos. - metadata->_chunksMap = oldMetadata->_chunksMap; - - LOG(2) << "loading new chunks for collection " << ns - << " using old metadata w/ version " << oldMetadata->getShardVersion() << " and " - << metadata->_chunksMap.size() << " chunks" << endl; - } else { - warning() << "reloading collection metadata for " << ns << " with new epoch " - << epoch.toString() << ", the current epoch is " - << oldMetadata->getCollVersion().epoch().toString() << endl; - } - } - - - // Exposes the new metadata's range map and version to the "differ," who - // would ultimately be responsible of filling them up. - SCMConfigDiffTracker differ(shard); - differ.attach(ns, metadata->_chunksMap, metadata->_collVersion, versionMap); - - try { - std::vector chunks; - Query diffQuery = differ.configDiffQuery(); - Status status = catalogManager->getChunks( - diffQuery.getFilter(), diffQuery.getSort(), boost::none, &chunks); - if (!status.isOK()) { - if (status == ErrorCodes::HostUnreachable) { - // Make our metadata invalid - metadata->_collVersion = ChunkVersion(0, 0, OID()); - metadata->_chunksMap.clear(); - } - return status; - } - - // - // The diff tracker should always find at least one chunk (the highest chunk we saw - // last time). If not, something has changed on the config server (potentially between - // when we read the collection data and when we read the chunks data). - // - int diffsApplied = differ.calculateConfigDiff(chunks); - if (diffsApplied > 0) { - // Chunks found, return ok - LOG(2) << "loaded " << diffsApplied << " chunks into new metadata for " << ns - << " with version " << metadata->_collVersion; - - metadata->_shardVersion = versionMap[shard]; - metadata->fillRanges(); - - invariant(metadata->isValid()); - return Status::OK(); - } else if (diffsApplied == 0) { - // No chunks found, the collection is dropping or we're confused - // If this is a full reload, assume it is a drop for backwards compatibility - // TODO: drop the config.collections entry *before* the chunks and eliminate this - // ambiguity - - string errMsg = str::stream() - << "no chunks found when reloading " << ns << ", previous version was " - << metadata->_collVersion.toString() << (fullReload ? ", this is a drop" : ""); - - warning() << errMsg << endl; - - metadata->_collVersion = ChunkVersion(0, 0, OID()); - metadata->_chunksMap.clear(); - - return fullReload ? Status(ErrorCodes::NamespaceNotFound, errMsg) - : Status(ErrorCodes::RemoteChangeDetected, errMsg); - } else { - // Invalid chunks found, our epoch may have changed because we dropped/recreated - // the collection. - string errMsg = str::stream() - << "invalid chunks found when reloading " << ns << ", previous version was " - << metadata->_collVersion.toString() << ", this should be rare"; - warning() << errMsg; - - metadata->_collVersion = ChunkVersion(0, 0, OID()); - metadata->_chunksMap.clear(); - - return Status(ErrorCodes::RemoteChangeDetected, errMsg); - } - } catch (const DBException& e) { - // We deliberately do not return connPtr to the pool, since it was involved with the - // error here. - return Status(ErrorCodes::HostUnreachable, - str::stream() << "problem querying chunks metadata" << causedBy(e)); - } -} - -Status MetadataLoader::promotePendingChunks(const CollectionMetadata* afterMetadata, - CollectionMetadata* remoteMetadata) const { - // Ensure pending chunks are applicable - bool notApplicable = (NULL == afterMetadata || NULL == remoteMetadata) || - (afterMetadata->getShardVersion() > remoteMetadata->getShardVersion()) || - (afterMetadata->getShardVersion().epoch() != remoteMetadata->getShardVersion().epoch()); - if (notApplicable) - return Status::OK(); - - // The chunks from remoteMetadata are the latest version, and the pending chunks - // from afterMetadata are the latest version. If no trickery is afoot, pending chunks - // should match exactly zero or one loaded chunk. - - remoteMetadata->_pendingMap = afterMetadata->_pendingMap; - - // Resolve our pending chunks against the chunks we've loaded - for (RangeMap::iterator it = remoteMetadata->_pendingMap.begin(); - it != remoteMetadata->_pendingMap.end();) { - if (!rangeMapOverlaps(remoteMetadata->_chunksMap, it->first, it->second)) { - ++it; - continue; - } - - // Our pending range overlaps at least one chunk - - if (rangeMapContains(remoteMetadata->_chunksMap, it->first, it->second)) { - // Chunk was promoted from pending, successful migration - LOG(2) << "verified chunk " << rangeToString(it->first, it->second) - << " was migrated earlier to this shard" << endl; - - remoteMetadata->_pendingMap.erase(it++); - } else { - // Something strange happened, maybe manual editing of config? - RangeVector overlap; - getRangeMapOverlap(remoteMetadata->_chunksMap, it->first, it->second, &overlap); - - string errMsg = str::stream() - << "the remote metadata changed unexpectedly, pending range " - << rangeToString(it->first, it->second) - << " does not exactly overlap loaded chunks " << overlapToString(overlap); - - return Status(ErrorCodes::RemoteChangeDetected, errMsg); - } - } - - return Status::OK(); -} - - -} // namespace mongo diff --git a/src/mongo/s/metadata_loader.h b/src/mongo/s/metadata_loader.h deleted file mode 100644 index 15ca227926e..00000000000 --- a/src/mongo/s/metadata_loader.h +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright (C) 2012 10gen 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 . - * - * 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 - -#include "mongo/base/status.h" -#include "mongo/client/dbclientinterface.h" -#include "mongo/db/jsobj.h" - -namespace mongo { - -class CatalogManager; -class CollectionMetadata; -class CollectionType; -class DBClientCursor; - -/** - * The MetadataLoader is responsible for interfacing with the config servers and previous - * metadata to build new instances of CollectionMetadata. MetadataLoader is the "builder" - * class for metadata. - * - * CollectionMetadata has both persisted and volatile state (for now) - the persisted - * config server chunk state and the volatile pending state which is only tracked locally - * while a server is the primary. This requires a two-step loading process - the persisted - * chunk state *cannot* be loaded in a DBLock lock while the pending chunk state *must* be. - * - * Example usage: - * beforeMetadata = ; - * remoteMetadata = makeCollectionMetadata( beforeMetadata, remoteMetadata ); - * DBLock lock(txn, dbname, MODE_X); - * afterMetadata = ; - * promotePendingChunks( afterMetadata, remoteMetadata ); - * - * The loader will go out of its way to try to fetch the smaller amount possible of data - * from the config server without sacrificing the freshness and accuracy of the metadata it - * builds. (See ConfigDiffTracker class.) - * - * The class is not thread safe. - */ -class MetadataLoader { -public: - explicit MetadataLoader(); - - ~MetadataLoader(); - - /** - * Fills a new metadata instance representing the chunkset of the collection 'ns' - * (or its entirety, if not sharded) that lives on 'shard' with data from the config server. - * Optionally, uses an 'oldMetadata' for the same 'ns'/'shard'; the contents of - * 'oldMetadata' can help reducing the amount of data read from the config servers. - * - * Locking note: - * + Must not be called in a DBLock, since this loads over the network - * - * OK on success. - * - * Failure return values: - * Abnormal: - * @return FailedToParse if there was an error parsing the remote config data - * Normal: - * @return NamespaceNotFound if the collection no longer exists - * @return HostUnreachable if there was an error contacting the config servers - * @return RemoteChangeDetected if the data loaded was modified by another operation - */ - Status makeCollectionMetadata(CatalogManager* catalogManager, - const std::string& ns, - const std::string& shard, - const CollectionMetadata* oldMetadata, - CollectionMetadata* metadata) const; - - /** - * Replaces the pending chunks of the remote metadata with the more up-to-date pending - * chunks of the 'after' metadata (metadata from after the remote load), and removes pending - * chunks which are now regular chunks. - * - * Pending chunks should always correspond to one or zero chunks in the remoteMetadata - * if the epochs are the same and the remote version is the same or higher, otherwise they - * are not applicable. - * - * Locking note: - * + Must be called in a DBLock, to ensure validity of afterMetadata - * - * Returns OK if pending chunks correctly follow the rule above or are not applicable - * Returns RemoteChangeDetected if pending chunks do not follow the rule above, indicating - * either the config server or us has changed unexpectedly. - * This should only occur with manual editing of the config - * server. - * - * TODO: This is a bit ugly but necessary for now. If/when pending chunk info is stored on - * the config server, this should go away. - */ - Status promotePendingChunks(const CollectionMetadata* afterMetadata, - CollectionMetadata* remoteMetadata) const; - -private: - /** - * Returns OK and fills in the internal state of 'metadata' with general collection - * information, not including chunks. - * - * If information about the collection can be accessed or is invalid, returns: - * @return NamespaceNotFound if the collection no longer exists - * @return FailedToParse if there was an error parsing the remote config data - * @return HostUnreachable if there was an error contacting the config servers - * @return RemoteChangeDetected if the collection doc loaded is unexpectedly different - * - */ - Status _initCollection(CatalogManager* catalogManager, - const std::string& ns, - const std::string& shard, - CollectionMetadata* metadata) const; - - /** - * Returns OK and fills in the chunk state of 'metadata' to portray the chunks of the - * collection 'ns' that sit in 'shard'. If provided, uses the contents of 'oldMetadata' - * as a base (see description in initCollection above). - * - * If information about the chunks can be accessed or is invalid, returns: - * @return HostUnreachable if there was an error contacting the config servers - * @return RemoteChangeDetected if the chunks loaded are unexpectedly different - * - * For backwards compatibility, - * @return NamespaceNotFound if there are no chunks loaded and an epoch change is detected - * TODO: @return FailedToParse - */ - Status initChunks(CatalogManager* catalogManager, - const std::string& ns, - const std::string& shard, - const CollectionMetadata* oldMetadata, - CollectionMetadata* metadata) const; -}; - -} // namespace mongo diff --git a/src/mongo/s/metadata_loader_test.cpp b/src/mongo/s/metadata_loader_test.cpp deleted file mode 100644 index d38a29d2cf5..00000000000 --- a/src/mongo/s/metadata_loader_test.cpp +++ /dev/null @@ -1,924 +0,0 @@ -/** - * Copyright (C) 2012 10gen 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 . - * - * 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 - -#include "mongo/base/status.h" -#include "mongo/base/owned_pointer_vector.h" -#include "mongo/client/connpool.h" -#include "mongo/client/dbclientinterface.h" -#include "mongo/db/jsobj.h" -#include "mongo/dbtests/mock/mock_conn_registry.h" -#include "mongo/dbtests/mock/mock_remote_db_server.h" -#include "mongo/s/catalog/legacy/catalog_manager_legacy.h" -#include "mongo/s/catalog/type_chunk.h" -#include "mongo/s/catalog/type_collection.h" -#include "mongo/s/collection_metadata.h" -#include "mongo/s/metadata_loader.h" -#include "mongo/unittest/unittest.h" -#include "mongo/util/net/hostandport.h" - -namespace { - -using namespace mongo; - -using std::unique_ptr; -using std::string; -using std::vector; - -const std::string CONFIG_HOST_PORT = "$dummy_config:27017"; - -// TODO: Test config server down -// TODO: Test read of chunks with new epoch -// TODO: Test that you can properly load config using format with deprecated fields? - -class MetadataLoaderFixture : public mongo::unittest::Test { -public: - void setUp() { - ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT)); - ASSERT(configLoc.isValid()); - ASSERT_OK(_catalogManager.init(configLoc)); - } - -protected: - CatalogManager* catalogManager() { - return &_catalogManager; - } - -private: - CatalogManagerLegacy _catalogManager; -}; - - -TEST_F(MetadataLoaderFixture, DroppedColl) { - MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(&dummyConfig); - - CollectionType collInfo; - collInfo.setNs(NamespaceString{"test.foo"}); - collInfo.setKeyPattern(BSON("a" << 1)); - collInfo.setUpdatedAt(Date_t()); - collInfo.setEpoch(OID()); - collInfo.setDropped(true); - ASSERT_OK(collInfo.validate()); - - dummyConfig.insert(CollectionType::ConfigNS, collInfo.toBSON()); - - MetadataLoader loader; - - string errmsg; - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - - ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound); - - MockConnRegistry::get()->clear(); - ScopedDbConnection::clearPool(); -} - -TEST_F(MetadataLoaderFixture, EmptyColl) { - MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(&dummyConfig); - - MetadataLoader loader; - - string errmsg; - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - - ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound); - - MockConnRegistry::get()->clear(); - ScopedDbConnection::clearPool(); -} - -TEST_F(MetadataLoaderFixture, BadColl) { - MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(&dummyConfig); - - dummyConfig.insert(CollectionType::ConfigNS, BSON(CollectionType::fullNs("test.foo"))); - - MetadataLoader loader; - - string errmsg; - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - - ASSERT_EQUALS(status.code(), ErrorCodes::NoSuchKey); - - MockConnRegistry::get()->clear(); - ScopedDbConnection::clearPool(); -} - -TEST_F(MetadataLoaderFixture, BadChunk) { - MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(&dummyConfig); - - CollectionType collInfo; - collInfo.setNs(NamespaceString{"test.foo"}); - collInfo.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - collInfo.setKeyPattern(BSON("a" << 1)); - collInfo.setEpoch(OID::gen()); - ASSERT_OK(collInfo.validate()); - - dummyConfig.insert(CollectionType::ConfigNS, collInfo.toBSON()); - - ChunkType chunkInfo; - chunkInfo.setNS(NamespaceString{"test.foo"}.ns()); - chunkInfo.setVersion(ChunkVersion(1, 0, collInfo.getEpoch())); - ASSERT(!chunkInfo.validate().isOK()); - - dummyConfig.insert(ChunkType::ConfigNS, chunkInfo.toBSON()); - - MetadataLoader loader; - - string errmsg; - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - - ASSERT_EQUALS(status.code(), ErrorCodes::FailedToParse); - - MockConnRegistry::get()->clear(); - ScopedDbConnection::clearPool(); -} - -class NoChunkFixture : public MetadataLoaderFixture { -protected: - void setUp() { - MetadataLoaderFixture::setUp(); - - _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(_dummyConfig.get()); - - OID epoch = OID::gen(); - - CollectionType collType; - collType.setNs(NamespaceString{"test.foo"}); - collType.setKeyPattern(BSON("a" << 1)); - collType.setUnique(false); - collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - collType.setEpoch(epoch); - _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); - } - - void tearDown() { - MockConnRegistry::get()->clear(); - ScopedDbConnection::clearPool(); - } - -private: - unique_ptr _dummyConfig; -}; - -TEST_F(NoChunkFixture, NoChunksIsDropped) { - MetadataLoader loader; - - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - - // This is interpreted as a dropped ns, since we drop the chunks first - ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound); -} - -class NoChunkHereFixture : public MetadataLoaderFixture { -protected: - void setUp() { - MetadataLoaderFixture::setUp(); - - _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(_dummyConfig.get()); - - OID epoch = OID::gen(); - - CollectionType collType; - collType.setNs(NamespaceString{"test.foo"}); - collType.setKeyPattern(BSON("a" << 1)); - collType.setUnique(false); - collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - collType.setEpoch(epoch); - ASSERT_OK(collType.validate()); - - _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); - - // Need a chunk on another shard, otherwise the chunks are invalid in general and we - // can't load metadata - ChunkType chunkType; - chunkType.setNS("test.foo"); - chunkType.setShard("shard0001"); - chunkType.setMin(BSON("a" << MINKEY)); - chunkType.setMax(BSON("a" << MAXKEY)); - chunkType.setVersion(ChunkVersion(1, 0, epoch)); - chunkType.setName(OID::gen().toString()); - ASSERT(chunkType.validate().isOK()); - - _dummyConfig->insert(ChunkType::ConfigNS, chunkType.toBSON()); - } - - MockRemoteDBServer* getDummyConfig() { - return _dummyConfig.get(); - } - - void tearDown() { - MockConnRegistry::get()->clear(); - ScopedDbConnection::clearPool(); - } - -private: - unique_ptr _dummyConfig; -}; - -TEST_F(NoChunkHereFixture, CheckNumChunk) { - MetadataLoader loader; - - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - - ASSERT(status.isOK()); - ASSERT_EQUALS(0U, metadata.getNumChunks()); - ASSERT_EQUALS(1, metadata.getCollVersion().majorVersion()); - ASSERT_EQUALS(0, metadata.getShardVersion().majorVersion()); - ASSERT_NOT_EQUALS(OID(), metadata.getCollVersion().epoch()); - ASSERT_NOT_EQUALS(OID(), metadata.getShardVersion().epoch()); -} - -class ConfigServerFixture : public MetadataLoaderFixture { -protected: - void setUp() { - MetadataLoaderFixture::setUp(); - - _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(_dummyConfig.get()); - - OID epoch = OID::gen(); - _maxCollVersion = ChunkVersion(1, 0, epoch); - - CollectionType collType; - collType.setNs(NamespaceString{"test.foo"}); - collType.setKeyPattern(BSON("a" << 1)); - collType.setUnique(false); - collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - collType.setEpoch(epoch); - _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON()); - - BSONObj fooSingle = BSON( - ChunkType::name("test.foo-a_MinKey") - << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << MINKEY)) - << ChunkType::max(BSON("a" << MAXKEY)) - << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(_maxCollVersion.toLong())) - << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000")); - _dummyConfig->insert(ChunkType::ConfigNS, fooSingle); - } - - void tearDown() { - MockConnRegistry::get()->clear(); - } - - ChunkVersion getMaxCollVersion() const { - return _maxCollVersion; - } - - ChunkVersion getMaxShardVersion() const { - return _maxCollVersion; - } - - MockRemoteDBServer* getConfigServer() const { - return _dummyConfig.get(); - } - -private: - unique_ptr _dummyConfig; - ChunkVersion _maxCollVersion; -}; - -TEST_F(ConfigServerFixture, SingleChunkCheckNumChunk) { - MetadataLoader loader; - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - ASSERT(status.isOK()); - ASSERT_EQUALS(1U, metadata.getNumChunks()); -} - -TEST_F(ConfigServerFixture, SingleChunkGetNext) { - MetadataLoader loader; - CollectionMetadata metadata; - loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - ChunkType chunkInfo; - ASSERT_TRUE(metadata.getNextChunk(metadata.getMinKey(), &chunkInfo)); -} - -TEST_F(ConfigServerFixture, SingleChunkGetShardKey) { - MetadataLoader loader; - CollectionMetadata metadata; - loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - ASSERT_TRUE(metadata.getKeyPattern().equal(BSON("a" << 1))); -} - -TEST_F(ConfigServerFixture, SingleChunkGetMaxCollVersion) { - MetadataLoader loader; - CollectionMetadata metadata; - loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - - ASSERT_TRUE(getMaxCollVersion().equals(metadata.getCollVersion())); -} - -TEST_F(ConfigServerFixture, SingleChunkGetMaxShardVersion) { - MetadataLoader loader; - CollectionMetadata metadata; - loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - - ASSERT_TRUE(getMaxShardVersion().equals(metadata.getShardVersion())); -} - -TEST_F(ConfigServerFixture, NoChunks) { - getConfigServer()->remove(ChunkType::ConfigNS, BSONObj()); - - MetadataLoader loader; - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata(catalogManager(), - "test.foo", - "shard0000", - NULL, /* no old metadata */ - &metadata); - - // NSNotFound because we're reloading with no old metadata - ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound); -} - -class MultipleMetadataFixture : public MetadataLoaderFixture { -protected: - void setUp() { - MetadataLoaderFixture::setUp(); - - _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT)); - mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook()); - MockConnRegistry::get()->addServer(_dummyConfig.get()); - - ConnectionString confServerStr((HostAndPort(CONFIG_HOST_PORT))); - ConnectionString configLoc(confServerStr); - _loader.reset(new MetadataLoader); - } - - MetadataLoader& loader() { - return *_loader; - } - - void getMetadataFor(const OwnedPointerVector& chunks, CollectionMetadata* metadata) { - // Infer namespace, shard, epoch, keypattern from first chunk - const ChunkType* firstChunk = *(chunks.vector().begin()); - - const string ns = firstChunk->getNS(); - const string shardName = firstChunk->getShard(); - const OID epoch = firstChunk->getVersion().epoch(); - - BSONObjBuilder keyPatternB; - BSONObjIterator keyPatternIt(firstChunk->getMin()); - while (keyPatternIt.more()) - keyPatternB.append(keyPatternIt.next().fieldName(), 1); - BSONObj keyPattern = keyPatternB.obj(); - - _dummyConfig->remove(CollectionType::ConfigNS, BSONObj()); - _dummyConfig->remove(ChunkType::ConfigNS, BSONObj()); - - CollectionType coll; - coll.setNs(NamespaceString{ns}); - coll.setKeyPattern(BSON("a" << 1)); - coll.setUpdatedAt(Date_t::fromMillisSinceEpoch(1)); - coll.setEpoch(epoch); - ASSERT_OK(coll.validate()); - - _dummyConfig->insert(CollectionType::ConfigNS, coll.toBSON()); - - ChunkVersion version(1, 0, epoch); - for (const auto chunkVal : chunks.vector()) { - ChunkType chunk(*chunkVal); - - chunk.setName(OID::gen().toString()); - if (!chunk.isVersionSet()) { - chunk.setVersion(version); - version.incMajor(); - } - - ASSERT(chunk.validate().isOK()); - - _dummyConfig->insert(ChunkType::ConfigNS, chunk.toBSON()); - } - - Status status = - loader().makeCollectionMetadata(catalogManager(), ns, shardName, NULL, metadata); - ASSERT(status.isOK()); - } - - void tearDown() { - MockConnRegistry::get()->clear(); - } - -private: - unique_ptr _dummyConfig; - unique_ptr _loader; -}; - -TEST_F(MultipleMetadataFixture, PromotePendingNA) { - unique_ptr chunk(new ChunkType()); - chunk->setNS("foo.bar"); - chunk->setShard("shard0000"); - chunk->setMin(BSON("x" << MINKEY)); - chunk->setMax(BSON("x" << 0)); - chunk->setVersion(ChunkVersion(1, 0, OID::gen())); - - OwnedPointerVector chunks; - chunks.mutableVector().push_back(chunk.release()); - - CollectionMetadata afterMetadata; - getMetadataFor(chunks, &afterMetadata); - - // Metadata of different epoch - (*chunks.vector().begin())->setVersion(ChunkVersion(1, 0, OID::gen())); - - CollectionMetadata remoteMetadata; - getMetadataFor(chunks, &remoteMetadata); - - Status status = loader().promotePendingChunks(&afterMetadata, &remoteMetadata); - ASSERT(status.isOK()); - - string errMsg; - ChunkType pending; - pending.setMin(BSON("x" << 0)); - pending.setMax(BSON("x" << 10)); - - unique_ptr cloned(afterMetadata.clonePlusPending(pending, &errMsg)); - ASSERT(cloned != NULL); - - status = loader().promotePendingChunks(cloned.get(), &remoteMetadata); - ASSERT(status.isOK()); - ASSERT_EQUALS(remoteMetadata.getNumPending(), 0u); -} - -TEST_F(MultipleMetadataFixture, PromotePendingNAVersion) { - OID epoch = OID::gen(); - - unique_ptr chunk(new ChunkType()); - chunk->setNS("foo.bar"); - chunk->setShard("shard0000"); - chunk->setMin(BSON("x" << MINKEY)); - chunk->setMax(BSON("x" << 0)); - chunk->setVersion(ChunkVersion(1, 1, epoch)); - - OwnedPointerVector chunks; - chunks.mutableVector().push_back(chunk.release()); - - CollectionMetadata afterMetadata; - getMetadataFor(chunks, &afterMetadata); - - // Metadata of same epoch, but lower version - (*chunks.vector().begin())->setVersion(ChunkVersion(1, 0, epoch)); - - CollectionMetadata remoteMetadata; - getMetadataFor(chunks, &remoteMetadata); - - Status status = loader().promotePendingChunks(&afterMetadata, &remoteMetadata); - ASSERT(status.isOK()); - - string errMsg; - ChunkType pending; - pending.setMin(BSON("x" << 0)); - pending.setMax(BSON("x" << 10)); - - unique_ptr cloned(afterMetadata.clonePlusPending(pending, &errMsg)); - ASSERT(cloned != NULL); - - status = loader().promotePendingChunks(cloned.get(), &remoteMetadata); - ASSERT(status.isOK()); - ASSERT_EQUALS(remoteMetadata.getNumPending(), 0u); -} - -TEST_F(MultipleMetadataFixture, PromotePendingGoodOverlap) { - OID epoch = OID::gen(); - - // - // Setup chunk range for remote metadata - // - - OwnedPointerVector chunks; - - unique_ptr chunk(new ChunkType()); - chunk->setNS("foo.bar"); - chunk->setShard("shard0000"); - chunk->setMin(BSON("x" << MINKEY)); - chunk->setMax(BSON("x" << 0)); - chunk->setVersion(ChunkVersion(1, 0, epoch)); - chunks.mutableVector().push_back(chunk.release()); - - chunk.reset(new ChunkType()); - chunk->setNS("foo.bar"); - chunk->setShard("shard0000"); - chunk->setMin(BSON("x" << 10)); - chunk->setMax(BSON("x" << 20)); - chunks.mutableVector().push_back(chunk.release()); - - chunk.reset(new ChunkType()); - chunk->setNS("foo.bar"); - chunk->setShard("shard0000"); - chunk->setMin(BSON("x" << 30)); - chunk->setMax(BSON("x" << MAXKEY)); - chunks.mutableVector().push_back(chunk.release()); - - CollectionMetadata remoteMetadata; - getMetadataFor(chunks, &remoteMetadata); - - // - // Setup chunk and pending range for afterMetadata - // - - chunks.clear(); - - chunk.reset(new ChunkType()); - chunk->setNS("foo.bar"); - chunk->setShard("shard0000"); - chunk->setMin(BSON("x" << 0)); - chunk->setMax(BSON("x" << 10)); - chunk->setVersion(ChunkVersion(1, 0, epoch)); - - chunks.mutableVector().push_back(chunk.release()); - - CollectionMetadata afterMetadata; - getMetadataFor(chunks, &afterMetadata); - - string errMsg; - ChunkType pending; - pending.setMin(BSON("x" << MINKEY)); - pending.setMax(BSON("x" << 0)); - - unique_ptr cloned(afterMetadata.clonePlusPending(pending, &errMsg)); - ASSERT(cloned != NULL); - - pending.setMin(BSON("x" << 10)); - pending.setMax(BSON("x" << 20)); - - cloned.reset(cloned->clonePlusPending(pending, &errMsg)); - ASSERT(cloned != NULL); - - pending.setMin(BSON("x" << 20)); - pending.setMax(BSON("x" << 30)); - - cloned.reset(cloned->clonePlusPending(pending, &errMsg)); - ASSERT(cloned != NULL); - - pending.setMin(BSON("x" << 30)); - pending.setMax(BSON("x" << MAXKEY)); - - cloned.reset(cloned->clonePlusPending(pending, &errMsg)); - ASSERT(cloned != NULL); - - Status status = loader().promotePendingChunks(cloned.get(), &remoteMetadata); - ASSERT(status.isOK()); - - ASSERT_EQUALS(remoteMetadata.getNumPending(), 1u); - ASSERT(remoteMetadata.keyIsPending(BSON("x" << 25))); -} - -TEST_F(MultipleMetadataFixture, PromotePendingBadOverlap) { - OID epoch = OID::gen(); - - // - // Setup chunk range for remote metadata - // - - OwnedPointerVector chunks; - - unique_ptr chunk(new ChunkType()); - chunk->setNS("foo.bar"); - chunk->setShard("shard0000"); - chunk->setMin(BSON("x" << MINKEY)); - chunk->setMax(BSON("x" << 0)); - chunk->setVersion(ChunkVersion(1, 0, epoch)); - - chunks.mutableVector().push_back(chunk.release()); - - CollectionMetadata remoteMetadata; - getMetadataFor(chunks, &remoteMetadata); - - // - // Setup chunk and pending range for afterMetadata - // - - chunks.clear(); - - chunk.reset(new ChunkType()); - chunk->setNS("foo.bar"); - chunk->setShard("shard0000"); - chunk->setMin(BSON("x" << 15)); - chunk->setMax(BSON("x" << MAXKEY)); - chunk->setVersion(ChunkVersion(1, 0, epoch)); - - chunks.mutableVector().push_back(chunk.release()); - - CollectionMetadata afterMetadata; - getMetadataFor(chunks, &afterMetadata); - - string errMsg; - ChunkType pending; - pending.setMin(BSON("x" << MINKEY)); - pending.setMax(BSON("x" << 1)); - - unique_ptr cloned(afterMetadata.clonePlusPending(pending, &errMsg)); - ASSERT(cloned != NULL); - - cloned.reset(cloned->clonePlusPending(pending, &errMsg)); - ASSERT(cloned != NULL); - - Status status = loader().promotePendingChunks(cloned.get(), &remoteMetadata); - ASSERT_EQUALS(status.code(), ErrorCodes::RemoteChangeDetected); -} - -#if 0 - // TODO: MockServer functionality does not support selective query - consider - // inserting nothing at all to chunk/collections collection - TEST_F(ConfigServerFixture, EmptyDataForNS) { - MetadataLoader loader; - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata( "not.sharded", - "shard0000", - NULL, /* no old metadata */ - &metadata ); - ASSERT( !status.isOK() ); - } -#endif - -#if 0 - // TODO: d_chunk_manager_test has no tests for passing old ShardChunkManager - class TwoChunkFixture : public mongo::unittest::Test { - protected: - void setUp() { - _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) ); - mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() ); - MockConnRegistry::get()->addServer( _dummyConfig.get() ); - - OID epoch = OID::gen(); - _maxCollVersion = ChunkVersion( 1, 0, epoch ); - - BSONObj collFoo = BSON(CollectionType::ns("test.foo") << - CollectionType::keyPattern(BSON("a" << 1)) << - CollectionType::unique(false) << - CollectionType::updatedAt(1ULL) << - CollectionType::epoch(epoch)); - _dummyConfig->insert( CollectionType::ConfigNS, collFoo ); - - BSONObj fooSingle = BSON(ChunkType::name("test.foo-a_MinKey") << - ChunkType::ns("test.foo") << - ChunkType::min(BSON("a" << MINKEY)) << - ChunkType::max(BSON("a" << MAXKEY)) << - ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << - ChunkType::DEPRECATED_epoch(epoch) << - ChunkType::shard("shard0000")); - _dummyConfig->insert( ChunkType::ConfigNS, fooSingle ); - - MetadataLoader loader; - Status status = loader.makeCollectionMetadata( "not.sharded", - "shard0000", - NULL, /* no old metadata */ - &_oldMetadata ); - ASSERT( status.isOK() ); - - // Needs to delete the collection and rebuild because the mock server - // not support updates. - _dummyConfig->remove( CollectionType::ConfigNS, BSONObj() ); - _dummyConfig->remove( ChunkType::ConfigNS, BSONObj() ); - - OID epoch2 = OID::gen(); - _maxCollVersion = ChunkVersion( 2, 0, epoch2 ); - - BSONObj collFoo = BSON(CollectionType::ns("test.foo") << - CollectionType::keyPattern(BSON("a" << 1)) << - CollectionType::unique(false) << - CollectionType::updatedAt(2ULL) << - CollectionType::epoch(epoch2)); - _dummyConfig->insert( CollectionType::ConfigNS, collFoo ); - - BSONObj chunk1 = BSON(ChunkType::name("test.foo-a_MinKey") << - ChunkType::ns("test.foo") << - ChunkType::min(BSON("a" << MINKEY)) << - ChunkType::max(BSON("a" << 100)) << - ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << - ChunkType::DEPRECATED_epoch(epoch2) << - ChunkType::shard("shard0000")); - _dummyConfig->insert( ChunkType::ConfigNS, chunk1 ); - - BSONObj chunk2 = BSON(ChunkType::name("test.foo-a_100") << - ChunkType::ns("test.foo") << - ChunkType::min(BSON("a" << 100)) << - ChunkType::max(BSON("a" << MAXKEY)) << - ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << - ChunkType::DEPRECATED_epoch(epoch2) << - ChunkType::shard("shard0000")); - _dummyConfig->insert( ChunkType::ConfigNS, chunk2 ); - } - - void tearDown() { - MockConnRegistry::get()->removeServer( _dummyConfig->getServerAddress() ); - } - - ChunkVersion getCollVersion() const { - return _maxCollVersion; - } - - const ChunkVersion& getShardVersion( size_t shard ) const { - return _maxCollVersion; - } - - const CollectionMetadata* getOldMetadata() const { - return _oldMetadata; - } - - private: - unique_ptr _dummyConfig; - CollectionMetadata _oldMetadata; - - ChunkVersion _maxCollVersion; - }; -#endif - -#if 0 - - // TODO: MockServer functionality does not support selective query - class ThreeChunkTwoShardFixture : public mongo::unittest::Test { - protected: - void setUp() { - _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) ); - mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() ); - MockConnRegistry::get()->addServer( _dummyConfig.get() ); - - OID epoch = OID::gen(); - _maxCollVersion = ChunkVersion( 1, 0, epoch ); - - BSONObj collFoo = BSON(CollectionType::ns("test.foo") << - CollectionType::keyPattern(BSON("a" << 1)) << - CollectionType::unique(false) << - CollectionType::updatedAt(1ULL) << - CollectionType::epoch(epoch)); - _dummyConfig->insert( CollectionType::ConfigNS, collFoo ); - - BSONObj fooSingle = BSON(ChunkType::name("test.foo-a_MinKey") << - ChunkType::ns("test.foo") << - ChunkType::min(BSON("a" << MINKEY)) << - ChunkType::max(BSON("a" << MAXKEY)) << - ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << - ChunkType::DEPRECATED_epoch(epoch) << - ChunkType::shard("shard0000")); - _dummyConfig->insert( ChunkType::ConfigNS, fooSingle ); - - MetadataLoader loader; - CollectionMetadata metadata; - Status status = loader.makeCollectionMetadata( "not.sharded", - "shard0000", - NULL, /* no old metadata */ - &metadata ); - ASSERT( status.isOK() ); - - // Needs to delete the collection and rebuild because the mock server - // not support updates. - _dummyConfig->remove( CollectionType::ConfigNS, BSONObj() ); - _dummyConfig->remove( ChunkType::ConfigNS, BSONObj() ); - - OID epoch2 = OID::gen(); - _maxCollVersion = ChunkVersion( 2, 0, epoch2 ); - _maxShardVersion.push_back( _maxCollVersion ); - - BSONObj collFoo = BSON(CollectionType::ns("test.foo") << - CollectionType::keyPattern(BSON("a" << 1)) << - CollectionType::unique(false) << - CollectionType::updatedAt(2ULL) << - CollectionType::epoch(epoch2)); - _dummyConfig->insert( CollectionType::ConfigNS, collFoo ); - - BSONObj chunk1 = BSON(ChunkType::name("test.foo-a_MinKey") << - ChunkType::ns("test.foo") << - ChunkType::min(BSON("a" << MINKEY)) << - ChunkType::max(BSON("a" << 10)) << - ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << - ChunkType::DEPRECATED_epoch(epoch2) << - ChunkType::shard("shard0000")); - _dummyConfig->insert( ChunkType::ConfigNS, chunk1 ); - - OID epoch3 = OID::gen(); - _maxCollVersion = ChunkVersion( 2, 0, epoch3 ); - _maxShardVersion.push_back( _maxCollVersion ); - - BSONObj chunk2 = BSON(ChunkType::name("test.foo-a_10") << - ChunkType::ns("test.foo") << - ChunkType::min(BSON("a" << 10)) << - ChunkType::max(BSON("a" << 100)) << - ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << - ChunkType::DEPRECATED_epoch(epoch3) << - ChunkType::shard("shard0001")); - _dummyConfig->insert( ChunkType::ConfigNS, chunk2 ); - - BSONObj chunk3 = BSON(ChunkType::name("test.foo-a_100") << - ChunkType::ns("test.foo") << - ChunkType::min(BSON("a" << 100)) << - ChunkType::max(BSON("a" << MAXKEY)) << - ChunkType::DEPRECATED_lastmod(_maxCollVersion.toLong()) << - ChunkType::DEPRECATED_epoch(epoch3) << - ChunkType::shard("shard0001")); - _dummyConfig->insert( ChunkType::ConfigNS, chunk3 ); - } - - void tearDown() { - MockConnRegistry::get()->removeServer( _dummyConfig->getServerAddress() ); - } - - ChunkVersion getCollVersion() const { - return _maxCollVersion; - } - - const ChunkVersion& getShardVersion( size_t shard ) const { - return _maxShardVersion[shard]; - } - - private: - unique_ptr _dummyConfig; - CollectionMetadata _oldMetadata; - - ChunkVersion _maxCollVersion; - vector _maxShardVersion; - }; -#endif -} -// unnamed namespace -- cgit v1.2.1