/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * 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 * Server Side Public License for more details. * * You should have received a copy of the Server Side 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 Server Side 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 #include "mongo/client/read_preference.h" #include "mongo/client/remote_command_targeter_factory_mock.h" #include "mongo/client/remote_command_targeter_mock.h" #include "mongo/db/catalog/collection_mock.h" #include "mongo/db/s/config/config_server_test_fixture.h" #include "mongo/db/s/config/initial_split_policy.h" #include "mongo/rpc/metadata/tracking_metadata.h" #include "mongo/s/balancer_configuration.h" #include "mongo/s/catalog/type_chunk.h" #include "mongo/s/client/shard_registry.h" #include "mongo/s/request_types/sharded_ddl_commands_gen.h" #include "mongo/s/shard_key_pattern.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kSharding namespace mongo { namespace { using executor::RemoteCommandRequest; using unittest::assertGet; class ShardCollectionTestBase : public ConfigServerTestFixture { protected: void expectSplitVector(const HostAndPort& shardHost, const ShardKeyPattern& keyPattern, const BSONArray& splitPoints) { onCommand([&](const RemoteCommandRequest& request) { ASSERT_EQUALS(shardHost, request.target); std::string cmdName = request.cmdObj.firstElement().fieldName(); ASSERT_EQUALS("autoSplitVector", cmdName); // autoSplitVector concatenates the collection name to the command's db const auto receivedNs = request.dbname + '.' + request.cmdObj["autoSplitVector"].String(); ASSERT_EQUALS(kNamespace.ns(), receivedNs); ASSERT_BSONOBJ_EQ(keyPattern.toBSON(), request.cmdObj["keyPattern"].Obj()); ASSERT_BSONOBJ_EQ(keyPattern.getKeyPattern().globalMin(), request.cmdObj["min"].Obj()); ASSERT_BSONOBJ_EQ(keyPattern.getKeyPattern().globalMax(), request.cmdObj["max"].Obj()); ASSERT_EQUALS(ChunkSizeSettingsType::kDefaultMaxChunkSizeBytes, static_cast(request.cmdObj["maxChunkSizeBytes"].numberLong())); ASSERT_BSONOBJ_EQ( ReadPreferenceSetting(ReadPreference::PrimaryPreferred).toContainingBSON(), rpc::TrackingMetadata::removeTrackingData(request.metadata)); return BSON("ok" << 1 << "splitKeys" << splitPoints << "continuation" << false); }); } const ShardId testPrimaryShard{"shard0"}; const NamespaceString kNamespace{"db1.foo"}; private: const HostAndPort configHost{"configHost1"}; const ConnectionString configCS{ConnectionString::forReplicaSet("configReplSet", {configHost})}; const HostAndPort clientHost{"clientHost1"}; }; // Direct tests for InitialSplitPolicy::createFirstChunks which is the base call for both the config // server and shard server's shard collection logic class CreateFirstChunksTest : public ShardCollectionTestBase { protected: const ShardKeyPattern kShardKeyPattern{BSON("x" << 1)}; }; TEST_F(CreateFirstChunksTest, Split_Disallowed_With_Both_SplitPoints_And_Zones) { CreateCollectionRequest request; std::vector splitPoints = {BSONObj()}; request.setNumInitialChunks(0); request.setPresplitHashedZones(false); request.setInitialSplitPoints(splitPoints); std::vector tags = { TagsType(kNamespace, "TestZone", ChunkRange(kShardKeyPattern.getKeyPattern().globalMin(), BSON("x" << 0)))}; ASSERT_THROWS_CODE( InitialSplitPolicy::calculateOptimizationStrategy(operationContext(), kShardKeyPattern, request.getNumInitialChunks().value(), request.getPresplitHashedZones().value(), request.getInitialSplitPoints(), tags, 2 /* numShards */, true /* collectionIsEmpty */), AssertionException, ErrorCodes::InvalidOptions); ASSERT_THROWS_CODE( InitialSplitPolicy::calculateOptimizationStrategy(operationContext(), kShardKeyPattern, request.getNumInitialChunks().value(), request.getPresplitHashedZones().value(), request.getInitialSplitPoints(), tags, 2 /* numShards */, false /* collectionIsEmpty */), AssertionException, ErrorCodes::InvalidOptions); } TEST_F(CreateFirstChunksTest, NonEmptyCollection_SplitPoints_FromSplitVector_ManyChunksToPrimary) { const std::vector kShards{ShardType("shard0", "rs0/shard0:123"), ShardType("shard1", "rs1/shard1:123"), ShardType("shard2", "rs2/shard2:123")}; const auto connStr = assertGet(ConnectionString::parse(kShards[1].getHost())); std::unique_ptr targeter( std::make_unique()); targeter->setConnectionStringReturnValue(connStr); targeter->setFindHostReturnValue(connStr.getServers()[0]); targeterFactory()->addTargeterToReturn(connStr, std::move(targeter)); setupShards(kShards); shardRegistry()->reload(operationContext()); auto uuid = UUID::gen(); CollectionCatalog::write(getServiceContext(), [&](CollectionCatalog& catalog) { catalog.registerCollection(operationContext(), uuid, std::make_shared(kNamespace), /*ts=*/boost::none); }); auto future = launchAsync([&] { ThreadClient tc("Test", getServiceContext()); auto opCtx = cc().makeOperationContext(); CreateCollectionRequest request; request.setNumInitialChunks(0); request.setPresplitHashedZones(false); auto optimization = InitialSplitPolicy::calculateOptimizationStrategy( operationContext(), kShardKeyPattern, request.getNumInitialChunks().value(), request.getPresplitHashedZones().value(), request.getInitialSplitPoints(), {}, /* tags */ 3 /* numShards */, false /* collectionIsEmpty */); ASSERT(!optimization->isOptimized()); return optimization->createFirstChunks( opCtx.get(), kShardKeyPattern, {uuid, ShardId("shard1")}); }); expectSplitVector(connStr.getServers()[0], kShardKeyPattern, BSON_ARRAY(BSON("x" << 0))); const auto& firstChunks = future.default_timed_get(); ASSERT_EQ(2U, firstChunks.chunks.size()); ASSERT_EQ(kShards[1].getName(), firstChunks.chunks[0].getShard()); ASSERT_EQ(kShards[1].getName(), firstChunks.chunks[1].getShard()); } TEST_F(CreateFirstChunksTest, NonEmptyCollection_SplitPoints_FromClient_ManyChunksToPrimary) { const std::vector kShards{ShardType("shard0", "rs0/shard0:123"), ShardType("shard1", "rs1/shard1:123"), ShardType("shard2", "rs2/shard2:123")}; const auto connStr = assertGet(ConnectionString::parse(kShards[1].getHost())); std::unique_ptr targeter( std::make_unique()); targeter->setConnectionStringReturnValue(connStr); targeter->setFindHostReturnValue(connStr.getServers()[0]); targeterFactory()->addTargeterToReturn(connStr, std::move(targeter)); setupShards(kShards); shardRegistry()->reload(operationContext()); auto future = launchAsync([&] { ThreadClient tc("Test", getServiceContext()); auto opCtx = cc().makeOperationContext(); std::vector splitPoints{BSON("x" << 0)}; std::vector zones{}; bool collectionIsEmpty = false; CreateCollectionRequest request; request.setNumInitialChunks(0); request.setPresplitHashedZones(false); request.setInitialSplitPoints(splitPoints); auto optimization = InitialSplitPolicy::calculateOptimizationStrategy( operationContext(), kShardKeyPattern, request.getNumInitialChunks().value(), request.getPresplitHashedZones().value(), request.getInitialSplitPoints(), zones, 3 /* numShards */, collectionIsEmpty); ASSERT(optimization->isOptimized()); return optimization->createFirstChunks( opCtx.get(), kShardKeyPattern, {UUID::gen(), ShardId("shard1")}); }); const auto& firstChunks = future.default_timed_get(); ASSERT_EQ(2U, firstChunks.chunks.size()); ASSERT_EQ(kShards[1].getName(), firstChunks.chunks[0].getShard()); ASSERT_EQ(kShards[1].getName(), firstChunks.chunks[1].getShard()); } TEST_F(CreateFirstChunksTest, NonEmptyCollection_WithZones_OneChunkToPrimary) { const std::vector kShards{ShardType("shard0", "rs0/shard0:123", {"TestZone"}), ShardType("shard1", "rs1/shard1:123", {"TestZone"}), ShardType("shard2", "rs2/shard2:123")}; setupShards(kShards); shardRegistry()->reload(operationContext()); std::vector zones{ TagsType(kNamespace, "TestZone", ChunkRange(kShardKeyPattern.getKeyPattern().globalMin(), BSON("x" << 0)))}; bool collectionIsEmpty = false; CreateCollectionRequest request; request.setNumInitialChunks(0); request.setPresplitHashedZones(false); auto optimization = InitialSplitPolicy::calculateOptimizationStrategy(operationContext(), kShardKeyPattern, request.getNumInitialChunks().value(), request.getPresplitHashedZones().value(), request.getInitialSplitPoints(), zones, 3 /* numShards */, collectionIsEmpty); ASSERT(optimization->isOptimized()); const auto firstChunks = optimization->createFirstChunks( operationContext(), kShardKeyPattern, {UUID::gen(), ShardId("shard1")}); ASSERT_EQ(1U, firstChunks.chunks.size()); ASSERT_EQ(kShards[1].getName(), firstChunks.chunks[0].getShard()); } TEST_F(CreateFirstChunksTest, EmptyCollection_SplitPoints_FromClient_ManyChunksDistributed) { const std::vector kShards{ShardType("shard0", "rs0/shard0:123"), ShardType("shard1", "rs1/shard1:123"), ShardType("shard2", "rs2/shard2:123")}; const auto connStr = assertGet(ConnectionString::parse(kShards[1].getHost())); std::unique_ptr targeter( std::make_unique()); targeter->setConnectionStringReturnValue(connStr); targeter->setFindHostReturnValue(connStr.getServers()[0]); targeterFactory()->addTargeterToReturn(connStr, std::move(targeter)); setupShards(kShards); shardRegistry()->reload(operationContext()); auto future = launchAsync([&] { ThreadClient tc("Test", getServiceContext()); auto opCtx = cc().makeOperationContext(); std::vector splitPoints{BSON("x" << 0), BSON("x" << 100)}; std::vector zones{}; bool collectionIsEmpty = true; CreateCollectionRequest request; request.setNumInitialChunks(0); request.setPresplitHashedZones(false); request.setInitialSplitPoints(splitPoints); auto optimization = InitialSplitPolicy::calculateOptimizationStrategy( operationContext(), kShardKeyPattern, request.getNumInitialChunks().value(), request.getPresplitHashedZones().value(), request.getInitialSplitPoints(), zones, 3 /* numShards */, collectionIsEmpty); ASSERT(optimization->isOptimized()); return optimization->createFirstChunks( operationContext(), kShardKeyPattern, {UUID::gen(), ShardId("shard1")}); }); const auto& firstChunks = future.default_timed_get(); ASSERT_EQ(3U, firstChunks.chunks.size()); ASSERT_EQ(kShards[0].getName(), firstChunks.chunks[0].getShard()); ASSERT_EQ(kShards[1].getName(), firstChunks.chunks[1].getShard()); ASSERT_EQ(kShards[2].getName(), firstChunks.chunks[2].getShard()); } TEST_F(CreateFirstChunksTest, EmptyCollection_NoSplitPoints_OneChunkToPrimary) { const std::vector kShards{ShardType("shard0", "rs0/shard0:123"), ShardType("shard1", "rs1/shard1:123"), ShardType("shard2", "rs2/shard2:123")}; const auto connStr = assertGet(ConnectionString::parse(kShards[1].getHost())); std::unique_ptr targeter( std::make_unique()); targeter->setConnectionStringReturnValue(connStr); targeter->setFindHostReturnValue(connStr.getServers()[0]); targeterFactory()->addTargeterToReturn(connStr, std::move(targeter)); setupShards(kShards); shardRegistry()->reload(operationContext()); auto future = launchAsync([&] { ThreadClient tc("Test", getServiceContext()); auto opCtx = cc().makeOperationContext(); std::vector splitPoints{}; std::vector zones{}; bool collectionIsEmpty = true; CreateCollectionRequest request; request.setNumInitialChunks(0); request.setPresplitHashedZones(false); request.setInitialSplitPoints(splitPoints); auto optimization = InitialSplitPolicy::calculateOptimizationStrategy( operationContext(), kShardKeyPattern, request.getNumInitialChunks().value(), request.getPresplitHashedZones().value(), request.getInitialSplitPoints(), zones, 3 /* numShards */, collectionIsEmpty); ASSERT(optimization->isOptimized()); return optimization->createFirstChunks( operationContext(), kShardKeyPattern, {UUID::gen(), ShardId("shard1")}); }); const auto& firstChunks = future.default_timed_get(); ASSERT_EQ(1U, firstChunks.chunks.size()); ASSERT_EQ(kShards[1].getName(), firstChunks.chunks[0].getShard()); } TEST_F(CreateFirstChunksTest, EmptyCollection_WithZones_ManyChunksOnFirstZoneShard) { const std::vector kShards{ShardType("shard0", "rs0/shard0:123", {"TestZone"}), ShardType("shard1", "rs1/shard1:123", {"TestZone"}), ShardType("shard2", "rs2/shard2:123")}; setupShards(kShards); shardRegistry()->reload(operationContext()); std::vector splitPoints{}; std::vector zones{ TagsType(kNamespace, "TestZone", ChunkRange(kShardKeyPattern.getKeyPattern().globalMin(), BSON("x" << 0)))}; bool collectionIsEmpty = true; CreateCollectionRequest request; request.setNumInitialChunks(0); request.setPresplitHashedZones(false); auto optimization = InitialSplitPolicy::calculateOptimizationStrategy(operationContext(), kShardKeyPattern, request.getNumInitialChunks().value(), request.getPresplitHashedZones().value(), request.getInitialSplitPoints(), zones, 3 /* numShards */, collectionIsEmpty); ASSERT(optimization->isOptimized()); const auto firstChunks = optimization->createFirstChunks( operationContext(), kShardKeyPattern, {UUID::gen(), ShardId("shard1")}); ASSERT_EQ(2U, firstChunks.chunks.size()); ASSERT_EQ(kShards[0].getName(), firstChunks.chunks[0].getShard()); ASSERT_EQ(kShards[0].getName(), firstChunks.chunks[1].getShard()); } } // namespace } // namespace mongo