/** * 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 "mongo/platform/basic.h" #include "mongo/client/connection_string.h" #include "mongo/db/logical_time.h" #include "mongo/db/query/cursor_response.h" #include "mongo/db/vector_clock.h" #include "mongo/s/catalog/type_shard.h" #include "mongo/s/client/shard_factory.h" #include "mongo/s/client/shard_registry.h" #include "mongo/s/client/shard_remote.h" #include "mongo/s/query/establish_cursors.h" #include "mongo/s/shard_id.h" #include "mongo/s/sharding_router_test_fixture.h" namespace mongo { namespace { const HostAndPort kTestConfigShardHost = HostAndPort("FakeConfigHost", 12345); const std::vector kTestShardIds = { ShardId("FakeShard1"), ShardId("FakeShard2"), ShardId("FakeShard3")}; const std::vector kTestShardHosts = {HostAndPort("FakeShard1Host", 12345), HostAndPort("FakeShard2Host", 12345), HostAndPort("FakeShard3Host", 12345)}; class ShardRemoteTest : public ShardingTestFixture { protected: void setUp() { ShardingTestFixture::setUp(); configTargeter()->setFindHostReturnValue(kTestConfigShardHost); std::vector shards; for (size_t i = 0; i < kTestShardIds.size(); i++) { ShardType shardType; shardType.setName(kTestShardIds[i].toString()); shardType.setHost(kTestShardHosts[i].toString()); shards.push_back(shardType); std::unique_ptr targeter( std::make_unique()); targeter->setConnectionStringReturnValue(ConnectionString(kTestShardHosts[i])); targeter->setFindHostReturnValue(kTestShardHosts[i]); targeterFactory()->addTargeterToReturn(ConnectionString(kTestShardHosts[i]), std::move(targeter)); } setupShards(shards); } void runDummyCommandOnShard(ShardId shardId) { auto shard = shardRegistry()->getShardNoReload(shardId); uassertStatusOK(shard->runCommand(operationContext(), ReadPreferenceSetting{ReadPreference::PrimaryOnly}, "unusedDb", BSON("unused" << "cmd"), Shard::RetryPolicy::kNoRetry)); } }; BSONObj makeLastCommittedOpTimeMetadata(LogicalTime time) { return BSON("lastCommittedOpTime" << time.asTimestamp()); } TEST_F(ShardRemoteTest, GetAndSetLatestLastCommittedOpTime) { auto shard = shardRegistry()->getShardNoReload(kTestShardIds[0]); // Shards can store last committed opTimes. LogicalTime time(Timestamp(10, 2)); shard->updateLastCommittedOpTime(time); ASSERT_EQ(time, shard->getLastCommittedOpTime()); // Later times overwrite earlier times. LogicalTime laterTime(Timestamp(20, 2)); shard->updateLastCommittedOpTime(laterTime); ASSERT_EQ(laterTime, shard->getLastCommittedOpTime()); // Earlier times are ignored. LogicalTime earlierTime(Timestamp(5, 1)); shard->updateLastCommittedOpTime(earlierTime); ASSERT_EQ(laterTime, shard->getLastCommittedOpTime()); } TEST_F(ShardRemoteTest, NetworkReplyWithLastCommittedOpTime) { // Send a request to one shard. auto targetedShard = kTestShardIds[0]; auto future = launchAsync([&] { runDummyCommandOnShard(targetedShard); }); // Mock a find response with a returned lastCommittedOpTime. LogicalTime expectedTime(Timestamp(100, 2)); onFindWithMetadataCommand([&](const executor::RemoteCommandRequest& request) { auto result = std::vector{BSON("_id" << 1)}; auto metadata = makeLastCommittedOpTimeMetadata(expectedTime); return std::make_tuple(result, metadata); }); future.default_timed_get(); // Verify the targeted shard has updated its lastCommittedOpTime. ASSERT_EQ(expectedTime, shardRegistry()->getShardNoReload(targetedShard)->getLastCommittedOpTime()); // Verify shards that were not targeted were not affected. for (auto shardId : kTestShardIds) { if (shardId != targetedShard) { ASSERT(!VectorClock::isValidComponentTime( shardRegistry()->getShardNoReload(shardId)->getLastCommittedOpTime())); } } } TEST_F(ShardRemoteTest, NetworkReplyWithoutLastCommittedOpTime) { // Send a request to one shard. auto targetedShard = kTestShardIds[0]; auto future = launchAsync([&] { runDummyCommandOnShard(targetedShard); }); // Mock a find response without a returned lastCommittedOpTime. onFindWithMetadataCommand([&](const executor::RemoteCommandRequest& request) { auto result = std::vector{BSON("_id" << 1)}; auto metadata = BSONObj(); return std::make_tuple(result, metadata); }); future.default_timed_get(); // Verify the targeted shard has not updated its lastCommittedOpTime. ASSERT_EQ(LogicalTime::kUninitialized, shardRegistry()->getShardNoReload(targetedShard)->getLastCommittedOpTime()); } TEST_F(ShardRemoteTest, ScatterGatherRepliesWithLastCommittedOpTime) { // Send requests to several shards. auto nss = NamespaceString("test.foo"); auto cmdObj = BSON("find" << nss.coll()); std::vector> remotes{ {kTestShardIds[0], cmdObj}, {kTestShardIds[1], cmdObj}, {kTestShardIds[2], cmdObj}}; auto future = launchAsync([&] { establishCursors(operationContext(), executor(), nss, ReadPreferenceSetting{ReadPreference::PrimaryOnly}, remotes, false); // allowPartialResults }); // All remotes respond with a lastCommittedOpTime. LogicalTime expectedTime(Timestamp(50, 1)); for (auto remote : remotes) { onCommandWithMetadata([&](const executor::RemoteCommandRequest& request) { std::vector batch = {BSON("_id" << 1)}; CursorResponse cursorResponse(nss, CursorId(123), batch); auto result = BSONObjBuilder( cursorResponse.toBSON(CursorResponse::ResponseType::InitialResponse)); result.appendElements(makeLastCommittedOpTimeMetadata(expectedTime)); return executor::RemoteCommandResponse(result.obj(), Milliseconds(1)); }); } future.default_timed_get(); // Verify all shards updated their lastCommittedOpTime. for (auto shardId : kTestShardIds) { ASSERT_EQ(expectedTime, shardRegistry()->getShardNoReload(shardId)->getLastCommittedOpTime()); } } TEST_F(ShardRemoteTest, TargeterMarksHostAsDownWhenConfigStepdown) { auto targetedNode = ShardId("config"); ASSERT_EQ(0UL, configTargeter()->getAndClearMarkedDownHosts().size()); auto future = launchAsync([&] { runDummyCommandOnShard(targetedNode); }); auto error = Status(ErrorCodes::PrimarySteppedDown, "Config stepped down"); onCommand([&](const executor::RemoteCommandRequest& request) { return error; }); ASSERT_THROWS_CODE(future.default_timed_get(), DBException, ErrorCodes::PrimarySteppedDown); ASSERT_EQ(1UL, configTargeter()->getAndClearMarkedDownHosts().size()); } TEST_F(ShardRemoteTest, TargeterMarksHostAsDownWhenConfigShuttingDown) { auto targetedNode = ShardId("config"); ASSERT_EQ(0UL, configTargeter()->getAndClearMarkedDownHosts().size()); auto future = launchAsync([&] { runDummyCommandOnShard(targetedNode); }); auto error = Status(ErrorCodes::InterruptedAtShutdown, "Interrupted at shutdown"); onCommand([&](const executor::RemoteCommandRequest& request) { return error; }); ASSERT_THROWS_CODE(future.default_timed_get(), DBException, ErrorCodes::InterruptedAtShutdown); ASSERT_EQ(1UL, configTargeter()->getAndClearMarkedDownHosts().size()); } } // namespace } // namespace mongo