已开启
【UBSE】补充HA相关IT用例 #1460
【UBSE】补充HA相关IT用例 #1460
已开启
weaver创建于 14 天前
15 个文件变更+394-5
@@ -196,7 +196,9 @@ set(UBSE_IT_SCENARIOS
196 tongsuan_1d_single_node196 tongsuan_1d_single_node
197 tongsuan_1d_two_nodes197 tongsuan_1d_two_nodes
198 tongsuan_1d_two_nodes_election_candidate_false198 tongsuan_1d_two_nodes_election_candidate_false
199+ tongsuan_1d_two_nodes_both_candidate_false
199 tongsuan_1d_four_nodes200 tongsuan_1d_four_nodes
201+ tongsuan_1d_four_nodes_staggered_start
200 tongsuan_clos_eight_nodes202 tongsuan_clos_eight_nodes
201 tongsuan_clos_eight_nodes_cap2203 tongsuan_clos_eight_nodes_cap2
202 zhisuan_npu_single_node204 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})
237else()239else()
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::infra446+} // namespace ubse::it::infra
@@ -178,4 +178,4 @@ private:
178 178 
179} // namespace ubse::it::infra179} // namespace ubse::it::infra
180 180 
181-#endif // IT_CLUSTER_H181+#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::infra161+} // namespace ubse::it::infra
@@ -99,4 +99,4 @@ private:
99 99 
100} // namespace ubse::it::infra100} // namespace ubse::it::infra
101 101 
102-#endif // IT_CLUSTER_BUILDER_H102+#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+#include "scenario.h"
14+#include "tests/election/election_cases.h"
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+#include "it_scenario_fixture.h"
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备
709TEST_F(Tongsuan1dFullMeshTwoNodesScenario, StandbyDownMasterDegrades)717TEST_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+#include "scenario.h"
13+#include "tests/election/election_cases.h"
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+#include "it_scenario_fixture.h"
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// 收敛后主节点应为候选节点、备节点为最小节点,并验证主节点周期性心跳。
208void RunTwoNodeElectionCandidateFalseTest(ubse::it::infra::ItCluster& cluster)227void 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代理
263void RunFourNodeElectionTest(ubse::it::infra::ItCluster& cluster)342void 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// 四节点主节点重启测试:收敛后重启主节点,备节点应接管成为新主,集群最终重新收敛
280void RunFourNodeMasterRestartTest(ubse::it::infra::ItCluster& cluster)439void 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代理
320void RunEightNodeElectionTest(ubse::it::infra::ItCluster& cluster)575void 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备(默认候选配置)
41void RunTwoNodeElectionTest(ubse::it::infra::ItCluster& cluster);41void 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// 收敛后主节点应为候选节点、备节点为最小节点,并验证主节点周期性心跳。
45void RunTwoNodeElectionCandidateFalseTest(ubse::it::infra::ItCluster& cluster);49void 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代理
48void RunFourNodeElectionTest(ubse::it::infra::ItCluster& cluster);56void RunFourNodeElectionTest(ubse::it::infra::ItCluster& cluster);
49 57 
@@ -57,9 +65,21 @@ void RunEightNodeClosCap2ElectionTest(ubse::it::infra::ItCluster& cluster);
57// 四节点主节点重启测试:收敛后重启主节点,备节点应接管成为新主,集群最终重新收敛65// 四节点主节点重启测试:收敛后重启主节点,备节点应接管成为新主,集群最终重新收敛
58void RunFourNodeMasterRestartTest(ubse::it::infra::ItCluster& cluster);66void 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不变;重启备节点后集群重新收敛。
61void RunTwoNodeStandbyDownMasterDegradesTest(ubse::it::infra::ItCluster& cluster);74void 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,新主保持不变。
65void RunFourNodeMasterAndStandbyDownAgentTakesOverTest(ubse::it::infra::ItCluster& cluster);85void RunFourNodeMasterAndStandbyDownAgentTakesOverTest(ubse::it::infra::ItCluster& cluster);