From a5a5bc622a469ae27aa6e6ba3671cc10037bea56 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Mon, 20 Apr 2026 00:08:28 -0700 Subject: [PATCH] [nexus] migrate test_br_upgrade_router_role to nexus (#12921) This commit migrates the functionality of the Python-based certification test 'test_br_upgrade_router_role.py' to a new Nexus-based C++ test 'test_br_upgrade_router_role.cpp'. The test verifies that Border Routers (BRs) providing IP connectivity are eligible to request a router role upgrade even when the active router count already meets the 'router_upgrade_threshold'. Key test steps: - Set router upgrade threshold to 2. - Ensure three BRs remain in child role when 2 routers already exist. - Verify BRs upgrade to router role when they provide external routes or prefixes, up to the limit of 2 BR routers. - Verify that a third BR providing external routes remains a child. - Verify that removing a route from one BR router triggers the child BR to upgrade to a router. The Python test is removed as it is now covered by the Nexus test. --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/test_br_upgrade_router_role.cpp | 185 ++++++++++++++++++ .../test_br_upgrade_router_role.py | 158 --------------- 3 files changed, 186 insertions(+), 158 deletions(-) create mode 100644 tests/nexus/test_br_upgrade_router_role.cpp delete mode 100755 tests/scripts/thread-cert/test_br_upgrade_router_role.py diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 2322f0cd9..81836c6cd 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -384,6 +384,7 @@ ot_nexus_test(1_4_CS_TC_3 "cert;nexus") ot_nexus_test(inform_previous_parent_on_reattach "cert;nexus") # Misc tests +ot_nexus_test(br_upgrade_router_role "core;nexus") ot_nexus_test(border_admitter "core;nexus") ot_nexus_test(border_agent "core;nexus") ot_nexus_test(border_agent_tracker "core;nexus") diff --git a/tests/nexus/test_br_upgrade_router_role.cpp b/tests/nexus/test_br_upgrade_router_role.cpp new file mode 100644 index 000000000..aa5140798 --- /dev/null +++ b/tests/nexus/test_br_upgrade_router_role.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2024, 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 "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +/** + * Time to advance for a node to form a network and become leader. + */ +static constexpr uint32_t kFormNetworkTime = 13 * 1000; + +/** + * Time to advance for a node to join as a child and upgrade to a router. + */ +static constexpr uint32_t kAttachToRouterTime = 125 * 1000; + +/** + * Time to wait for BR to upgrade to router. + */ +static constexpr uint32_t kBrUpgradeTime = 15 * 1000; + +void TestBrUpgradeRouterRole(void) +{ + // This test verifies behavior of Border Routers (which provide IP connectivity) requesting router role within + // Thread mesh. + + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &router = nexus.CreateNode(); + Node &br1 = nexus.CreateNode(); + Node &br2 = nexus.CreateNode(); + Node &br3 = nexus.CreateNode(); + + leader.SetName("Leader"); + router.SetName("Router"); + br1.SetName("BR_1"); + br2.SetName("BR_2"); + br3.SetName("BR_3"); + + nexus.AdvanceTime(0); + + SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote)); + + Log("---------------------------------------------------------------------------------------"); + Log("Set the router upgrade threshold to 2 on all nodes."); + + leader.Get().SetRouterUpgradeThreshold(2); + router.Get().SetRouterUpgradeThreshold(2); + br1.Get().SetRouterUpgradeThreshold(2); + br2.Get().SetRouterUpgradeThreshold(2); + br3.Get().SetRouterUpgradeThreshold(2); + + Log("---------------------------------------------------------------------------------------"); + Log("Start the leader and router."); + + leader.Form(); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(leader.Get().IsLeader()); + + router.Join(leader); + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(router.Get().IsRouter()); + + Log("---------------------------------------------------------------------------------------"); + Log("Start all three BRs, we expect all to stay as child since there are already two"); + Log("routers in the Thread mesh."); + + br1.Join(leader); + br2.Join(leader); + br3.Join(leader); + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(br1.Get().IsChild()); + VerifyOrQuit(br2.Get().IsChild()); + VerifyOrQuit(br3.Get().IsChild()); + + Log("---------------------------------------------------------------------------------------"); + Log("Add an external route on `br1`, it should now try to become a router requesting"); + Log("with status BorderRouterRequest. Verify that leader allows it to become router."); + + { + NetworkData::ExternalRouteConfig routeConfig; + routeConfig.Clear(); + SuccessOrQuit(routeConfig.GetPrefix().FromString("2001:dead:beef:cafe::/64")); + routeConfig.mStable = true; + SuccessOrQuit(br1.Get().AddHasRoutePrefix(routeConfig)); + br1.Get().HandleServerDataUpdated(); + } + + nexus.AdvanceTime(kBrUpgradeTime); + VerifyOrQuit(br1.Get().IsRouter()); + + Log("---------------------------------------------------------------------------------------"); + Log("Add a prefix with default route on `br2`, it should also become a router."); + + { + NetworkData::OnMeshPrefixConfig prefixConfig; + prefixConfig.Clear(); + SuccessOrQuit(prefixConfig.GetPrefix().FromString("2001:dead:beef:2222::/64")); + prefixConfig.mSlaac = true; + prefixConfig.mPreferred = true; + prefixConfig.mOnMesh = true; + prefixConfig.mDefaultRoute = true; + prefixConfig.mStable = true; + SuccessOrQuit(br2.Get().AddOnMeshPrefix(prefixConfig)); + br2.Get().HandleServerDataUpdated(); + } + + nexus.AdvanceTime(kBrUpgradeTime); + VerifyOrQuit(br2.Get().IsRouter()); + + Log("---------------------------------------------------------------------------------------"); + Log("Add an external route on `br3`, it should not become a router since we already have"); + Log("two that requested router role upgrade as border router reason."); + + { + NetworkData::ExternalRouteConfig routeConfig; + routeConfig.Clear(); + SuccessOrQuit(routeConfig.GetPrefix().FromString("2001:dead:beef:cafe::/64")); + routeConfig.mStable = true; + SuccessOrQuit(br3.Get().AddHasRoutePrefix(routeConfig)); + br3.Get().HandleServerDataUpdated(); + } + + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(br3.Get().IsChild()); + + Log("---------------------------------------------------------------------------------------"); + Log("Remove the external route on `br1`. This should now trigger `br3` to request a"); + Log("router role upgrade since number of BRs acting as router in network data is now"); + Log("below the threshold of two."); + + { + Ip6::Prefix prefix; + SuccessOrQuit(prefix.FromString("2001:dead:beef:cafe::/64")); + SuccessOrQuit(br1.Get().RemoveHasRoutePrefix(prefix)); + br1.Get().HandleServerDataUpdated(); + } + + nexus.AdvanceTime(kBrUpgradeTime); + VerifyOrQuit(br1.Get().IsRouter()); + VerifyOrQuit(br3.Get().IsRouter()); + + nexus.SaveTestInfo("test_br_upgrade_router_role.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestBrUpgradeRouterRole(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_br_upgrade_router_role.py b/tests/scripts/thread-cert/test_br_upgrade_router_role.py deleted file mode 100755 index 91b1fa92d..000000000 --- a/tests/scripts/thread-cert/test_br_upgrade_router_role.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2022, 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. -# - -import ipaddress -import unittest - -import command -import config -import thread_cert - -# Test description: -# -# This test verifies behavior of Border Routers (which provide IP connectivity) requesting router role within Thread -# mesh. - -# Topology: -# -# 5 FTD nodes, all connected. -# -# Topology is expected to change during execution of test. -# - -LEADER = 1 -ROUTER = 2 -BR1 = 3 -BR2 = 4 -BR3 = 5 - - -class BrUpgradeRouterRole(thread_cert.TestCase): - USE_MESSAGE_FACTORY = False - SUPPORT_NCP = False - - TOPOLOGY = { - LEADER: { - 'name': 'leader', - 'mode': 'rdn', - }, - ROUTER: { - 'name': 'reader', - 'mode': 'rdn', - }, - BR1: { - 'name': 'br-1', - 'mode': 'rdn', - }, - BR2: { - 'name': 'br-2', - 'mode': 'rdn', - }, - BR3: { - 'name': 'br-3', - 'mode': 'rdn', - }, - } - - def test(self): - leader = self.nodes[LEADER] - router = self.nodes[ROUTER] - br1 = self.nodes[BR1] - br2 = self.nodes[BR2] - br3 = self.nodes[BR3] - - #------------------------------------------------------------------------------------- - # Set the router upgrade threshold to 2 on all nodes. - - for node in [leader, router, br1, br2, br3]: - node.set_router_upgrade_threshold(2) - - #------------------------------------------------------------------------------------- - # Start the leader and router. - - leader.start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(leader.get_state(), 'leader') - - router.start() - self.simulator.go(125) - self.assertEqual(router.get_state(), 'router') - - #------------------------------------------------------------------------------------- - # Start all three BRs, we expect all to stay as child since there are already two - # routers in the Thread mesh. - - br1.start() - br2.start() - br3.start() - self.simulator.go(125) - self.assertEqual(br1.get_state(), 'child') - self.assertEqual(br2.get_state(), 'child') - self.assertEqual(br3.get_state(), 'child') - - #------------------------------------------------------------------------------------- - # Add an external route on `br1`, it should now try to become a router requesting - # with status BorderRouterRequest. Verify that leader allows it to become router. - - br1.add_route('2001:dead:beef:cafe::/64', stable=True) - br1.register_netdata() - self.simulator.go(15) - self.assertEqual(br1.get_state(), 'router') - - #------------------------------------------------------------------------------------- - # Add a prefix with default route on `br2`, it should also become a router. - - br2.add_prefix('2001:dead:beef:2222::/64', 'paros') - br2.register_netdata() - self.simulator.go(15) - self.assertEqual(br2.get_state(), 'router') - - #------------------------------------------------------------------------------------- - # Add an external route on `br3`, it should not become a router since we already have - # two that requested router role upgrade as border router reason. - - br3.add_route('2001:dead:beef:cafe::/64', stable=True) - br3.register_netdata() - self.simulator.go(120) - self.assertEqual(br3.get_state(), 'child') - - #------------------------------------------------------------------------------------- - # Remove the external route on `br1`. This should now trigger `br3` to request a - # router role upgrade since number of BRs acting as router in network data is now - # below the threshold of two. - - br1.remove_route('2001:dead:beef:cafe::/64') - br1.register_netdata() - self.simulator.go(15) - self.assertEqual(br1.get_state(), 'router') - self.assertEqual(br3.get_state(), 'router') - - -if __name__ == '__main__': - unittest.main()