diff --git a/src/mongo/client/sdam/server_selector_test.cpp b/src/mongo/client/sdam/server_selector_test.cpp index 8d6118ba9c6..25d19d39c54 100644 --- a/src/mongo/client/sdam/server_selector_test.cpp +++ b/src/mongo/client/sdam/server_selector_test.cpp @@ -151,6 +151,68 @@ public: ServerSelector selector = ServerSelector(sdamConfiguration); + static ServerDescriptionPtr makeReplicaSetPrimary(HostAndPort address, + std::initializer_list hosts, + std::string processType = "") { + auto builder = ServerDescriptionBuilder() + .withAddress(address) + .withType(ServerType::kRSPrimary) + .withLastUpdateTime(Date_t::now()) + .withLastWriteDate(Date_t::now()) + .withRtt(Milliseconds{1}) + .withSetName(SET_NAME) + .withMinWireVersion(WireVersion::SUPPORTS_OP_MSG) + .withMaxWireVersion(WireVersion::LATEST_WIRE_VERSION) + .withElectionId(kOidOne) + .withSetVersion(100); + for (const auto& host : hosts) { + builder.withHost(host); + } + if (!processType.empty()) { + builder.withTag(ServerDescription::kProcessTypeTagKey, processType); + } + return builder.instance(); + } + + static ServerDescriptionPtr makeReplicaSetSecondary(HostAndPort address, + std::string processType = "") { + auto builder = ServerDescriptionBuilder() + .withAddress(address) + .withType(ServerType::kRSSecondary) + .withRtt(Milliseconds{1}) + .withSetName(SET_NAME) + .withMinWireVersion(WireVersion::SUPPORTS_OP_MSG) + .withMaxWireVersion(WireVersion::LATEST_WIRE_VERSION) + .withLastUpdateTime(Date_t::now()) + .withLastWriteDate(Date_t::now()); + if (!processType.empty()) { + builder.withTag(ServerDescription::kProcessTypeTagKey, processType); + } + return builder.instance(); + } + + // Topology with a normal primary (s0), an injector-tagged secondary (s1) and a normal + // secondary (s2). + struct InjectorSecondaryTopology { + std::shared_ptr topologyDescription; + TopologyStateMachine stateMachine; + + InjectorSecondaryTopology(const SdamConfiguration& config) + : topologyDescription(std::make_shared(config)), + stateMachine(config) { + stateMachine.onServerDescription( + *topologyDescription, + makeReplicaSetPrimary(HostAndPort("s0"), + {HostAndPort("s0"), HostAndPort("s1"), HostAndPort("s2")})); + stateMachine.onServerDescription( + *topologyDescription, + makeReplicaSetSecondary(HostAndPort("s1"), ServerDescription::kInjectorTagValue)); + stateMachine.onServerDescription(*topologyDescription, + makeReplicaSetSecondary(HostAndPort("s2"))); + } + }; + + // Topology with an injector-tagged primary (s0) and a normal secondary (s1). struct InjectorPrimaryTopology { std::shared_ptr topologyDescription; TopologyStateMachine stateMachine; @@ -158,35 +220,32 @@ public: InjectorPrimaryTopology(const SdamConfiguration& config) : topologyDescription(std::make_shared(config)), stateMachine(config) { - const auto injector = ServerDescriptionBuilder() - .withAddress(HostAndPort("s0")) - .withType(ServerType::kRSPrimary) - .withLastUpdateTime(Date_t::now()) - .withLastWriteDate(Date_t::now()) - .withRtt(Milliseconds{1}) - .withSetName("set") - .withHost(HostAndPort("s0")) - .withHost(HostAndPort("s1")) - .withMinWireVersion(WireVersion::SUPPORTS_OP_MSG) - .withMaxWireVersion(WireVersion::LATEST_WIRE_VERSION) - .withTag(ServerDescription::kProcessTypeTagKey, - ServerDescription::kInjectorTagValue) - .withElectionId(kOidOne) - .withSetVersion(100) - .instance(); - stateMachine.onServerDescription(*topologyDescription, injector); + stateMachine.onServerDescription( + *topologyDescription, + makeReplicaSetPrimary(HostAndPort("s0"), + {HostAndPort("s0"), HostAndPort("s1")}, + ServerDescription::kInjectorTagValue)); + stateMachine.onServerDescription(*topologyDescription, + makeReplicaSetSecondary(HostAndPort("s1"))); + } + }; - const auto secondary = ServerDescriptionBuilder() - .withAddress(HostAndPort("s1")) - .withType(ServerType::kRSSecondary) - .withRtt(Milliseconds{1}) - .withSetName("set") - .withMinWireVersion(WireVersion::SUPPORTS_OP_MSG) - .withMaxWireVersion(WireVersion::LATEST_WIRE_VERSION) - .withLastUpdateTime(Date_t::now()) - .withLastWriteDate(Date_t::now()) - .instance(); - stateMachine.onServerDescription(*topologyDescription, secondary); + // Topology where every node is injector-tagged (s0 primary, s1 secondary). + struct AllInjectorsTopology { + std::shared_ptr topologyDescription; + TopologyStateMachine stateMachine; + + AllInjectorsTopology(const SdamConfiguration& config) + : topologyDescription(std::make_shared(config)), + stateMachine(config) { + stateMachine.onServerDescription( + *topologyDescription, + makeReplicaSetPrimary(HostAndPort("s0"), + {HostAndPort("s0"), HostAndPort("s1")}, + ServerDescription::kInjectorTagValue)); + stateMachine.onServerDescription( + *topologyDescription, + makeReplicaSetSecondary(HostAndPort("s1"), ServerDescription::kInjectorTagValue)); } }; }; @@ -1069,32 +1128,154 @@ TEST_F(ConfigOnlyServerSelectorTest, ShouldExcludeInjectorFromPrimaryPreferredSe TEST_F(ServerSelectorTestFixture, ShouldNotExcludeInjectorWhenNotConfigOnly) { InjectorPrimaryTopology topo(sdamConfiguration); - auto result = selector.selectServers( + auto primaryOnlyResult = selector.selectServers( topo.topologyDescription, ReadPreferenceSetting(ReadPreference::PrimaryOnly), {}); - ASSERT(result); - ASSERT_EQ(1, result->size()); - ASSERT_EQ(HostAndPort("s0"), (*result)[0]->getAddress()); + ASSERT(primaryOnlyResult); + ASSERT_EQ(1, primaryOnlyResult->size()); + ASSERT_EQ(HostAndPort("s0"), (*primaryOnlyResult)[0]->getAddress()); + + auto nearestResult = selector.selectServers( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::Nearest), {}); + ASSERT(nearestResult); + ASSERT_EQ(2, nearestResult->size()); + bool foundInjectorPrimary = false; + bool foundSecondary = false; + for (const auto& server : *nearestResult) { + foundInjectorPrimary |= server->getAddress() == HostAndPort("s0"); + foundSecondary |= server->getAddress() == HostAndPort("s1"); + } + ASSERT_TRUE(foundInjectorPrimary); + ASSERT_TRUE(foundSecondary); + + auto primaryPreferredResult = selector.selectServers( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::PrimaryPreferred), {}); + ASSERT(primaryPreferredResult); + ASSERT_EQ(1, primaryPreferredResult->size()); + ASSERT_EQ(HostAndPort("s0"), (*primaryPreferredResult)[0]->getAddress()); +} + +TEST_F(ConfigOnlyServerSelectorTest, ShouldExcludeInjectorSecondaryFromSecondaryOnlySelection) { + InjectorSecondaryTopology topo(sdamConfiguration); + + std::map frequencyInfo{ + {HostAndPort("s0"), 0}, {HostAndPort("s1"), 0}, {HostAndPort("s2"), 0}}; + for (int i = 0; i < NUM_ITERATIONS; i++) { + auto server = selector.selectServer( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::SecondaryOnly), {}); + if (server) { + frequencyInfo[(*server)->getAddress()]++; + } + } + + ASSERT_FALSE(frequencyInfo[HostAndPort("s0")]); + ASSERT_FALSE(frequencyInfo[HostAndPort("s1")]); + ASSERT_EQ(frequencyInfo[HostAndPort("s2")], NUM_ITERATIONS); +} + +TEST_F(ConfigOnlyServerSelectorTest, + ShouldExcludeInjectorSecondaryFromSecondaryPreferredSelection) { + InjectorSecondaryTopology topo(sdamConfiguration); + + std::map frequencyInfo{ + {HostAndPort("s0"), 0}, {HostAndPort("s1"), 0}, {HostAndPort("s2"), 0}}; + for (int i = 0; i < NUM_ITERATIONS; i++) { + auto server = + selector.selectServer(topo.topologyDescription, + ReadPreferenceSetting(ReadPreference::SecondaryPreferred), + {}); + if (server) { + frequencyInfo[(*server)->getAddress()]++; + } + } + + ASSERT_FALSE(frequencyInfo[HostAndPort("s0")]); + ASSERT_FALSE(frequencyInfo[HostAndPort("s1")]); + ASSERT_EQ(frequencyInfo[HostAndPort("s2")], NUM_ITERATIONS); +} + +TEST_F(ConfigOnlyServerSelectorTest, ShouldExcludeInjectorSecondaryFromNearestSelection) { + InjectorSecondaryTopology topo(sdamConfiguration); + + std::map frequencyInfo{ + {HostAndPort("s0"), 0}, {HostAndPort("s1"), 0}, {HostAndPort("s2"), 0}}; + for (int i = 0; i < NUM_ITERATIONS; i++) { + auto server = selector.selectServer( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::Nearest), {}); + if (server) { + frequencyInfo[(*server)->getAddress()]++; + } + } + + ASSERT_FALSE(frequencyInfo[HostAndPort("s1")]); + ASSERT(frequencyInfo[HostAndPort("s0")] || frequencyInfo[HostAndPort("s2")]); +} + +TEST_F(ServerSelectorTestFixture, ShouldNotExcludeInjectorSecondaryWhenNotConfigOnly) { + InjectorSecondaryTopology topo(sdamConfiguration); + + auto secondaryOnlyResult = selector.selectServers( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::SecondaryOnly), {}); + ASSERT(secondaryOnlyResult); + ASSERT_EQ(2, secondaryOnlyResult->size()); + + auto secondaryPreferredResult = selector.selectServers( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::SecondaryPreferred), {}); + ASSERT(secondaryPreferredResult); + ASSERT_EQ(2, secondaryPreferredResult->size()); + + auto nearestResult = selector.selectServers( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::Nearest), {}); + ASSERT(nearestResult); + ASSERT_EQ(3, nearestResult->size()); +} + +class ConfigOnlyAllInjectorsTest : public ConfigOnlyServerSelectorTest, + public ::testing::WithParamInterface {}; + +TEST_P(ConfigOnlyAllInjectorsTest, ShouldReturnNoneWhenAllNodesAreInjectors) { + AllInjectorsTopology topo(sdamConfiguration); + + auto pref = GetParam(); + auto result = selector.selectServers(topo.topologyDescription, ReadPreferenceSetting(pref), {}); + ASSERT_EQ(adaptForAssert(boost::none), adaptForAssert(result)); +} + +INSTANTIATE_TEST_SUITE_P(ReadPreferences, + ConfigOnlyAllInjectorsTest, + ::testing::Values(ReadPreference::PrimaryOnly, + ReadPreference::SecondaryOnly, + ReadPreference::PrimaryPreferred, + ReadPreference::SecondaryPreferred, + ReadPreference::Nearest)); + +TEST_F(ServerSelectorTestFixture, ShouldNotExcludeAnyNodesWhenAllInjectorsAndNotConfigOnly) { + AllInjectorsTopology topo(sdamConfiguration); + + auto primaryOnlyResult = selector.selectServers( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::PrimaryOnly), {}); + ASSERT(primaryOnlyResult); + ASSERT_EQ(1, primaryOnlyResult->size()); + ASSERT_EQ(HostAndPort("s0"), (*primaryOnlyResult)[0]->getAddress()); + + auto secondaryOnlyResult = selector.selectServers( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::SecondaryOnly), {}); + ASSERT(secondaryOnlyResult); + ASSERT_EQ(1, secondaryOnlyResult->size()); + ASSERT_EQ(HostAndPort("s1"), (*secondaryOnlyResult)[0]->getAddress()); + + auto nearestResult = selector.selectServers( + topo.topologyDescription, ReadPreferenceSetting(ReadPreference::Nearest), {}); + ASSERT(nearestResult); + ASSERT_EQ(2, nearestResult->size()); } TEST_F(ServerSelectorTestFixture, ShouldNotExcludeNonInjectorWithOtherTags) { TopologyStateMachine stateMachine(sdamConfiguration); auto topologyDescription = std::make_shared(sdamConfiguration); - const auto primary = ServerDescriptionBuilder() - .withAddress(HostAndPort("s0")) - .withType(ServerType::kRSPrimary) - .withLastUpdateTime(Date_t::now()) - .withLastWriteDate(Date_t::now()) - .withRtt(Milliseconds{1}) - .withSetName("set") - .withHost(HostAndPort("s0")) - .withMinWireVersion(WireVersion::SUPPORTS_OP_MSG) - .withMaxWireVersion(WireVersion::LATEST_WIRE_VERSION) - .withTag(ServerDescription::kProcessTypeTagKey, "NORMAL") - .withElectionId(kOidOne) - .withSetVersion(100) - .instance(); - stateMachine.onServerDescription(*topologyDescription, primary); + stateMachine.onServerDescription( + *topologyDescription, + makeReplicaSetPrimary(HostAndPort("s0"), {HostAndPort("s0")}, "NORMAL")); auto result = selector.selectServers( topologyDescription, ReadPreferenceSetting(ReadPreference::PrimaryOnly), {});