|
1 | 1 | package org.tron.common.backup; |
2 | 2 |
|
3 | 3 | import io.netty.channel.Channel; |
| 4 | +import io.prometheus.client.CollectorRegistry; |
4 | 5 | import java.lang.reflect.Field; |
5 | 6 | import java.lang.reflect.Method; |
6 | 7 | import java.lang.reflect.Modifier; |
|
20 | 21 | import org.junit.Rule; |
21 | 22 | import org.junit.Test; |
22 | 23 | import org.junit.rules.TemporaryFolder; |
| 24 | +import org.mockito.Mockito; |
23 | 25 | import org.tron.common.TestConstants; |
24 | 26 | import org.tron.common.backup.BackupManager.BackupStatusEnum; |
25 | 27 | import org.tron.common.backup.message.KeepAliveMessage; |
26 | 28 | import org.tron.common.backup.socket.BackupServer; |
| 29 | +import org.tron.common.backup.socket.MessageHandler; |
27 | 30 | import org.tron.common.backup.socket.UdpEvent; |
28 | 31 | import org.tron.common.parameter.CommonParameter; |
| 32 | +import org.tron.common.prometheus.MetricKeys; |
| 33 | +import org.tron.common.prometheus.SRMetrics; |
29 | 34 | import org.tron.common.utils.PublicMethod; |
30 | 35 | import org.tron.common.utils.ReflectUtils; |
| 36 | +import org.tron.consensus.base.Param; |
31 | 37 | import org.tron.core.config.args.Args; |
32 | 38 | import org.tron.core.config.args.InetUtil; |
33 | 39 |
|
@@ -76,6 +82,9 @@ public void tearDown() throws Exception { |
76 | 82 | }); |
77 | 83 | InetUtil.dnsLookup = previousDnsLookup; |
78 | 84 | Args.clearParam(); |
| 85 | + // the consensus miner list is a process-wide singleton: reset it so this class |
| 86 | + // cannot leak state into other tests of the same JVM |
| 87 | + Param.getInstance().setMiners(new ArrayList<>()); |
79 | 88 | BackupTestUtils.throwIfAnyError(errors); |
80 | 89 | } |
81 | 90 |
|
@@ -183,6 +192,64 @@ public void testBackupServerLifecycleDuringKeepAliveInterval() throws Exception |
183 | 192 | BackupTestUtils.assertExecutorsTerminated(manager, backupServer); |
184 | 193 | } |
185 | 194 |
|
| 195 | + /** |
| 196 | + * A backup group whose only member is this node's own address is a "single-master" group: |
| 197 | + * no peer ever answers a keep-alive, so the node must leave the pre-election INIT window and |
| 198 | + * self-promote to MASTER once the keep-alive timeout elapses. |
| 199 | + * |
| 200 | + * <p>Drives the real {@link BackupManager#init()} and its real scheduled keep-alive task. The |
| 201 | + * timeout condition is made deterministic by ageing the last keep-alive time, so the first |
| 202 | + * (fixed 1s) scheduler tick is guaranteed to observe an expired keep-alive; recovery is then |
| 203 | + * awaited by polling, so there is no fixed sleep. The published {@code tron:node_role} must |
| 204 | + * track the transition (0 = init, 1 = master) and the manager executor must terminate. |
| 205 | + */ |
| 206 | + @Test(timeout = 30000) |
| 207 | + public void singleMasterGroupRecoversFromInitToMaster() throws Exception { |
| 208 | + CommonParameter parameter = CommonParameter.getInstance(); |
| 209 | + parameter.setBackupPriority(8); |
| 210 | + // the group's only member is ourselves, so init() drops it and no peer can answer |
| 211 | + parameter.setBackupMembers( |
| 212 | + Collections.singletonList(InetAddress.getLocalHost().getHostAddress())); |
| 213 | + |
| 214 | + boolean previousMetrics = Args.getInstance().isMetricsPrometheusEnable(); |
| 215 | + Args.getInstance().setMetricsPrometheusEnable(true); |
| 216 | + BackupManager single = new BackupManager(); |
| 217 | + // absorb any keep-alive send in case the local IP lookup behaves differently |
| 218 | + single.setMessageHandler(Mockito.mock(MessageHandler.class)); |
| 219 | + try { |
| 220 | + // backup configured but not yet init'ed: the pre-election window reports init (0) |
| 221 | + Assert.assertEquals(BackupManager.BackupStatusEnum.MASTER, single.getStatus()); |
| 222 | + single.recordNodeRole(); |
| 223 | + Assert.assertEquals(SRMetrics.NODE_ROLE_INIT, nodeRoleGauge(), 0.0); |
| 224 | + |
| 225 | + single.init(); |
| 226 | + Assert.assertEquals(BackupManager.BackupStatusEnum.INIT, single.getStatus()); |
| 227 | + Assert.assertEquals(SRMetrics.NODE_ROLE_INIT, nodeRoleGauge(), 0.0); |
| 228 | + |
| 229 | + // no keep-alive has ever arrived, so the next keep-alive tick must self-promote. |
| 230 | + // setStatus() flips the volatile status before it republishes the gauge, so wait on |
| 231 | + // the published series (the externally visible result), not on the status field alone. |
| 232 | + ReflectUtils.setFieldValue(single, "lastKeepAliveTime", 0L); |
| 233 | + BackupTestUtils.awaitCondition("lone master to recover from INIT", |
| 234 | + () -> single.getStatus() == BackupManager.BackupStatusEnum.MASTER |
| 235 | + && Double.valueOf(SRMetrics.NODE_ROLE_MASTER).equals(nodeRoleGauge())); |
| 236 | + Assert.assertEquals(BackupManager.BackupStatusEnum.MASTER, single.getStatus()); |
| 237 | + Assert.assertEquals(SRMetrics.NODE_ROLE_MASTER, nodeRoleGauge(), 0.0); |
| 238 | + } finally { |
| 239 | + single.stop(); |
| 240 | + Args.getInstance().setMetricsPrometheusEnable(previousMetrics); |
| 241 | + } |
| 242 | + |
| 243 | + ScheduledExecutorService executorService = |
| 244 | + (ScheduledExecutorService) ReflectUtils.getFieldObject(single, "executorService"); |
| 245 | + Assert.assertTrue("single-master executor must terminate", executorService.isTerminated()); |
| 246 | + } |
| 247 | + |
| 248 | + private Double nodeRoleGauge() { |
| 249 | + return CollectorRegistry.defaultRegistry.getSampleValue( |
| 250 | + MetricKeys.Gauge.NODE_ROLE, new String[] {}, new String[] {}); |
| 251 | + } |
| 252 | + |
186 | 253 | // ===== domain-handling tests for init() ===== |
187 | 254 |
|
188 | 255 | @Test(timeout = 5000) |
|
0 commit comments