From 139b2da4085955d1659f61a89178b61c584d5c23 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 11 Jun 2018 09:19:50 -0700 Subject: [PATCH] [toranj] add test-case for child table and child recovery (#2776) --- tests/toranj/start.sh | 1 + tests/toranj/test-011-child-table.py | 113 ++++++++++++++++++++++++++ tests/toranj/wpan.py | 116 ++++++++++++++++++++------- 3 files changed, 199 insertions(+), 31 deletions(-) create mode 100644 tests/toranj/test-011-child-table.py diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index b7dd78944..fe729c84c 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -123,6 +123,7 @@ run test-007-traffic-router-sleepy.py run test-008-permit-join.py run test-009-insecure-traffic-join.py run test-010-on-mesh-prefix-config-gateway.py +run test-011-child-table.py run test-100-mcu-power-state.py run test-600-channel-manager-properties.py run test-601-channel-manager-channel-change.py diff --git a/tests/toranj/test-011-child-table.py b/tests/toranj/test-011-child-table.py new file mode 100644 index 000000000..c1a45aba6 --- /dev/null +++ b/tests/toranj/test-011-child-table.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018, 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 time +import wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: Child-table and child recovery +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 4 +wpan.Node.set_time_speedup_factor(speedup) + +NUM_CHILDREN = 7 + +router = wpan.Node() + +children = [] +for i in range(NUM_CHILDREN): + children.append(wpan.Node()) + +all_nodes = [router] + children + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() +for node in all_nodes: + node.set(wpan.WPAN_OT_LOG_LEVEL, '0') + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +router.form('child-table') +for child in children: + child.join_node(router, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE) + child.set(wpan.WPAN_POLL_INTERVAL, '300') + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +# Get the child table and verify all children are in the table. + +child_table = wpan.parse_child_table_result(router.get(wpan.WPAN_THREAD_CHILD_TABLE)) + +verify(len(child_table) == len(children)) + +for child in children: + ext_addr = child.get(wpan.WPAN_EXT_ADDRESS)[1:-1] + for entry in child_table: + if entry.ext_address == ext_addr: + break; + else: + print 'Failed to find a child entry for extended address {} in table'.format(ext_addr) + exit(1) + + verify(int(entry.rloc16, 16) == int(child.get(wpan.WPAN_THREAD_RLOC16), 16)) + verify(int(entry.timeout, 0) == 120) + verify(entry.is_rx_on_when_idle() == False) + verify(entry.is_ffd() == False) + + +# Reset router and ensure all children are restored. +router.reset() + +start_time = time.time() +wait_time = 5 +while time.time() - start_time < wait_time: + time.sleep(0.25) + child_table = wpan.parse_child_table_result(router.get(wpan.WPAN_THREAD_CHILD_TABLE)) + if (len(child_table) == len(children)): + break +else: + print 'Took too long for node to recover after reset ({}>{} sec)'.format(time.time() - start_time, wait_time) + exit(1) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/wpan.py b/tests/toranj/wpan.py index 7ace9ef04..3f3fceb2f 100644 --- a/tests/toranj/wpan.py +++ b/tests/toranj/wpan.py @@ -63,36 +63,36 @@ WPAN_IP6_MESH_LOCAL_PREFIX = "IPv6:MeshLocalPrefix" WPAN_IP6_ALL_ADDRESSES = "IPv6:AllAddresses" WPAN_IP6_MULTICAST_ADDRESSES = "IPv6:MulticastAddresses" -WPAN_THREAD_RLOC16 = "Thread:RLOC16" -WPAN_THREAD_ROUTER_ID = "Thread:RouterID" -WPAN_THREAD_LEADER_ADDRESS = "Thread:Leader:Address" -WPAN_THREAD_LEADER_ROUTER_ID = "Thread:Leader:RouterID" -WPAN_THREAD_LEADER_WEIGHT = "Thread:Leader:Weight" -WPAN_THREAD_LEADER_LOCAL_WEIGHT = "Thread:Leader:LocalWeight" -WPAN_THREAD_LEADER_NETWORK_DATA = "Thread:Leader:NetworkData" -WPAN_THREAD_STABLE_LEADER_NETWORK_DATA = "Thread:Leader:StableNetworkData" -WPAN_THREAD_NETWORK_DATA = "Thread:NetworkData" -WPAN_THREAD_CHILD_TABLE = "Thread:ChildTable" -WPAN_THREAD_CHILD_TABLE_ASVALMAP = "Thread:ChildTable:AsValMap" -WPAN_THREAD_CHILD_TABLE_ADDRESSES = "Thread:ChildTable:Addresses" -WPAN_THREAD_NEIGHBOR_TABLE = "Thread:NeighborTable" -WPAN_THREAD_NEIGHBOR_TABLE_ASVALMAP = "Thread:NeighborTable:AsValMap" -WPAN_THREAD_ROUTER_TABLE = "Thread:RouterTable" -WPAN_THREAD_ROUTER_TABLE_ASVALMAP = "Thread:RouterTable:AsValMap" -WPAN_THREAD_NETWORK_DATA_VERSION = "Thread:NetworkDataVersion" -WPAN_THREAD_STABLE_NETWORK_DATA = "Thread:StableNetworkData" -WPAN_THREAD_STABLE_NETWORK_DATA_VERSION = "Thread:StableNetworkDataVersion" -WPAN_THREAD_PREFERRED_ROUTER_ID = "Thread:PreferredRouterID" -WPAN_THREAD_COMMISSIONER_ENABLED = "Thread:Commissioner:Enabled" -WPAN_THREAD_DEVICE_MODE = "Thread:DeviceMode" -WPAN_THREAD_OFF_MESH_ROUTES = "Thread:OffMeshRoutes" -WPAN_THREAD_ON_MESH_PREFIXES = "Thread:OnMeshPrefixes" -WPAN_THREAD_ROUTER_ROLE_ENABLED = "Thread:RouterRole:Enabled" -WPAN_THREAD_CONFIG_FILTER_RLOC_ADDRESSES = "Thread:Config:FilterRLOCAddresses" -WPAN_THREAD_ACTIVE_DATASET = "Thread:ActiveDataset" -WPAN_THREAD_ACTIVE_DATASET_ASVALMAP = "Thread:ActiveDataset:AsValMap" -WPAN_THREAD_PENDING_DATASET = "Thread:PendingDataset" -WPAN_THREAD_PENDING_DATASET_ASVALMAP = "Thread:PendingDataset:AsValMap" +WPAN_THREAD_RLOC16 = "Thread:RLOC16" +WPAN_THREAD_ROUTER_ID = "Thread:RouterID" +WPAN_THREAD_LEADER_ADDRESS = "Thread:Leader:Address" +WPAN_THREAD_LEADER_ROUTER_ID = "Thread:Leader:RouterID" +WPAN_THREAD_LEADER_WEIGHT = "Thread:Leader:Weight" +WPAN_THREAD_LEADER_LOCAL_WEIGHT = "Thread:Leader:LocalWeight" +WPAN_THREAD_LEADER_NETWORK_DATA = "Thread:Leader:NetworkData" +WPAN_THREAD_STABLE_LEADER_NETWORK_DATA = "Thread:Leader:StableNetworkData" +WPAN_THREAD_NETWORK_DATA = "Thread:NetworkData" +WPAN_THREAD_CHILD_TABLE = "Thread:ChildTable" +WPAN_THREAD_CHILD_TABLE_ASVALMAP = "Thread:ChildTable:AsValMap" +WPAN_THREAD_CHILD_TABLE_ADDRESSES = "Thread:ChildTable:Addresses" +WPAN_THREAD_NEIGHBOR_TABLE = "Thread:NeighborTable" +WPAN_THREAD_NEIGHBOR_TABLE_ASVALMAP = "Thread:NeighborTable:AsValMap" +WPAN_THREAD_ROUTER_TABLE = "Thread:RouterTable" +WPAN_THREAD_ROUTER_TABLE_ASVALMAP = "Thread:RouterTable:AsValMap" +WPAN_THREAD_NETWORK_DATA_VERSION = "Thread:NetworkDataVersion" +WPAN_THREAD_STABLE_NETWORK_DATA = "Thread:StableNetworkData" +WPAN_THREAD_STABLE_NETWORK_DATA_VERSION = "Thread:StableNetworkDataVersion" +WPAN_THREAD_PREFERRED_ROUTER_ID = "Thread:PreferredRouterID" +WPAN_THREAD_COMMISSIONER_ENABLED = "Thread:Commissioner:Enabled" +WPAN_THREAD_DEVICE_MODE = "Thread:DeviceMode" +WPAN_THREAD_OFF_MESH_ROUTES = "Thread:OffMeshRoutes" +WPAN_THREAD_ON_MESH_PREFIXES = "Thread:OnMeshPrefixes" +WPAN_THREAD_ROUTER_ROLE_ENABLED = "Thread:RouterRole:Enabled" +WPAN_THREAD_CONFIG_FILTER_RLOC_ADDRESSES = "Thread:Config:FilterRLOCAddresses" +WPAN_THREAD_ACTIVE_DATASET = "Thread:ActiveDataset" +WPAN_THREAD_ACTIVE_DATASET_ASVALMAP = "Thread:ActiveDataset:AsValMap" +WPAN_THREAD_PENDING_DATASET = "Thread:PendingDataset" +WPAN_THREAD_PENDING_DATASET_ASVALMAP = "Thread:PendingDataset:AsValMap" WPAN_OT_LOG_LEVEL = "OpenThread:LogLevel" WPAN_OT_STEERING_DATA_ADDRESS = "OpenThread:SteeringData:Address" @@ -755,6 +755,7 @@ def verify(condition): #----------------------------------------------------------------------------------------------------------------------- # Parsing `wpanctl` output +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class ScanResult(object): """ This object encapsulates a scan result (active/discover/energy scan)""" @@ -850,6 +851,7 @@ def parse_list(list_string): # return [line[2:-1].split()[0] for line in list_string.split('\n')[1:-1]] +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class OnMeshPrefix(object): """ This object encapsulates an on-mesh prefix""" @@ -920,4 +922,56 @@ class OnMeshPrefix(object): def parse_on_mesh_prefix_result(on_mesh_prefix_list): """ Parses on-mesh prefix list string and returns an array of `OnMeshPrefix` objects""" - return [ OnMeshPrefix(item) for item in on_mesh_prefix_list.split('\n')[1:-1] ] \ No newline at end of file + return [ OnMeshPrefix(item) for item in on_mesh_prefix_list.split('\n')[1:-1] ] + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class ChildEntry(object): + """ This object encapsulates an child entry""" + + def __init__(self, text): + + # Example of expected text: + # + # `\t"E24C5F67F4B8CBB9, RLOC16:d402, NetDataVer:175, LQIn:3, AveRssi:-20, LastRssi:-20, Timeout:120, Age:0, ` + # `RxOnIdle:no, FFD:no, SecDataReq:yes, FullNetData:yes"` + # + + # We get rid of the first two chars `\t"' and last char '"', split the rest using whitespace as seperator. + # Then remove any ',' at end of items in the list. + items = [item[:-1] if item[-1] ==',' else item for item in text[2:-1].split()] + + # First item in the extended address + self._ext_address = items[0] + + # Convert the rest into a dictionary by splitting using ':' as seperator + dict = {item.split(':')[0] : item.split(':')[1] for item in items[1:]} + + self._rloc16 = dict['RLOC16'] + self._timeout = dict['Timeout'] + self._rx_on_idle = (dict['RxOnIdle'] == 'yes') + self._ffd = (dict['FFD'] == 'yes') + + @property + def ext_address(self): + return self._ext_address + + @property + def rloc16(self): + return self._rloc16 + + @property + def timeout(self): + return self._timeout + + def is_rx_on_when_idle(self): + return self._rx_on_idle + + def is_ffd(self): + return self._ffd + + def __repr__(self): + return 'ChildEntry({})'.format(self.__dict__) + +def parse_child_table_result(child_table_list): + """ Parses child table list string and returns an array of `ChildEntry` objects""" + return [ ChildEntry(item) for item in child_table_list.split('\n')[1:-1] ]