已开启
【UBSE】补充HA相关IT用例 #1460
weaver创建于 14 天前
【UBSE】补充HA相关IT用例 #1460
已开启
共 15 个文件变更+394-5
| @@ -196,7 +196,9 @@ set(UBSE_IT_SCENARIOS | |||
| 196 | tongsuan_1d_single_node | 196 | tongsuan_1d_single_node |
| 197 | tongsuan_1d_two_nodes | 197 | tongsuan_1d_two_nodes |
| 198 | tongsuan_1d_two_nodes_election_candidate_false | 198 | tongsuan_1d_two_nodes_election_candidate_false |
| 199 | + tongsuan_1d_two_nodes_both_candidate_false | ||
| 199 | tongsuan_1d_four_nodes | 200 | tongsuan_1d_four_nodes |
| 201 | + tongsuan_1d_four_nodes_staggered_start | ||
| 200 | tongsuan_clos_eight_nodes | 202 | tongsuan_clos_eight_nodes |
| 201 | tongsuan_clos_eight_nodes_cap2 | 203 | tongsuan_clos_eight_nodes_cap2 |
| 202 | zhisuan_npu_single_node | 204 | zhisuan_npu_single_node |
| @@ -236,4 +238,4 @@ if(IT_SCENE_BINARIES) | |||
| 236 | add_dependencies(it ${IT_SCENE_BINARIES}) | 238 | add_dependencies(it ${IT_SCENE_BINARIES}) |
| 237 | else() | 239 | else() |
| 238 | message(WARNING "No IT scene binaries registered.") | 240 | message(WARNING "No IT scene binaries registered.") |
| 239 | -endif() | 241 | +endif() |
| @@ -443,4 +443,4 @@ UbseResult ItCluster::InjectAlarmEvent(const std::string& nodeId, unsigned short | |||
| 443 | return it->second->InjectAlarmEvent(alarmId, paras); | 443 | return it->second->InjectAlarmEvent(alarmId, paras); |
| 444 | } | 444 | } |
| 445 | 445 | ||
| 446 | -} // namespace ubse::it::infra | 446 | +} // namespace ubse::it::infra |
| @@ -178,4 +178,4 @@ private: | |||
| 178 | 178 | ||
| 179 | } // namespace ubse::it::infra | 179 | } // namespace ubse::it::infra |
| 180 | 180 | ||
| 181 | -#endif // IT_CLUSTER_H | 181 | +#endif // IT_CLUSTER_H |
| @@ -158,4 +158,4 @@ ClusterSpec ItClusterBuilder::BuildSpec() const | |||
| 158 | return spec; | 158 | return spec; |
| 159 | } | 159 | } |
| 160 | 160 | ||
| 161 | -} // namespace ubse::it::infra | 161 | +} // namespace ubse::it::infra |
| @@ -99,4 +99,4 @@ private: | |||
| 99 | 99 | ||
| 100 | } // namespace ubse::it::infra | 100 | } // namespace ubse::it::infra |
| 101 | 101 | ||
| 102 | -#endif // IT_CLUSTER_BUILDER_H | 102 | +#endif // IT_CLUSTER_BUILDER_H |
| @@ -180,3 +180,10 @@ TEST_F(Tongsuan1dFullMeshFourNodesScenario, MasterAndStandbyDownAgentTakesOver) | |||
| 180 | { | 180 | { |
| 181 | ubse::it::tests::election::RunFourNodeMasterAndStandbyDownAgentTakesOverTest(Cluster()); | 181 | ubse::it::tests::election::RunFourNodeMasterAndStandbyDownAgentTakesOverTest(Cluster()); |
| 182 | } | 182 | } |
| 183 | + | ||
| 184 | +// 备节点重选:收敛后kill备节点,主节点在剩余agent中重选出新备(主保持不变); | ||
| 185 | +// 重启原备后,再kill当前备节点,主节点再次重选出新备;最终集群收敛为1主+1备+2agent | ||
| 186 | +TEST_F(Tongsuan1dFullMeshFourNodesScenario, StandbyDownReelectNewStandby) | ||
| 187 | +{ | ||
| 188 | + ubse::it::tests::election::RunFourNodeStandbyReelectTest(Cluster()); | ||
| 189 | +} | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | +add_it_scene(four_nodes_staggered_start | ||
| 2 | + LINK_LIBS it_election_cases | ||
| 3 | +) | ||
| @@ -0,0 +1,23 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. | ||
| 3 | + * ubs-engine is licensed under Mulan PSL v2. | ||
| 4 | + * You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 5 | + * You may obtain a copy of Mulan PSL v2 at: | ||
| 6 | + * http://license.coscl.org.cn/MulanPSL2 | ||
| 7 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 8 | + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 9 | + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 10 | + * See the Mulan PSL v2 for more details. | ||
| 11 | + */ | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +using ubse::it::infra::Tongsuan1dFourNodesStaggeredStartScenario; | ||
| 17 | + | ||
| 18 | +// 四节点错峰启动:节点2先启动并独自成为主,随后节点1、3、4同时启动。 | ||
| 19 | +// 通过 CLI display cluster / display node 验证:存在唯一主备,主节点仍为节点2。 | ||
| 20 | +TEST_F(Tongsuan1dFourNodesStaggeredStartScenario, FourNodeStaggeredStartMasterKept) | ||
| 21 | +{ | ||
| 22 | + ubse::it::tests::election::RunFourNodeStaggeredStartTest(Cluster()); | ||
| 23 | +} | ||
| @@ -0,0 +1,19 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. | ||
| 3 | + * ubs-engine is licensed under Mulan PSL v2. | ||
| 4 | + * You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 5 | + * You may obtain a copy of Mulan PSL v2 at: | ||
| 6 | + * http://license.coscl.org.cn/MulanPSL2 | ||
| 7 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 8 | + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 9 | + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 10 | + * See the Mulan PSL v2 for more details. | ||
| 11 | + */ | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +// 通算1D四节点错峰启动场景:集群先全部启动并收敛(默认主=节点1、备=节点2), | ||
| 16 | +// 由测试用例通过 KillNode 停掉节点1、3、4(仅剩节点2),节点2检测主下线后升主; | ||
| 17 | +// 再通过 RestartNode 同时恢复节点1、3、4,验证主节点保持为节点2。 | ||
| 18 | +IT_DEFINE_SCENARIO(Tongsuan1dFourNodesStaggeredStartScenario, | ||
| 19 | + MakeBuilder().Tongsuan().FourNode().Start(cluster_)) | ||
| @@ -705,6 +705,14 @@ TEST_F(Tongsuan1dFullMeshTwoNodesScenario, ElectionConvergence) | |||
| 705 | ubse::it::tests::election::RunTwoNodeElectionTest(Cluster()); | 705 | ubse::it::tests::election::RunTwoNodeElectionTest(Cluster()); |
| 706 | } | 706 | } |
| 707 | 707 | ||
| 708 | +// 选举测试:验证主节点向集群其他节点周期发送心跳。 | ||
| 709 | +// 观测主节点日志 "[ELECTION] ProcTimer MASTER send pkt id=<id>"(DEBUG级别), | ||
| 710 | +// 等待3个心跳周期后应看到至少3次发送,且确有发往对端(备节点)的心跳。 | ||
| 711 | +TEST_F(Tongsuan1dFullMeshTwoNodesScenario, MasterPeriodicHeartbeat) | ||
| 712 | +{ | ||
| 713 | + ubse::it::tests::election::RunTwoNodeMasterPeriodicHeartbeatTest(Cluster()); | ||
| 714 | +} | ||
| 715 | + | ||
| 708 | // 选举测试:备节点故障后主节点检测备下线并清除standbyId,重启备后重新收敛为1主+1备 | 716 | // 选举测试:备节点故障后主节点检测备下线并清除standbyId,重启备后重新收敛为1主+1备 |
| 709 | TEST_F(Tongsuan1dFullMeshTwoNodesScenario, StandbyDownMasterDegrades) | 717 | TEST_F(Tongsuan1dFullMeshTwoNodesScenario, StandbyDownMasterDegrades) |
| 710 | { | 718 | { |
| @@ -0,0 +1,3 @@ | |||
| 1 | +add_it_scene(two_nodes_both_candidate_false | ||
| 2 | + LINK_LIBS it_election_cases | ||
| 3 | +) | ||
| @@ -0,0 +1,22 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. | ||
| 3 | + * ubs-engine is licensed under Mulan PSL v2. | ||
| 4 | + * You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 5 | + * You may obtain a copy of Mulan PSL v2 at: | ||
| 6 | + * http://license.coscl.org.cn/MulanPSL2 | ||
| 7 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 8 | + * EITHER EXPRESS OR IMPLIED, NON-INFRINGEMENT, MERCHANTABILITY OR A PARTICULAR PURPOSE. | ||
| 9 | + * See the Mulan PSL v2 for more details. | ||
| 10 | + */ | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +using ubse::it::infra::Tongsuan1dTwoNodesBothCandidateFalseScenario; | ||
| 16 | + | ||
| 17 | +// 双节点全候选关闭测试:两节点均配置 candidate=false,均不可参与选主。 | ||
| 18 | +// 等待超过选举超时后,预期两节点始终保持 init(Initializer)角色,不升主、不升备。 | ||
| 19 | +TEST_F(Tongsuan1dTwoNodesBothCandidateFalseScenario, ElectionBothCandidateFalseKeepInit) | ||
| 20 | +{ | ||
| 21 | + ubse::it::tests::election::RunTwoNodeBothCandidateFalseTest(Cluster()); | ||
| 22 | +} | ||
| @@ -0,0 +1,27 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. | ||
| 3 | + * ubs-engine is licensed under Mulan PSL v2. | ||
| 4 | + * You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 5 | + * You may obtain a copy of Mulan PSL v2 at: | ||
| 6 | + * http://license.coscl.org.cn/MulanPSL2 | ||
| 7 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 8 | + * EITHER EXPRESS OR IMPLIED, NON-INFRINGEMENT, MERCHANTABILITY OR A PARTICULAR PURPOSE. | ||
| 9 | + * See the Mulan PSL v2 for more details. | ||
| 10 | + */ | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +// 通算1D双节点全候选关闭场景:两节点均配置 election.candidate=false, | ||
| 15 | +// 均不可参与选主,集群不会收敛出主/备,两节点应始终保持 init(Initializer)角色。 | ||
| 16 | +// 由于选举永不收敛,需调用 NoElection() 跳过启动期的选举收敛等待; | ||
| 17 | +// 并开启 DEBUG 日志级别以便通过日志校验节点未发生升主/升备/升代理的角色切换。 | ||
| 18 | +IT_DEFINE_SCENARIO(Tongsuan1dTwoNodesBothCandidateFalseScenario, | ||
| 19 | + MakeBuilder() | ||
| 20 | + .Tongsuan() | ||
| 21 | + .TwoNode() | ||
| 22 | + .NoElection() | ||
| 23 | + .WithNodeConfig("1", "ubse.election", "election.candidate", "false") | ||
| 24 | + .WithNodeConfig("2", "ubse.election", "election.candidate", "false") | ||
| 25 | + .WithNodeConfig("1", "ubse.log", "log.level", "DEBUG") | ||
| 26 | + .WithNodeConfig("2", "ubse.log", "log.level", "DEBUG") | ||
| 27 | + .Start(cluster_)) | ||
| @@ -203,6 +203,25 @@ void RunTwoNodeElectionTest(ubse::it::infra::ItCluster& cluster) | |||
| 203 | CheckMasterPeriodicHeartbeat(cluster, roles.masterNodeId, peerNodeId); | 203 | CheckMasterPeriodicHeartbeat(cluster, roles.masterNodeId, peerNodeId); |
| 204 | } | 204 | } |
| 205 | 205 | ||
| 206 | +// 双节点主节点周期心跳测试:收敛为1主+1备后,验证主节点向集群其他节点周期发送心跳。 | ||
| 207 | +// 观测主节点日志中的 "[ELECTION] ProcTimer MASTER send pkt id=<id>"(DEBUG级别): | ||
| 208 | +// 等待 3 个心跳周期后,主节点至少发出 3 次心跳(证明周期性),且确有发往对端节点的心跳。 | ||
| 209 | +void RunTwoNodeMasterPeriodicHeartbeatTest(ubse::it::infra::ItCluster& cluster) | ||
| 210 | +{ | ||
| 211 | + // 收敛为1主+1备,并确认主节点为节点"1"(双节点默认配置,最小节点升主) | ||
| 212 | + auto roles = CollectElectionRoles(cluster); | ||
| 213 | + ASSERT_EQ(roles.masterCount, 1U) << "集群应存在唯一主节点"; | ||
| 214 | + ASSERT_EQ(roles.standbyCount, 1U) << "集群应存在唯一备节点"; | ||
| 215 | + ASSERT_EQ(roles.masterNodeId, "1") << "双节点默认配置下主节点应为节点1"; | ||
| 216 | + ASSERT_EQ(roles.standbyNodeId, "2") << "双节点默认配置下备节点应为节点2"; | ||
| 217 | + | ||
| 218 | + // 双节点环境中,主节点的对端节点即备节点(节点2) | ||
| 219 | + std::string peerNodeId = roles.standbyNodeId; | ||
| 220 | + | ||
| 221 | + // 通过主节点日志校验周期心跳:周期发送(>=3次)且确实发往对端节点 | ||
| 222 | + CheckMasterPeriodicHeartbeat(cluster, roles.masterNodeId, peerNodeId); | ||
| 223 | +} | ||
| 224 | + | ||
| 206 | // 双节点选举候选约束测试:最小节点 candidate=false、另一节点 candidate=true, | 225 | // 双节点选举候选约束测试:最小节点 candidate=false、另一节点 candidate=true, |
| 207 | // 收敛后主节点应为候选节点、备节点为最小节点,并验证主节点周期性心跳。 | 226 | // 收敛后主节点应为候选节点、备节点为最小节点,并验证主节点周期性心跳。 |
| 208 | void RunTwoNodeElectionCandidateFalseTest(ubse::it::infra::ItCluster& cluster) | 227 | void RunTwoNodeElectionCandidateFalseTest(ubse::it::infra::ItCluster& cluster) |
| @@ -259,6 +278,66 @@ void RunTwoNodeElectionCandidateFalseTest(ubse::it::infra::ItCluster& cluster) | |||
| 259 | CheckMasterPeriodicHeartbeat(cluster, roles.masterNodeId, peerNodeId); | 278 | CheckMasterPeriodicHeartbeat(cluster, roles.masterNodeId, peerNodeId); |
| 260 | } | 279 | } |
| 261 | 280 | ||
| 281 | +// 双节点全候选约束测试:两节点均配置 candidate=false,均不可参与选举。 | ||
| 282 | +// 预期两节点始终保持 init(Initializer)角色,不会升主、不会升备。 | ||
| 283 | +// | ||
| 284 | +// 说明:init 节点未加入集群,CLI display node/display cluster 会报 | ||
| 285 | +// "Node X does not exist in the cluster",无法通过 CLI 查询角色, | ||
| 286 | +// 故本用例不依赖 CLI,仅以节点日志为准: | ||
| 287 | +// - 正面信号:角色初始化为 Initializer,选举定时器持续运行(local node state is ready, start to elect.) | ||
| 288 | +// - 反面信号:等待超过选举超时后,任一节点日志中均不应出现 | ||
| 289 | +// SwitchRole Group Master/Standby/Agent 的角色切换记录(节点始终停留在 Initializer 角色) | ||
| 290 | +void RunTwoNodeBothCandidateFalseTest(ubse::it::infra::ItCluster& cluster) | ||
| 291 | +{ | ||
| 292 | + const auto& nodeIds = cluster.GetNodeIds(); | ||
| 293 | + ASSERT_EQ(nodeIds.size(), 2U) << "本用例要求2节点集群,实际=" << nodeIds.size(); | ||
| 294 | + | ||
| 295 | + // 1. 配置校验:两节点均应配置 election.candidate=false | ||
| 296 | + for (const auto& nodeId : nodeIds) { | ||
| 297 | + std::string candidate = | ||
| 298 | + ReadConfigValue(cluster.GetNode(nodeId).GetConfigFilePath(), "ubse.election", "election.candidate"); | ||
| 299 | + EXPECT_EQ(candidate, "false") << "节点 " << nodeId << " 应配置 election.candidate=false"; | ||
| 300 | + } | ||
| 301 | + | ||
| 302 | + // 2. 等待超过选举超时(lostThreshold(3) × heartbeat.timeInterval(默认2000ms) + 余量), | ||
| 303 | + // 期间若存在升主/升备的倾向应早已发生 | ||
| 304 | + uint32_t hbIntervalMs = 2000; | ||
| 305 | + auto intervalStr = ReadConfigValue(cluster.GetNode(nodeIds.front()).GetConfigFilePath(), "ubse.election", | ||
| 306 | + "heartbeat.timeInterval"); | ||
| 307 | + if (!intervalStr.empty()) { | ||
| 308 | + try { | ||
| 309 | + hbIntervalMs = static_cast<uint32_t>(std::stoul(intervalStr)); | ||
| 310 | + } catch (const std::exception&) { | ||
| 311 | + hbIntervalMs = 2000; | ||
| 312 | + } | ||
| 313 | + } | ||
| 314 | + if (hbIntervalMs == 0) { | ||
| 315 | + hbIntervalMs = 2000; | ||
| 316 | + } | ||
| 317 | + std::this_thread::sleep_for(std::chrono::milliseconds(3 * static_cast<int64_t>(hbIntervalMs) + 1000)); | ||
| 318 | + | ||
| 319 | + // 3. 日志正面校验:两节点角色初始化为 init(Initializer),且选举定时器持续运行 | ||
| 320 | + // (每次触发都会打印 start to elect,但 candidate=false 导致永远停留在 init 尝试阶段) | ||
| 321 | + for (const auto& nodeId : nodeIds) { | ||
| 322 | + const std::string logPath = cluster.GetNode(nodeId).GetLogFilePath(); | ||
| 323 | + EXPECT_GT(CountLogLines(logPath, "[ELECTION] Initializer:"), 0U) | ||
| 324 | + << "节点 " << nodeId << " 角色应初始化为 init(Initializer)"; | ||
| 325 | + EXPECT_GT(CountLogLines(logPath, "[ELECTION] local node state is ready, start to elect."), 0U) | ||
| 326 | + << "节点 " << nodeId << " 应持续处于 init 选举尝试阶段"; | ||
| 327 | + } | ||
| 328 | + | ||
| 329 | + // 4. 日志反面校验:两节点均未发生升主/升备/升代理的角色切换,即始终停留在 init(Initializer)角色 | ||
| 330 | + for (const auto& nodeId : nodeIds) { | ||
| 331 | + const std::string logPath = cluster.GetNode(nodeId).GetLogFilePath(); | ||
| 332 | + EXPECT_EQ(CountLogLines(logPath, "[ELECTION] SwitchRole Group Master"), 0U) | ||
| 333 | + << "节点 " << nodeId << " 不应升主"; | ||
| 334 | + EXPECT_EQ(CountLogLines(logPath, "[ELECTION] SwitchRole Group Standby"), 0U) | ||
| 335 | + << "节点 " << nodeId << " 不应升备"; | ||
| 336 | + EXPECT_EQ(CountLogLines(logPath, "[ELECTION] SwitchRole Group Agent"), 0U) | ||
| 337 | + << "节点 " << nodeId << " 不应升为代理,应保持 init 角色"; | ||
| 338 | + } | ||
| 339 | +} | ||
| 340 | + | ||
| 262 | // 四节点选举测试:验证集群收敛为1主+1备+2代理 | 341 | // 四节点选举测试:验证集群收敛为1主+1备+2代理 |
| 263 | void RunFourNodeElectionTest(ubse::it::infra::ItCluster& cluster) | 342 | void RunFourNodeElectionTest(ubse::it::infra::ItCluster& cluster) |
| 264 | { | 343 | { |
| @@ -276,6 +355,86 @@ void RunFourNodeElectionTest(ubse::it::infra::ItCluster& cluster) | |||
| 276 | EXPECT_EQ(masterNodeId, roles.masterNodeId); | 355 | EXPECT_EQ(masterNodeId, roles.masterNodeId); |
| 277 | } | 356 | } |
| 278 | 357 | ||
| 358 | +// 四节点错峰启动测试:节点2先启动并独自成为主节点,随后节点1、3、4同时启动。 | ||
| 359 | +// | ||
| 360 | +// 场景先全部启动并收敛,再用现有故障注入能力模拟错峰启动: | ||
| 361 | +// 1) 前置:集群已全部启动并收敛为1主+1备+2agent(默认主=节点1、备=节点2) | ||
| 362 | +// 2) KillNode 停掉节点1、3、4 -> 仅剩节点2 | ||
| 363 | +// 3) 节点2检测主节点1下线后于心跳超时升主,等待其角色成为 master(CLI 查询) | ||
| 364 | +// 4) RestartNode 同时恢复节点1、3、4(新节点以 init 身份加入,被主节点2收编) | ||
| 365 | +// 5) 等待收敛后通过 CLI display cluster / display node 验证: | ||
| 366 | +// 存在唯一主、唯一备,且主节点仍为节点2 | ||
| 367 | +void RunFourNodeStaggeredStartTest(ubse::it::infra::ItCluster& cluster) | ||
| 368 | +{ | ||
| 369 | + const auto& nodeIds = cluster.GetNodeIds(); | ||
| 370 | + ASSERT_EQ(nodeIds.size(), 4U) << "本用例要求4节点集群,实际=" << nodeIds.size(); | ||
| 371 | + | ||
| 372 | + // 1. 前置:集群已全部启动并收敛为1主+1备+2agent | ||
| 373 | + auto roles = CollectElectionRoles(cluster); | ||
| 374 | + ASSERT_EQ(roles.masterCount, 1U); | ||
| 375 | + ASSERT_EQ(roles.standbyCount, 1U); | ||
| 376 | + | ||
| 377 | + // 2. 停掉节点1、3、4,仅保留节点2 | ||
| 378 | + ASSERT_IT_OK(cluster.KillNode("1")); | ||
| 379 | + ASSERT_IT_OK(cluster.KillNode("3")); | ||
| 380 | + ASSERT_IT_OK(cluster.KillNode("4")); | ||
| 381 | + ASSERT_FALSE(cluster.IsNodeRunning("1")); | ||
| 382 | + ASSERT_FALSE(cluster.IsNodeRunning("3")); | ||
| 383 | + ASSERT_FALSE(cluster.IsNodeRunning("4")); | ||
| 384 | + ASSERT_TRUE(cluster.IsNodeRunning("2")); | ||
| 385 | + | ||
| 386 | + // 3. 等待节点2独自成为主节点(主节点1下线,节点2心跳超时后升主) | ||
| 387 | + auto ret = ubse::it::infra::ItWaitHelper::WaitForCondition( | ||
| 388 | + [&]() -> bool { | ||
| 389 | + std::string role; | ||
| 390 | + return cluster.GetCliInvoker("2").GetRole(role) == UBS_SUCCESS && | ||
| 391 | + role == ubse::election::ELECTION_ROLE_MASTER; | ||
| 392 | + }, | ||
| 393 | + 30000); | ||
| 394 | + EXPECT_IT_OK(ret) << "仅剩节点2时应由节点2升为主节点"; | ||
| 395 | + | ||
| 396 | + // 4. 同时恢复节点1、3、4(不逐个等待收敛,最后统一收敛) | ||
| 397 | + ASSERT_IT_OK(cluster.RestartNode("1", false)); | ||
| 398 | + ASSERT_IT_OK(cluster.RestartNode("3", false)); | ||
| 399 | + ASSERT_IT_OK(cluster.RestartNode("4", false)); | ||
| 400 | + ASSERT_TRUE(cluster.IsNodeRunning("1")); | ||
| 401 | + ASSERT_TRUE(cluster.IsNodeRunning("3")); | ||
| 402 | + ASSERT_TRUE(cluster.IsNodeRunning("4")); | ||
| 403 | + | ||
| 404 | + // 5. 等待集群选举收敛(1主+1备+2agent) | ||
| 405 | + ASSERT_IT_OK(cluster.WaitForElectionConvergence(30000)); | ||
| 406 | + | ||
| 407 | + // 6. 通过 CLI display node 统计角色:唯一主、唯一备,主节点仍为节点2 | ||
| 408 | + auto finalRoles = CollectElectionRoles(cluster); | ||
| 409 | + EXPECT_EQ(finalRoles.masterCount, 1U) << "集群应存在唯一主节点"; | ||
| 410 | + EXPECT_EQ(finalRoles.standbyCount, 1U) << "集群应存在唯一备节点"; | ||
| 411 | + EXPECT_EQ(finalRoles.agentCount, 2U) << "集群应存在2个agent节点"; | ||
| 412 | + EXPECT_EQ(finalRoles.agentNodeIds.size(), 2U); | ||
| 413 | + EXPECT_EQ(finalRoles.masterNodeId, "2") << "主节点应保持为节点2"; | ||
| 414 | + | ||
| 415 | + // 7. 通过 CLI display cluster 从主节点2视角再次确认唯一主备 | ||
| 416 | + std::vector<ubse::it::infra::ItNodeInfo> nodeInfos; | ||
| 417 | + EXPECT_EQ(cluster.GetCliInvoker("2").QueryClusterInfo(nodeInfos), UBS_SUCCESS); | ||
| 418 | + uint32_t masterCount = 0; | ||
| 419 | + uint32_t standbyCount = 0; | ||
| 420 | + uint32_t agentCount = 0; | ||
| 421 | + std::string masterInView; | ||
| 422 | + for (const auto& info : nodeInfos) { | ||
| 423 | + if (info.role == ubse::election::ELECTION_ROLE_MASTER) { | ||
| 424 | + ++masterCount; | ||
| 425 | + masterInView = ubse::it::infra::util::ExtractNodeId(info.node); | ||
| 426 | + } else if (info.role == ubse::election::ELECTION_ROLE_STANDBY) { | ||
| 427 | + ++standbyCount; | ||
| 428 | + } else if (info.role == ubse::election::ELECTION_ROLE_AGENT) { | ||
| 429 | + ++agentCount; | ||
| 430 | + } | ||
| 431 | + } | ||
| 432 | + EXPECT_EQ(masterCount, 1U) << "display cluster 应显示唯一主节点"; | ||
| 433 | + EXPECT_EQ(standbyCount, 1U) << "display cluster 应显示唯一备节点"; | ||
| 434 | + EXPECT_EQ(agentCount, 2U) << "display cluster 应显示2个agent节点"; | ||
| 435 | + EXPECT_EQ(masterInView, "2") << "display cluster 中主节点应仍为节点2"; | ||
| 436 | +} | ||
| 437 | + | ||
| 279 | // 四节点主节点重启测试:收敛后重启主节点,备节点应接管成为新主,集群最终重新收敛 | 438 | // 四节点主节点重启测试:收敛后重启主节点,备节点应接管成为新主,集群最终重新收敛 |
| 280 | void RunFourNodeMasterRestartTest(ubse::it::infra::ItCluster& cluster) | 439 | void RunFourNodeMasterRestartTest(ubse::it::infra::ItCluster& cluster) |
| 281 | { | 440 | { |
| @@ -316,6 +475,102 @@ void RunFourNodeMasterRestartTest(ubse::it::infra::ItCluster& cluster) | |||
| 316 | EXPECT_EQ(after.agentCount, 2U); | 475 | EXPECT_EQ(after.agentCount, 2U); |
| 317 | } | 476 | } |
| 318 | 477 | ||
| 478 | +// 四节点备节点重选测试:收敛为1主+1备+2agent后, | ||
| 479 | +// 1) kill 备节点 -> 主节点检测备下线后在剩余 agent 中重选出新备(主角色保持不变,全程通过 CLI 查询验证) | ||
| 480 | +// 2) 重启原备节点(以 agent 身份回归)-> 集群重新收敛为1主+1备+2agent,主与新备保持不变 | ||
| 481 | +// 3) kill 当前备节点 -> 主节点再次重选出新备(主角色保持不变) | ||
| 482 | +// 4) 重启该备节点 -> 集群重新收敛为1主+1备+2agent,主保持不变 | ||
| 483 | +// 覆盖源码路径:Master 检测备心跳超时后清除 standbyId_,并在存活 agent 中重选备。 | ||
| 484 | +void RunFourNodeStandbyReelectTest(ubse::it::infra::ItCluster& cluster) | ||
| 485 | +{ | ||
| 486 | + // 前置条件:集群已收敛为1主+1备+2代理 | ||
| 487 | + RunFourNodeElectionTest(cluster); | ||
| 488 | + | ||
| 489 | + auto before = CollectElectionRoles(cluster); | ||
| 490 | + ASSERT_EQ(before.masterCount, 1U); | ||
| 491 | + ASSERT_EQ(before.standbyCount, 1U); | ||
| 492 | + ASSERT_EQ(before.agentCount, 2U); | ||
| 493 | + const std::string masterId = before.masterNodeId; | ||
| 494 | + const std::string standby1 = before.standbyNodeId; | ||
| 495 | + ASSERT_FALSE(masterId.empty()); | ||
| 496 | + ASSERT_FALSE(standby1.empty()); | ||
| 497 | + | ||
| 498 | + // 通过 display cluster 查询主节点视角下角色为 standby 的节点ID(解析 hostname(id) 形式的节点名) | ||
| 499 | + auto queryStandbyFromMaster = [&]() -> std::string { | ||
| 500 | + std::vector<ubse::it::infra::ItNodeInfo> nodeInfos; | ||
| 501 | + if (cluster.GetCliInvoker(masterId).QueryClusterInfo(nodeInfos) != UBS_SUCCESS) { | ||
| 502 | + return ""; | ||
| 503 | + } | ||
| 504 | + for (const auto& info : nodeInfos) { | ||
| 505 | + if (info.role == ubse::election::ELECTION_ROLE_STANDBY) { | ||
| 506 | + return ubse::it::infra::util::ExtractNodeId(info.node); | ||
| 507 | + } | ||
| 508 | + } | ||
| 509 | + return ""; | ||
| 510 | + }; | ||
| 511 | + | ||
| 512 | + // 轮询主节点视角,等待出现一个不同于指定节点的备节点(即主节点重选出新备) | ||
| 513 | + auto waitNewStandby = [&](const std::string& excludedNodeId) -> std::string { | ||
| 514 | + std::string newStandby; | ||
| 515 | + auto ret = ubse::it::infra::ItWaitHelper::WaitForCondition( | ||
| 516 | + [&]() -> bool { | ||
| 517 | + newStandby = queryStandbyFromMaster(); | ||
| 518 | + return !newStandby.empty() && newStandby != excludedNodeId; | ||
| 519 | + }, | ||
| 520 | + 30000); | ||
| 521 | + EXPECT_IT_OK(ret) << "master did not re-elect a new standby within timeout"; | ||
| 522 | + return newStandby; | ||
| 523 | + }; | ||
| 524 | + | ||
| 525 | + // ============ 第一轮:kill 备节点,主节点重选新备 ============ | ||
| 526 | + ASSERT_IT_OK(cluster.KillNode(standby1)); | ||
| 527 | + EXPECT_FALSE(cluster.IsNodeRunning(standby1)); | ||
| 528 | + | ||
| 529 | + const std::string newStandby1 = waitNewStandby(standby1); | ||
| 530 | + ASSERT_FALSE(newStandby1.empty()) << "应选出新备节点"; | ||
| 531 | + EXPECT_NE(newStandby1, masterId) << "新备不应为主节点"; | ||
| 532 | + EXPECT_NE(newStandby1, standby1) << "新备不应为已宕机的原备"; | ||
| 533 | + | ||
| 534 | + // 主角色保持不变 | ||
| 535 | + std::string masterRole; | ||
| 536 | + EXPECT_EQ(cluster.GetCliInvoker(masterId).GetRole(masterRole), UBS_SUCCESS); | ||
| 537 | + EXPECT_EQ(masterRole, ubse::election::ELECTION_ROLE_MASTER); | ||
| 538 | + | ||
| 539 | + // ============ 重启原备节点,以 agent 身份回归 ============ | ||
| 540 | + ASSERT_IT_OK(cluster.RestartNode(standby1, true, 30000)); | ||
| 541 | + EXPECT_TRUE(cluster.IsNodeRunning(standby1)); | ||
| 542 | + | ||
| 543 | + auto roles1 = CollectElectionRoles(cluster); | ||
| 544 | + EXPECT_EQ(roles1.masterCount, 1U); | ||
| 545 | + EXPECT_EQ(roles1.standbyCount, 1U); | ||
| 546 | + EXPECT_EQ(roles1.agentCount, 2U); | ||
| 547 | + EXPECT_EQ(roles1.masterNodeId, masterId) << "主节点应保持不变"; | ||
| 548 | + EXPECT_EQ(roles1.standbyNodeId, newStandby1) << "重选出的新备应保持为备"; | ||
| 549 | + | ||
| 550 | + // ============ 第二轮:kill 当前备节点,再次重选新备 ============ | ||
| 551 | + ASSERT_IT_OK(cluster.KillNode(newStandby1)); | ||
| 552 | + EXPECT_FALSE(cluster.IsNodeRunning(newStandby1)); | ||
| 553 | + | ||
| 554 | + const std::string newStandby2 = waitNewStandby(newStandby1); | ||
| 555 | + ASSERT_FALSE(newStandby2.empty()) << "应再次选出新备节点"; | ||
| 556 | + EXPECT_NE(newStandby2, masterId) << "再次重选出的新备不应为主节点"; | ||
| 557 | + EXPECT_NE(newStandby2, newStandby1) << "再次重选出的新备不应为已宕机的当前备"; | ||
| 558 | + | ||
| 559 | + // 主角色仍保持不变 | ||
| 560 | + EXPECT_EQ(cluster.GetCliInvoker(masterId).GetRole(masterRole), UBS_SUCCESS); | ||
| 561 | + EXPECT_EQ(masterRole, ubse::election::ELECTION_ROLE_MASTER); | ||
| 562 | + | ||
| 563 | + // ============ 恢复:重启该备节点,集群重新收敛 ============ | ||
| 564 | + ASSERT_IT_OK(cluster.RestartNode(newStandby1, true, 30000)); | ||
| 565 | + EXPECT_TRUE(cluster.IsNodeRunning(newStandby1)); | ||
| 566 | + | ||
| 567 | + auto roles2 = CollectElectionRoles(cluster); | ||
| 568 | + EXPECT_EQ(roles2.masterCount, 1U); | ||
| 569 | + EXPECT_EQ(roles2.standbyCount, 1U); | ||
| 570 | + EXPECT_EQ(roles2.agentCount, 2U); | ||
| 571 | + EXPECT_EQ(roles2.masterNodeId, masterId) << "主节点应保持不变"; | ||
| 572 | +} | ||
| 573 | + | ||
| 319 | // 八节点CLOS选举测试:通过 display cluster 获取集群视图,验证收敛为1主+1备+6代理 | 574 | // 八节点CLOS选举测试:通过 display cluster 获取集群视图,验证收敛为1主+1备+6代理 |
| 320 | void RunEightNodeElectionTest(ubse::it::infra::ItCluster& cluster) | 575 | void RunEightNodeElectionTest(ubse::it::infra::ItCluster& cluster) |
| 321 | { | 576 | { |
| @@ -40,10 +40,18 @@ void RunSingleNodeElectionTest(ubse::it::infra::ItCluster& cluster); | |||
| 40 | // 双节点选举测试:验证集群收敛为1主+1备(默认候选配置) | 40 | // 双节点选举测试:验证集群收敛为1主+1备(默认候选配置) |
| 41 | void RunTwoNodeElectionTest(ubse::it::infra::ItCluster& cluster); | 41 | void RunTwoNodeElectionTest(ubse::it::infra::ItCluster& cluster); |
| 42 | 42 | ||
| 43 | +// 双节点主节点周期心跳测试:收敛为1主+1备后,通过主节点日志 | ||
| 44 | +// "[ELECTION] ProcTimer MASTER send pkt id=<id>" 验证主节点周期性地向对端节点发送心跳。 | ||
| 45 | +void RunTwoNodeMasterPeriodicHeartbeatTest(ubse::it::infra::ItCluster& cluster); | ||
| 46 | + | ||
| 43 | // 双节点选举候选约束测试:最小节点 candidate=false、另一节点 candidate=true, | 47 | // 双节点选举候选约束测试:最小节点 candidate=false、另一节点 candidate=true, |
| 44 | // 收敛后主节点应为候选节点、备节点为最小节点,并验证主节点周期性心跳。 | 48 | // 收敛后主节点应为候选节点、备节点为最小节点,并验证主节点周期性心跳。 |
| 45 | void RunTwoNodeElectionCandidateFalseTest(ubse::it::infra::ItCluster& cluster); | 49 | void RunTwoNodeElectionCandidateFalseTest(ubse::it::infra::ItCluster& cluster); |
| 46 | 50 | ||
| 51 | +// 双节点全候选约束测试:两节点均配置 candidate=false,均不可参与选举, | ||
| 52 | +// 等待选举超时后两节点应始终保持 init(Initializer)角色,不升主、不升备。 | ||
| 53 | +void RunTwoNodeBothCandidateFalseTest(ubse::it::infra::ItCluster& cluster); | ||
| 54 | + | ||
| 47 | // 四节点选举测试:验证集群收敛为1主+1备+2代理 | 55 | // 四节点选举测试:验证集群收敛为1主+1备+2代理 |
| 48 | void RunFourNodeElectionTest(ubse::it::infra::ItCluster& cluster); | 56 | void RunFourNodeElectionTest(ubse::it::infra::ItCluster& cluster); |
| 49 | 57 | ||
| @@ -57,9 +65,21 @@ void RunEightNodeClosCap2ElectionTest(ubse::it::infra::ItCluster& cluster); | |||
| 57 | // 四节点主节点重启测试:收敛后重启主节点,备节点应接管成为新主,集群最终重新收敛 | 65 | // 四节点主节点重启测试:收敛后重启主节点,备节点应接管成为新主,集群最终重新收敛 |
| 58 | void RunFourNodeMasterRestartTest(ubse::it::infra::ItCluster& cluster); | 66 | void RunFourNodeMasterRestartTest(ubse::it::infra::ItCluster& cluster); |
| 59 | 67 | ||
| 68 | +// 四节点备节点重选测试:收敛为1主+1备+2agent后,kill备节点,主节点检测备下线后在剩余agent中 | ||
| 69 | +// 重选出新备(主角色保持不变);重启原备(以agent身份回归)后集群收敛;再kill当前备节点, | ||
| 70 | +// 主节点再次重选出新备;重启该备后集群重新收敛,全程通过 CLI display cluster/display node 验证。 | ||
| 71 | +void RunFourNodeStandbyReelectTest(ubse::it::infra::ItCluster& cluster); | ||
| 72 | + | ||
| 60 | // 双节点备故障:收敛后kill备节点,主节点检测备下线后清除standbyId,主节点角色保持master不变;重启备节点后集群重新收敛。 | 73 | // 双节点备故障:收敛后kill备节点,主节点检测备下线后清除standbyId,主节点角色保持master不变;重启备节点后集群重新收敛。 |
| 61 | void RunTwoNodeStandbyDownMasterDegradesTest(ubse::it::infra::ItCluster& cluster); | 74 | void RunTwoNodeStandbyDownMasterDegradesTest(ubse::it::infra::ItCluster& cluster); |
| 62 | 75 | ||
| 76 | +// 四节点错峰启动测试:节点2先启动并独自成为主节点,随后节点1、3、4同时启动。 | ||
| 77 | +// 全程通过 CLI display cluster/display node 验证: | ||
| 78 | +// - 集群收敛为唯一1主+1备+2agent | ||
| 79 | +// - 主节点仍为先启动的节点2(次小节点2在4节点配置中独自启动时升主,其他节点加入后保持主角色) | ||
| 80 | +// - 节点1成为备节点、节点3/4为agent | ||
| 81 | +void RunFourNodeStaggeredStartTest(ubse::it::infra::ItCluster& cluster); | ||
| 82 | + | ||
| 63 | // 四节点主备同时故障agent升主测试:收敛后同时kill主和备,两个agent节点检测主备下线后,较小ID agent升主,另一agent被收编为新备; | 83 | // 四节点主备同时故障agent升主测试:收敛后同时kill主和备,两个agent节点检测主备下线后,较小ID agent升主,另一agent被收编为新备; |
| 64 | // 重启原主和原备后集群重新收敛为1主+1备+2agent,新主保持不变。 | 84 | // 重启原主和原备后集群重新收敛为1主+1备+2agent,新主保持不变。 |
| 65 | void RunFourNodeMasterAndStandbyDownAgentTakesOverTest(ubse::it::infra::ItCluster& cluster); | 85 | void RunFourNodeMasterAndStandbyDownAgentTakesOverTest(ubse::it::infra::ItCluster& cluster); |