From a2fb36cd0b728198991de449cf1b530b0918c0fe Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 8 Jul 2025 15:10:30 -0700 Subject: [PATCH] [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. --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/platform/nexus_alarm.hpp | 7 +- tests/nexus/platform/nexus_mdns.cpp | 6 + tests/nexus/platform/nexus_mdns.hpp | 1 + tests/nexus/platform/nexus_node.cpp | 20 +++ tests/nexus/platform/nexus_node.hpp | 1 + tests/nexus/platform/nexus_radio.cpp | 9 ++ tests/nexus/platform/nexus_radio.hpp | 1 + tests/nexus/platform/nexus_trel.cpp | 7 + tests/nexus/platform/nexus_trel.hpp | 1 + tests/nexus/test_full_network_reset.cpp | 190 ++++++++++++++++++++++++ 11 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 tests/nexus/test_full_network_reset.cpp diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 95e668067..b70224027 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -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) diff --git a/tests/nexus/platform/nexus_alarm.hpp b/tests/nexus/platform/nexus_alarm.hpp index d569c04da..0a6fbbf3f 100644 --- a/tests/nexus/platform/nexus_alarm.hpp +++ b/tests/nexus/platform/nexus_alarm.hpp @@ -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; diff --git a/tests/nexus/platform/nexus_mdns.cpp b/tests/nexus/platform/nexus_mdns.cpp index 9318f3c6b..e32ba33b2 100644 --- a/tests/nexus/platform/nexus_mdns.cpp +++ b/tests/nexus/platform/nexus_mdns.cpp @@ -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; diff --git a/tests/nexus/platform/nexus_mdns.hpp b/tests/nexus/platform/nexus_mdns.hpp index f4c5dbce6..52238f3f1 100644 --- a/tests/nexus/platform/nexus_mdns.hpp +++ b/tests/nexus/platform/nexus_mdns.hpp @@ -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); diff --git a/tests/nexus/platform/nexus_node.cpp b/tests/nexus/platform/nexus_node.cpp index 270260c18..30eb31659 100644 --- a/tests/nexus/platform/nexus_node.cpp +++ b/tests/nexus/platform/nexus_node.cpp @@ -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; diff --git a/tests/nexus/platform/nexus_node.hpp b/tests/nexus/platform/nexus_node.hpp index cf7cda960..65ea7e516 100644 --- a/tests/nexus/platform/nexus_node.hpp +++ b/tests/nexus/platform/nexus_node.hpp @@ -74,6 +74,7 @@ public: kAsSed, }; + void Reset(void); void Form(void); void Join(Node &aNode, JoinMode aJoinMode = kAsFtd); void AllowList(Node &aNode); diff --git a/tests/nexus/platform/nexus_radio.cpp b/tests/nexus/platform/nexus_radio.cpp index 29744b5dc..487dba11c 100644 --- a/tests/nexus/platform/nexus_radio.cpp +++ b/tests/nexus/platform/nexus_radio.cpp @@ -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; diff --git a/tests/nexus/platform/nexus_radio.hpp b/tests/nexus/platform/nexus_radio.hpp index 242205e03..80f2c303f 100644 --- a/tests/nexus/platform/nexus_radio.hpp +++ b/tests/nexus/platform/nexus_radio.hpp @@ -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; diff --git a/tests/nexus/platform/nexus_trel.cpp b/tests/nexus/platform/nexus_trel.cpp index 4242b5f33..92cd285eb 100644 --- a/tests/nexus/platform/nexus_trel.cpp +++ b/tests/nexus/platform/nexus_trel.cpp @@ -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; diff --git a/tests/nexus/platform/nexus_trel.hpp b/tests/nexus/platform/nexus_trel.hpp index 9dfe3222d..eb73add31 100644 --- a/tests/nexus/platform/nexus_trel.hpp +++ b/tests/nexus/platform/nexus_trel.hpp @@ -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); diff --git a/tests/nexus/test_full_network_reset.cpp b/tests/nexus/test_full_network_reset.cpp new file mode 100644 index 000000000..dbe9f555f --- /dev/null +++ b/tests/nexus/test_full_network_reset.cpp @@ -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 +#include + +#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().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().Up(); + SuccessOrQuit(node.Get().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; +}