[nexus] add mechanism to emulate node reset (#11662)

This commit adds a new mechanism to emulate a node reset on
`Nexus::Node`. This is realized by resetting all platform components
while ensuring the non-volatile `mSettings` remains unchanged, then
reinitializing the `ot::Instance` by invoking its constructor.

This is used to add a new `test_full_network_reset` test, which
emulates a full simultaneous reset of all nodes in a large network,
tracking how long it takes for the network to stabilize after the
reset event.
This commit is contained in:
Abtin Keshavarzian
2025-07-08 15:10:30 -07:00
committed by GitHub
parent 64bc9f7ff6
commit a2fb36cd0b
11 changed files with 239 additions and 5 deletions
+1
View File
@@ -114,5 +114,6 @@ endmacro()
ot_nexus_test(border_agent)
ot_nexus_test(dtls)
ot_nexus_test(form_join)
ot_nexus_test(full_network_reset)
ot_nexus_test(large_network)
ot_nexus_test(trel)
+2 -5
View File
@@ -36,11 +36,8 @@ namespace Nexus {
struct Alarm
{
Alarm(void)
: mScheduled(false)
{
}
Alarm(void) { Reset(); }
void Reset(void) { mScheduled = false; }
bool ShouldTrigger(Time aNow) const { return mScheduled && (aNow >= mAlarmTime); }
bool mScheduled;
+6
View File
@@ -71,6 +71,12 @@ Mdns::Mdns(void)
SuccessOrQuit(mIfAddresses.PushBack(address));
}
void Mdns::Reset(void)
{
mEnabled = false;
mPendingTxList.Free();
}
Error Mdns::SetListeningEnabled(Instance &aInstance, bool aEnable, uint32_t aInfraIfIndex)
{
Error error = kErrorNone;
+1
View File
@@ -51,6 +51,7 @@ public:
};
Mdns(void);
void Reset(void);
Error SetListeningEnabled(Instance &aInstance, bool aEnable, uint32_t aInfraIfIndex);
void SendMulticast(Message &aMessage, uint32_t aInfraIfIndex);
void SendUnicast(Message &aMessage, const AddressInfo &aAddress);
+20
View File
@@ -32,6 +32,26 @@
namespace ot {
namespace Nexus {
void Node::Reset(void)
{
Instance *instance = &GetInstance();
uint32_t id = GetId();
mRadio.Reset();
mAlarm.Reset();
mMdns.Reset();
mPendingTasklet = false;
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
mTrel.Reset();
#endif
instance->~Instance();
instance = new (instance) Instance();
instance->SetId(id);
instance->AfterInit();
}
void Node::Form(void)
{
MeshCoP::Dataset::Info datasetInfo;
+1
View File
@@ -74,6 +74,7 @@ public:
kAsSed,
};
void Reset(void);
void Form(void);
void Join(Node &aNode, JoinMode aJoinMode = kAsFtd);
void AllowList(Node &aNode);
+9
View File
@@ -256,6 +256,15 @@ Radio::Radio(void)
{
}
void Radio::Reset(void)
{
mState = kStateDisabled;
mPromiscuous = false;
mSrcMatchEnabled = false;
mSrcMatchShortEntries.Clear();
mSrcMatchExtEntries.Clear();
}
bool Radio::CanReceiveOnChannel(uint8_t aChannel) const
{
bool canRx = false;
+1
View File
@@ -59,6 +59,7 @@ struct Radio
};
Radio(void);
void Reset(void);
bool CanReceiveOnChannel(uint8_t aChannel) const;
bool Matches(const Mac::Address &aAddress, Mac::PanId aPanId) const;
bool HasFramePendingFor(const Mac::Address &aAddress) const;
+7
View File
@@ -80,6 +80,13 @@ Trel::Trel(void)
ClearAllBytes(mCounters);
}
void Trel::Reset(void)
{
mEnabled = false;
ClearAllBytes(mCounters);
mPendingTxList.Free();
}
void Trel::Enable(uint16_t &aUdpPort)
{
mEnabled = true;
+1
View File
@@ -43,6 +43,7 @@ struct Trel
typedef otPlatTrelCounters Counters;
Trel(void);
void Reset(void);
void Enable(uint16_t &aUdpPort);
void Disable(void);
void Send(const uint8_t *aUdpPayload, uint16_t aUdpPayloadLen, const Ip6::SockAddr &aDestSockAddr);
+190
View File
@@ -0,0 +1,190 @@
/*
* Copyright (c) 2025, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h>
#include "platform/nexus_core.hpp"
#include "platform/nexus_node.hpp"
namespace ot {
namespace Nexus {
static constexpr uint16_t kNumberOfRoles = (Mle::kRoleLeader + 1);
typedef uint16_t RoleStats[kNumberOfRoles];
static void CalculateRoleStats(Core &aNexus, RoleStats &aRoleStats)
{
ClearAllBytes(aRoleStats);
for (Node &node : aNexus.GetNodes())
{
aRoleStats[node.Get<Mle::Mle>().GetRole()]++;
}
}
static bool CheckRoleStats(const RoleStats &aRoleStats)
{
return (aRoleStats[Mle::kRoleLeader] == 1) && (aRoleStats[Mle::kRoleDetached] == 0) &&
(aRoleStats[Mle::kRoleDisabled] == 0);
}
void Test(void)
{
static constexpr uint16_t kNumNodes = 200;
// All times in msec
static constexpr uint32_t kMaxWaitTime = 20 * Time::kOneMinuteInMsec;
static constexpr uint32_t kStatCollectionInterval = 500;
static constexpr uint32_t kExtraSimulTimeAfterPass = 5 * Time::kOneSecondInMsec;
Core nexus;
Node *leader;
RoleStats roleStats;
TimeMilli resetTime;
for (uint16_t i = 0; i < kNumNodes; i++)
{
nexus.CreateNode();
}
nexus.AdvanceTime(0);
Log("Starting %u nodes staggered", kNumNodes);
leader = nexus.GetNodes().GetHead();
leader->Form();
for (Node &node : nexus.GetNodes())
{
if (&node == leader)
{
continue;
}
node.Join(*leader);
nexus.AdvanceTime(Time::kOneSecondInMsec);
}
for (uint32_t step = 0; step < kMaxWaitTime / kStatCollectionInterval; step++)
{
if ((step % 20) == 0)
{
Log("+----------+----------+----------+----------+----------+");
Log("| Leader | Router | Child | Detached | Disabled |");
Log("+----------+----------+----------+----------+----------+");
}
CalculateRoleStats(nexus, roleStats);
Log("| %8u | %8u | %8u | %8u | %8u |", roleStats[Mle::kRoleLeader], roleStats[Mle::kRoleRouter],
roleStats[Mle::kRoleChild], roleStats[Mle::kRoleDetached], roleStats[Mle::kRoleDisabled]);
nexus.AdvanceTime(kStatCollectionInterval);
if (CheckRoleStats(roleStats))
{
break;
}
}
VerifyOrQuit(CheckRoleStats(roleStats));
Log("=========================================================");
Log("All nodes are now part of the same partition");
Log("Resetting all the nodes in the network");
Log("=========================================================");
resetTime = nexus.GetNow();
for (Node &node : nexus.GetNodes())
{
node.Reset();
}
for (Node &node : nexus.GetNodes())
{
node.Get<ThreadNetif>().Up();
SuccessOrQuit(node.Get<Mle::Mle>().Start());
}
for (uint32_t step = 0; step < kMaxWaitTime / kStatCollectionInterval; step++)
{
if ((step % 20) == 0)
{
Log("+----------+----------+----------+----------+----------+");
Log("| Leader | Router | Child | Detached | Disabled |");
Log("+----------+----------+----------+----------+----------+");
}
CalculateRoleStats(nexus, roleStats);
Log("| %8u | %8u | %8u | %8u | %8u |", roleStats[Mle::kRoleLeader], roleStats[Mle::kRoleRouter],
roleStats[Mle::kRoleChild], roleStats[Mle::kRoleDetached], roleStats[Mle::kRoleDisabled]);
nexus.AdvanceTime(kStatCollectionInterval);
if (CheckRoleStats(roleStats))
{
break;
}
}
Log("=========================================================");
Log("All nodes are restored");
Log("Network stabilized after %u sec", (nexus.GetNow() - resetTime) / Time::kOneSecondInMsec);
Log("=========================================================");
for (uint32_t step = 0; step < kExtraSimulTimeAfterPass / kStatCollectionInterval; step++)
{
if ((step % 20) == 0)
{
Log("+----------+----------+----------+----------+----------+");
Log("| Leader | Router | Child | Detached | Disabled |");
Log("+----------+----------+----------+----------+----------+");
}
CalculateRoleStats(nexus, roleStats);
Log("| %8u | %8u | %8u | %8u | %8u |", roleStats[Mle::kRoleLeader], roleStats[Mle::kRoleRouter],
roleStats[Mle::kRoleChild], roleStats[Mle::kRoleDetached], roleStats[Mle::kRoleDisabled]);
nexus.AdvanceTime(kStatCollectionInterval);
}
}
} // namespace Nexus
} // namespace ot
int main(void)
{
ot::Nexus::Test();
printf("All tests passed\n");
return 0;
}