From b3c35bc41970caae1ca96ffa17d8c1f44ba457f7 Mon Sep 17 00:00:00 2001 From: "Kamoltat (Junior) Sirivadhna" Date: Wed, 22 Apr 2026 23:28:18 +0000 Subject: [PATCH] src/test/mon: test_monmap_monitor.cc Added the following test cases: - Test success when explicitly supplied tiebreaker - Test success when auto-selecting tiebreaker monitor - Test success with minimal valid configuration (1 monitor per zone) - Test success with auto-selection and minimal config (1 monitor per zone) - Test success when strategy is automatically changed to CONNECTIVITY - Test failure when auto-selecting and tiebreaker is in a data zone - Test failure when explicitly specifying tiebreaker in a data zone - Test failure when multiple potential tiebreakers exist - Test failure when one data zone has 0 monitors - Test failure when tiebreaker monitor doesn't exist Signed-off-by: Kamoltat (Junior) Sirivadhna --- src/test/mon/CMakeLists.txt | 7 + src/test/mon/test_monmap_monitor.cc | 524 ++++++++++++++++++++++++++++ 2 files changed, 531 insertions(+) create mode 100644 src/test/mon/test_monmap_monitor.cc diff --git a/src/test/mon/CMakeLists.txt b/src/test/mon/CMakeLists.txt index 5601ca4b890f..9cdebc220d65 100644 --- a/src/test/mon/CMakeLists.txt +++ b/src/test/mon/CMakeLists.txt @@ -88,3 +88,10 @@ add_executable(unittest_mon_election ) add_ceph_unittest(unittest_mon_election) target_link_libraries(unittest_mon_election mon global) + +# unittest_monmap_monitor +add_executable(unittest_monmap_monitor + test_monmap_monitor.cc + ) +add_ceph_unittest(unittest_monmap_monitor) +target_link_libraries(unittest_monmap_monitor mon global) diff --git a/src/test/mon/test_monmap_monitor.cc b/src/test/mon/test_monmap_monitor.cc new file mode 100644 index 000000000000..d29d349d7740 --- /dev/null +++ b/src/test/mon/test_monmap_monitor.cc @@ -0,0 +1,524 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 sts=2 expandtab + +/* + * Test stretch mode logic in MonmapMonitor + * + * Tests tiebreaker validation, auto-selection, and stretch mode enablement + * for monitor-side stretch cluster configuration. + */ + +#include "gtest/gtest.h" +#include "mon/MonMap.h" +#include "mon/MonmapMonitor.h" +#include "mon/Monitor.h" +#include "mon/Paxos.h" +#include "crush/CrushWrapper.h" +#include "common/ceph_context.h" +#include "global/global_init.h" +#include "common/common_init.h" +#include "common/ceph_argparse.h" + +#include +#include + +using namespace std; + +class MonmapMonitorStretchTest : public ::testing::Test { +protected: + unique_ptr cct; + MonMap monmap; + CrushWrapper crush; + + void SetUp() override { + vector args; + cct.reset(new CephContext(CEPH_ENTITY_TYPE_MON)); + cct->_conf.set_val("mon_election_default_strategy", "3"); // connectivity + } + + void setup_basic_monmap_5mons() { + // Create 5 monitors: a, b (zone1), c, d (zone2), e (zone3) + monmap.strategy = MonMap::CONNECTIVITY; + + entity_addrvec_t addrs_a; + entity_addr_t addr_a; + addr_a.parse("127.0.0.1:6789"); + addrs_a.v.push_back(addr_a); + monmap.add("a", addrs_a); + monmap.mon_info["a"].crush_loc["zone"] = "zone1"; + + entity_addrvec_t addrs_b; + entity_addr_t addr_b; + addr_b.parse("127.0.0.2:6789"); + addrs_b.v.push_back(addr_b); + monmap.add("b", addrs_b); + monmap.mon_info["b"].crush_loc["zone"] = "zone1"; + + entity_addrvec_t addrs_c; + entity_addr_t addr_c; + addr_c.parse("127.0.0.3:6789"); + addrs_c.v.push_back(addr_c); + monmap.add("c", addrs_c); + monmap.mon_info["c"].crush_loc["zone"] = "zone2"; + + entity_addrvec_t addrs_d; + entity_addr_t addr_d; + addr_d.parse("127.0.0.4:6789"); + addrs_d.v.push_back(addr_d); + monmap.add("d", addrs_d); + monmap.mon_info["d"].crush_loc["zone"] = "zone2"; + + entity_addrvec_t addrs_e; + entity_addr_t addr_e; + addr_e.parse("127.0.0.5:6789"); + addrs_e.v.push_back(addr_e); + monmap.add("e", addrs_e); + monmap.mon_info["e"].crush_loc["zone"] = "zone3"; + } + + void setup_crush_two_zones() { + // Create a CRUSH map with two zones (zone1, zone2) + crush.create(); + crush.set_max_devices(10); + + // Set up type hierarchy: root (type 10) > zone (type 8) > host (type 1) > osd (type 0) + crush.set_type_name(10, "root"); + crush.set_type_name(8, "zone"); + crush.set_type_name(1, "host"); + crush.set_type_name(0, "osd"); + + // Insert OSDs into zone1 and zone2 + // This will automatically create the zone buckets and make them valid subtrees + map loc_zone1; + loc_zone1["root"] = "default"; + loc_zone1["zone"] = "zone1"; + loc_zone1["host"] = "host1"; + + map loc_zone2; + loc_zone2["root"] = "default"; + loc_zone2["zone"] = "zone2"; + loc_zone2["host"] = "host2"; + + // Insert a few OSDs in each zone to make them valid subtrees + crush.insert_item(cct.get(), 0, 1.0, "osd.0", loc_zone1); + crush.insert_item(cct.get(), 1, 1.0, "osd.1", loc_zone1); + crush.insert_item(cct.get(), 2, 1.0, "osd.2", loc_zone2); + crush.insert_item(cct.get(), 3, 1.0, "osd.3", loc_zone2); + } + + void setup_monmap_bad_tiebreaker_in_zone1() { + // Create 5 monitors where 'e' is in zone1 (same as a, b) + monmap.strategy = MonMap::CONNECTIVITY; + + entity_addrvec_t addrs_a; + entity_addr_t addr_a; + addr_a.parse("127.0.0.1:6789"); + addrs_a.v.push_back(addr_a); + monmap.add("a", addrs_a); + monmap.mon_info["a"].crush_loc["zone"] = "zone1"; + + entity_addrvec_t addrs_b; + entity_addr_t addr_b; + addr_b.parse("127.0.0.2:6789"); + addrs_b.v.push_back(addr_b); + monmap.add("b", addrs_b); + monmap.mon_info["b"].crush_loc["zone"] = "zone1"; + + entity_addrvec_t addrs_c; + entity_addr_t addr_c; + addr_c.parse("127.0.0.3:6789"); + addrs_c.v.push_back(addr_c); + monmap.add("c", addrs_c); + monmap.mon_info["c"].crush_loc["zone"] = "zone2"; + + entity_addrvec_t addrs_d; + entity_addr_t addr_d; + addr_d.parse("127.0.0.4:6789"); + addrs_d.v.push_back(addr_d); + monmap.add("d", addrs_d); + monmap.mon_info["d"].crush_loc["zone"] = "zone2"; + + entity_addrvec_t addrs_e; + entity_addr_t addr_e; + addr_e.parse("127.0.0.5:6789"); + addrs_e.v.push_back(addr_e); + monmap.add("e", addrs_e); + monmap.mon_info["e"].crush_loc["zone"] = "zone1"; // BAD: in data zone + } + + void setup_monmap_multiple_tiebreakers() { + // Create 6 monitors with 2 in zone3 (e and f) + monmap.strategy = MonMap::CONNECTIVITY; + + entity_addrvec_t addrs_a; + entity_addr_t addr_a; + addr_a.parse("127.0.0.1:6789"); + addrs_a.v.push_back(addr_a); + monmap.add("a", addrs_a); + monmap.mon_info["a"].crush_loc["zone"] = "zone1"; + + entity_addrvec_t addrs_b; + entity_addr_t addr_b; + addr_b.parse("127.0.0.2:6789"); + addrs_b.v.push_back(addr_b); + monmap.add("b", addrs_b); + monmap.mon_info["b"].crush_loc["zone"] = "zone1"; + + entity_addrvec_t addrs_c; + entity_addr_t addr_c; + addr_c.parse("127.0.0.3:6789"); + addrs_c.v.push_back(addr_c); + monmap.add("c", addrs_c); + monmap.mon_info["c"].crush_loc["zone"] = "zone2"; + + entity_addrvec_t addrs_d; + entity_addr_t addr_d; + addr_d.parse("127.0.0.4:6789"); + addrs_d.v.push_back(addr_d); + monmap.add("d", addrs_d); + monmap.mon_info["d"].crush_loc["zone"] = "zone2"; + + entity_addrvec_t addrs_e; + entity_addr_t addr_e; + addr_e.parse("127.0.0.5:6789"); + addrs_e.v.push_back(addr_e); + monmap.add("e", addrs_e); + monmap.mon_info["e"].crush_loc["zone"] = "zone3"; + + entity_addrvec_t addrs_f; + entity_addr_t addr_f; + addr_f.parse("127.0.0.6:6789"); + addrs_f.v.push_back(addr_f); + monmap.add("f", addrs_f); + monmap.mon_info["f"].crush_loc["zone"] = "zone3"; // Multiple in zone3 + } + + void setup_monmap_zero_monitors_in_zone2() { + // Create 3 monitors: a, b (zone1), e (zone3) + // No monitors in zone2 - should fail validation + monmap.strategy = MonMap::CONNECTIVITY; + + entity_addrvec_t addrs_a; + entity_addr_t addr_a; + addr_a.parse("127.0.0.1:6789"); + addrs_a.v.push_back(addr_a); + monmap.add("a", addrs_a); + monmap.mon_info["a"].crush_loc["zone"] = "zone1"; + + entity_addrvec_t addrs_b; + entity_addr_t addr_b; + addr_b.parse("127.0.0.2:6789"); + addrs_b.v.push_back(addr_b); + monmap.add("b", addrs_b); + monmap.mon_info["b"].crush_loc["zone"] = "zone1"; + + entity_addrvec_t addrs_e; + entity_addr_t addr_e; + addr_e.parse("127.0.0.5:6789"); + addrs_e.v.push_back(addr_e); + monmap.add("e", addrs_e); + monmap.mon_info["e"].crush_loc["zone"] = "zone3"; + } + + void setup_monmap_one_monitor_per_zone() { + // Create 3 monitors: a (zone1), c (zone2), e (zone3) + // Minimal valid configuration - 1 monitor per zone + monmap.strategy = MonMap::CONNECTIVITY; + + entity_addrvec_t addrs_a; + entity_addr_t addr_a; + addr_a.parse("127.0.0.1:6789"); + addrs_a.v.push_back(addr_a); + monmap.add("a", addrs_a); + monmap.mon_info["a"].crush_loc["zone"] = "zone1"; + + entity_addrvec_t addrs_c; + entity_addr_t addr_c; + addr_c.parse("127.0.0.3:6789"); + addrs_c.v.push_back(addr_c); + monmap.add("c", addrs_c); + monmap.mon_info["c"].crush_loc["zone"] = "zone2"; + + entity_addrvec_t addrs_e; + entity_addr_t addr_e; + addr_e.parse("127.0.0.5:6789"); + addrs_e.v.push_back(addr_e); + monmap.add("e", addrs_e); + monmap.mon_info["e"].crush_loc["zone"] = "zone3"; + } +}; + +// Test success when explicitly supplied tiebreaker +TEST_F(MonmapMonitorStretchTest, ExplicitTiebreakerSuccess) { + setup_basic_monmap_5mons(); + setup_crush_two_zones(); + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Test validation phase (commit=false) + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "e", "zone", crush); + + EXPECT_TRUE(okay) << "Validation failed: " << ss.str(); + EXPECT_EQ(errcode, 0); + EXPECT_FALSE(monmap.stretch_mode_enabled) + << "Should not be enabled in validation phase"; + + // Test commit phase (commit=true) + ss.str(""); + okay = false; + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, true, "e", "zone", crush); + + EXPECT_TRUE(okay) << "Commit failed: " << ss.str(); + EXPECT_EQ(errcode, 0); + EXPECT_TRUE(monmap.stretch_mode_enabled); + EXPECT_EQ(monmap.tiebreaker_mon, "e"); + EXPECT_TRUE(monmap.disallowed_leaders.count("e")); +} + +// Test success when auto-selecting of tiebreaker monitor +TEST_F(MonmapMonitorStretchTest, AutoSelectTiebreakerSuccess) { + setup_basic_monmap_5mons(); + setup_crush_two_zones(); + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Test with empty tiebreaker_mon string (triggers auto-selection) + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "", "zone", crush); + + EXPECT_TRUE(okay) << "Auto-selection validation failed: " << ss.str(); + EXPECT_EQ(errcode, 0); + + // Commit with auto-selection + ss.str(""); + okay = false; + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, true, "", "zone", crush); + + EXPECT_TRUE(okay) << "Auto-selection commit failed: " << ss.str(); + EXPECT_EQ(errcode, 0); + EXPECT_TRUE(monmap.stretch_mode_enabled); + EXPECT_EQ(monmap.tiebreaker_mon, "e") + << "Should auto-select 'e' as tiebreaker"; + EXPECT_TRUE(monmap.disallowed_leaders.count("e")); +} + +// Test success when strategy is automatically changed to CONNECTIVITY +TEST_F(MonmapMonitorStretchTest, StrategyAutoChangedToConnectivity) { + setup_basic_monmap_5mons(); + setup_crush_two_zones(); + + // Start with classic strategy + monmap.strategy = MonMap::CLASSIC; + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Validation phase should succeed and prepare strategy change + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "e", "zone", crush); + + EXPECT_TRUE(okay) << "Validation should succeed: " << ss.str(); + EXPECT_EQ(errcode, 0); + EXPECT_FALSE(monmap.stretch_mode_enabled) + << "Should not be enabled in validation phase"; + + // Commit phase should enable stretch mode and change strategy + ss.str(""); + okay = false; + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, true, "e", "zone", crush); + + EXPECT_TRUE(okay) << "Commit should succeed: " << ss.str(); + EXPECT_EQ(errcode, 0); + EXPECT_TRUE(monmap.stretch_mode_enabled); + EXPECT_EQ(monmap.strategy, MonMap::CONNECTIVITY) + << "Strategy should be changed to CONNECTIVITY"; + EXPECT_EQ(monmap.tiebreaker_mon, "e"); + EXPECT_TRUE(monmap.disallowed_leaders.count("e")); +} + +// Test failure when auto-selecting and tiebreaker is in a data zone +TEST_F(MonmapMonitorStretchTest, AutoSelectFailTiebreakerInDataZone) { + setup_monmap_bad_tiebreaker_in_zone1(); // 'e' is in zone1, not zone3 + setup_crush_two_zones(); + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Try auto-selection - should fail because 'e' is in zone1 + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "", "zone", crush); + + EXPECT_FALSE(okay) << "Should fail when no tiebreaker in third zone"; + EXPECT_EQ(errcode, -EINVAL); + EXPECT_FALSE(monmap.stretch_mode_enabled); + + string error_msg = ss.str(); + EXPECT_TRUE(error_msg.find("Could not auto-select a tiebreaker monitor") != string::npos) + << "Error message: " << error_msg; + EXPECT_TRUE(error_msg.find("third") != string::npos) + << "Should mention need for third location. Error: " << error_msg; +} + +// Test failure when explicitly specifying tiebreaker in a data zone +TEST_F(MonmapMonitorStretchTest, ExplicitTiebreakerInDataZoneFails) { + setup_monmap_bad_tiebreaker_in_zone1(); + setup_crush_two_zones(); + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Explicitly specify 'e' which is in zone1 (a data zone) + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "e", "zone", crush); + + EXPECT_FALSE(okay) << "Should fail when tiebreaker is in data zone"; + EXPECT_EQ(errcode, -EINVAL); + EXPECT_FALSE(monmap.stretch_mode_enabled); + + string error_msg = ss.str(); + EXPECT_TRUE(error_msg.find("one of the two data zones") != string::npos) + << "Error message: " << error_msg; +} + +// Test failure when multiple potential tiebreakers exist +TEST_F(MonmapMonitorStretchTest, AutoSelectFailMultipleTiebreakers) { + setup_monmap_multiple_tiebreakers(); // Both 'e' and 'f' are in zone3 + setup_crush_two_zones(); + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Try auto-selection - should fail with multiple candidates + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "", "zone", crush); + + EXPECT_FALSE(okay) << "Should fail with multiple tiebreaker candidates"; + EXPECT_EQ(errcode, -EINVAL); + EXPECT_FALSE(monmap.stretch_mode_enabled); + + string error_msg = ss.str(); + EXPECT_TRUE(error_msg.find("Could not auto-select") != string::npos) + << "Error message: " << error_msg; + EXPECT_TRUE(error_msg.find("found 2 monitors") != string::npos || + error_msg.find("but need exactly 1") != string::npos) + << "Should mention multiple monitors found. Error: " << error_msg; +} + +// Test failure when tiebreaker monitor doesn't exist +TEST_F(MonmapMonitorStretchTest, NonExistentTiebreakerFails) { + setup_basic_monmap_5mons(); + setup_crush_two_zones(); + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Specify a monitor that doesn't exist + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "nonexistent", "zone", crush); + + EXPECT_FALSE(okay) << "Should fail with non-existent monitor"; + EXPECT_EQ(errcode, -ENOENT); + + string error_msg = ss.str(); + EXPECT_TRUE(error_msg.find("does not seem to exist") != string::npos) + << "Error message: " << error_msg; +} + +// Test failure when one data zone has 0 monitors +TEST_F(MonmapMonitorStretchTest, ZeroMonitorsInDataZoneFails) { + setup_monmap_zero_monitors_in_zone2(); // No monitors in zone2 + setup_crush_two_zones(); + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Try to enable stretch mode - should fail due to no monitors in zone2 + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "e", "zone", crush); + + EXPECT_FALSE(okay) << "Should fail when a data zone has 0 monitors"; + EXPECT_EQ(errcode, -EINVAL); + EXPECT_FALSE(monmap.stretch_mode_enabled); + + string error_msg = ss.str(); + // Validation fails early when detecting monitors aren't in both CRUSH subtrees + EXPECT_TRUE(error_msg.find("Could not find monitors in both CRUSH subtrees") != string::npos) + << "Error message: " << error_msg; + EXPECT_TRUE(error_msg.find("found monitors only in") != string::npos) + << "Should mention which zones have monitors. Error: " << error_msg; +} + +// Test success with minimal valid configuration (1 monitor per zone) +TEST_F(MonmapMonitorStretchTest, OneMonitorPerZoneSucceeds) { + setup_monmap_one_monitor_per_zone(); // a (zone1), c (zone2), e (zone3) + setup_crush_two_zones(); + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Validation phase with explicit tiebreaker + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "e", "zone", crush); + + EXPECT_TRUE(okay) << "Validation should succeed with 1 monitor per zone: " << ss.str(); + EXPECT_EQ(errcode, 0); + + // Commit phase + ss.str(""); + okay = false; + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, true, "e", "zone", crush); + + EXPECT_TRUE(okay) << "Commit should succeed: " << ss.str(); + EXPECT_EQ(errcode, 0); + EXPECT_TRUE(monmap.stretch_mode_enabled); + EXPECT_EQ(monmap.tiebreaker_mon, "e"); +} + +// Test success with auto-selection and 1 monitor per zone +TEST_F(MonmapMonitorStretchTest, OneMonitorPerZoneAutoSelectSucceeds) { + setup_monmap_one_monitor_per_zone(); // a (zone1), c (zone2), e (zone3) + setup_crush_two_zones(); + + stringstream ss; + bool okay = false; + int errcode = 0; + + // Auto-selection with minimal valid configuration + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, false, "", "zone", crush); + + EXPECT_TRUE(okay) << "Auto-selection should succeed: " << ss.str(); + EXPECT_EQ(errcode, 0); + + // Commit with auto-selection + ss.str(""); + okay = false; + MonmapMonitor::validate_and_enable_stretch_mode( + monmap, monmap, ss, &okay, &errcode, true, "", "zone", crush); + + EXPECT_TRUE(okay) << "Commit should succeed: " << ss.str(); + EXPECT_EQ(errcode, 0); + EXPECT_TRUE(monmap.stretch_mode_enabled); + EXPECT_EQ(monmap.tiebreaker_mon, "e") << "Should auto-select 'e' as tiebreaker"; +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} -- 2.47.3