[bbr] add backbone router service (#4430)

- Introduce BACKBONE_ROUTER option for Backbone Router function.
- Implement Backbone Router service registration.
  - Add basic Backbone Router service.
  - Primary Backbone Router restores its Dataset when reattached after
    short reset, increases sequence number and re-register to Leader.
  - Add configurable jitter for Backbone Router service registration.
- Add Backbone Router service test.
This commit is contained in:
Rongli Sun
2020-03-26 08:54:23 -07:00
committed by GitHub
parent 6606f2befe
commit 315a27bf08
46 changed files with 2384 additions and 95 deletions
+159 -38
View File
@@ -43,11 +43,17 @@ import binascii
class Node:
def __init__(self, nodeid, is_mtd=False, simulator=None, version=None):
def __init__(self,
nodeid,
is_mtd=False,
simulator=None,
version=None,
is_bbr=False):
self.nodeid = nodeid
self.verbose = int(float(os.getenv('VERBOSE', 0)))
self.node_type = os.getenv('NODE_TYPE', 'sim')
self.env_version = os.getenv('THREAD_VERSION', '1.1')
self.is_bbr = is_bbr
self._initialized = False
if version is not None:
@@ -76,24 +82,45 @@ class Node:
def __init_sim(self, nodeid, mode):
""" Initialize a simulation node. """
if 'OT_CLI_PATH' in os.environ:
cmd = os.environ['OT_CLI_PATH']
elif (self.version == '1.1' and self.version != self.env_version):
# Default command if no match below, will be overridden if below conditions are met.
cmd = './ot-cli-%s' % (mode)
# If Thread version of node matches the testing environment version.
if self.version == self.env_version:
# Load Thread 1.2 BBR device when testing Thread 1.2 scenarios
# which requires device with Backbone functionality.
if self.version == '1.2' and self.is_bbr:
if 'OT_CLI_PATH_1_2_BBR' in os.environ:
cmd = os.environ['OT_CLI_PATH_1_2_BBR']
elif 'top_builddir_1_2_bbr' in os.environ:
srcdir = os.environ['top_builddir_1_2_bbr']
cmd = '%s/examples/apps/cli/ot-cli-%s' % (srcdir, mode)
# Load Thread device of the testing environment version (may be 1.1 or 1.2)
else:
if 'OT_CLI_PATH' in os.environ:
cmd = os.environ['OT_CLI_PATH']
elif 'top_builddir' in os.environ:
srcdir = os.environ['top_builddir']
cmd = '%s/examples/apps/cli/ot-cli-%s' % (srcdir, mode)
if 'RADIO_DEVICE' in os.environ:
cmd += ' -v %s' % os.environ['RADIO_DEVICE']
os.environ['NODE_ID'] = str(nodeid)
# Load Thread 1.1 node when testing Thread 1.2 scenarios for interoperability
elif self.version == '1.1':
# Posix app
if 'OT_CLI_PATH_1_1' in os.environ:
cmd = os.environ['OT_CLI_PATH_1_1']
elif ('top_builddir_1_1') in os.environ:
elif 'top_builddir_1_1' in os.environ:
srcdir = os.environ['top_builddir_1_1']
cmd = '%s/examples/apps/cli/ot-cli-%s' % (srcdir, mode)
elif ('top_builddir') in os.environ:
srcdir = os.environ['top_builddir']
cmd = '%s/examples/apps/cli/ot-cli-%s' % (srcdir, mode)
else:
cmd = '%s/ot-cli-%s' % (self.version, mode)
if 'RADIO_DEVICE' in os.environ:
cmd += ' -v %s' % os.environ['RADIO_DEVICE']
os.environ['NODE_ID'] = str(nodeid)
if 'RADIO_DEVICE_1_1' in os.environ:
cmd += ' -v %s' % os.environ['RADIO_DEVICE_1_1']
os.environ['NODE_ID'] = str(nodeid)
cmd += ' %d' % nodeid
print("%s" % cmd)
@@ -112,43 +139,69 @@ class Node:
def __init_ncp_sim(self, nodeid, mode):
""" Initialize an NCP simulation node. """
if 'RADIO_DEVICE' in os.environ:
args = ' %s' % os.environ['RADIO_DEVICE']
os.environ['NODE_ID'] = str(nodeid)
else:
args = ''
if 'OT_NCP_PATH' in os.environ:
cmd = 'spinel-cli.py -p "%s%s" -n' % (
os.environ['OT_NCP_PATH'],
args,
)
elif (self.version == '1.1' and self.version != self.env_version):
# Default command if no match below, will be overridden if below conditions are met.
cmd = 'spinel-cli.py -p ./ot-ncp-%s -n' % mode
# If Thread version of node matches the testing environment version.
if self.version == self.env_version:
if 'RADIO_DEVICE' in os.environ:
args = ' %s' % os.environ['RADIO_DEVICE']
os.environ['NODE_ID'] = str(nodeid)
else:
args = ''
# Load Thread 1.2 BBR device when testing Thread 1.2 scenarios
# which requires device with Backbone functionality.
if self.version == '1.2' and self.is_bbr:
if 'OT_NCP_PATH_1_2_BBR' in os.environ:
cmd = 'spinel-cli.py -p "%s%s" -n' % (
os.environ['OT_NCP_PATH_1_2_BBR'],
args,
)
elif 'top_builddir_1_2_bbr' in os.environ:
srcdir = os.environ['top_builddir_1_2_bbr']
cmd = '%s/examples/apps/ncp/ot-ncp-%s' % (srcdir, mode)
cmd = 'spinel-cli.py -p "%s%s" -n' % (
cmd,
args,
)
# Load Thread device of the testing environment version (may be 1.1 or 1.2).
else:
if 'OT_NCP_PATH' in os.environ:
cmd = 'spinel-cli.py -p "%s%s" -n' % (
os.environ['OT_NCP_PATH'],
args,
)
elif 'top_builddir' in os.environ:
srcdir = os.environ['top_builddir']
cmd = '%s/examples/apps/ncp/ot-ncp-%s' % (srcdir, mode)
cmd = 'spinel-cli.py -p "%s%s" -n' % (
cmd,
args,
)
# Load Thread 1.1 node when testing Thread 1.2 scenarios for interoperability.
elif self.version == '1.1':
if 'RADIO_DEVICE_1_1' in os.environ:
args = ' %s' % os.environ['RADIO_DEVICE_1_1']
os.environ['NODE_ID'] = str(nodeid)
else:
args = ''
if 'OT_NCP_PATH_1_1' in os.environ:
cmd = 'spinel-cli.py -p "%s%s" -n' % (
os.environ['OT_NCP_PATH_1_1'],
args,
)
elif ('top_builddir_1_1') in os.environ:
elif 'top_builddir_1_1' in os.environ:
srcdir = os.environ['top_builddir_1_1']
cmd = '%s/examples/apps/ncp/ot-ncp-%s' % (srcdir, mode)
cmd = 'spinel-cli.py -p "%s%s" -n' % (
cmd,
args,
)
elif ('top_builddir') in os.environ:
srcdir = os.environ['top_builddir']
cmd = '%s/examples/apps/ncp/ot-ncp-%s' % (srcdir, mode)
cmd = 'spinel-cli.py -p "%s%s" -n' % (
cmd,
args,
)
else:
cmd = 'spinel-cli.py -p "%s/ot-ncp-%s%s" -n' % (
self.version,
mode,
args,
)
cmd += ' %d' % nodeid
print("%s" % cmd)
@@ -388,6 +441,74 @@ class Node:
self.send_command(cmd)
self._expect('Done')
def get_bbr_registration_jitter(self):
self.send_command('bbr jitter')
return int(self._expect_result(r'\d+'))
def set_bbr_registration_jitter(self, jitter):
cmd = 'bbr jitter %d' % jitter
self.send_command(cmd)
self._expect('Done')
def enable_backbone_router(self):
cmd = 'bbr enable'
self.send_command(cmd)
self._expect('Done')
def disable_backbone_router(self):
cmd = 'bbr disable'
self.send_command(cmd)
self._expect('Done')
def register_backbone_router(self):
cmd = 'bbr register'
self.send_command(cmd)
self._expect('Done')
def get_backbone_router_state(self):
states = [r'Disabled', r'Primary', r'Secondary']
self.send_command('bbr state')
return self._expect_result(states)
def get_backbone_router(self):
cmd = 'bbr config'
self.send_command(cmd)
self._expect(r'(.*)Done')
g = self.pexpect.match.groups()
output = g[0].decode("utf-8")
lines = output.strip().split('\n')
lines = [l.strip() for l in lines]
ret = {}
for l in lines:
z = re.search(r'seqno:\s+([0-9]+)', l)
if z:
ret['seqno'] = int(z.groups()[0])
z = re.search(r'delay:\s+([0-9]+)', l)
if z:
ret['delay'] = int(z.groups()[0])
z = re.search(r'timeout:\s+([0-9]+)', l)
if z:
ret['timeout'] = int(z.groups()[0])
return ret
def set_backbone_router(self, seqno=None, reg_delay=None, mlr_timeout=None):
cmd = 'bbr config'
if seqno is not None:
cmd += ' seqno %d' % seqno
if reg_delay is not None:
cmd += ' delay %d' % reg_delay
if mlr_timeout is not None:
cmd += ' timeout %d' % mlr_timeout
self.send_command(cmd)
self._expect('Done')
def set_link_quality(self, addr, lqi):
cmd = 'macfilter rss add-lqi %s %s' % (addr, lqi)
self.send_command(cmd)
+2
View File
@@ -35,6 +35,7 @@ from node import Node
DEFAULT_PARAMS = {
'is_mtd': False,
'is_bbr': False,
'mode': 'rsdn',
'panid': 0xface,
'whitelist': None,
@@ -73,6 +74,7 @@ class TestCase(unittest.TestCase):
params['is_mtd'],
simulator=self.simulator,
version=params['version'],
is_bbr=params['is_bbr'],
)
self.nodes[i].set_panid(params['panid'])
self.nodes[i].set_mode(params['mode'])
@@ -0,0 +1,235 @@
#!/usr/bin/env python3
#
# Copyright (c) 2019, 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 unittest
import thread_cert
LEADER_1_1 = 1
BBR_1 = 2
BBR_2 = 3
WAIT_ATTACH = 5
WAIT_REDUNDANCE = 3
ROUTER_SELECTION_JITTER = 1
BBR_REGISTRATION_JITTER = 5
"""
Topology
LEADER_1_1 --- BBR_1
\ |
\ |
\ |
BBR_2
1) Bring up Leader_1_1 and then BBR_1, BBR_1 becomes Primary Backbone Router.
2) Reset BBR_1, if bring back soon, it could restore the Backbone Router Service
from the network, after increasing sequence number, it will reregister its
Backbone Router Service to the Leader and become Primary.
3) Reset BBR_1, if bring back after it is released in the network, BBR_1 will
choose a random sequence number, register its Backbone Router Service to
Leader and become Primary.
4) Configure BBR_2 with highest sequence number and explicitly trigger SRV_DATA.ntf.
BBR_2 would become Primary and BBR_1 would change to Secondary with sequence
number increased by 1.
5) Stop BBR_2, BBR_1 would become Primary after detecting there is no available
Backbone Router Service in Thread Network.
6) Bring back BBR_2, and it would become Secondary.
"""
class TestBackboneRouterService(thread_cert.TestCase):
topology = {
LEADER_1_1: {
'version': '1.1',
'whitelist': [BBR_1, BBR_2],
},
BBR_1: {
'version': '1.2',
'whitelist': [LEADER_1_1, BBR_2],
'is_bbr': True
},
BBR_2: {
'version': '1.2',
'whitelist': [LEADER_1_1, BBR_1],
'is_bbr': True
},
}
"""All nodes are created with default configurations"""
def test(self):
self.nodes[LEADER_1_1].start()
WAIT_TIME = WAIT_ATTACH
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[LEADER_1_1].get_state(), 'leader')
# 1) First Backbone Router would become the Primary.
self.nodes[BBR_1].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
self.nodes[BBR_1].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
self.nodes[BBR_1].set_backbone_router(seqno=1)
self.nodes[BBR_1].start()
WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_1].get_state(), 'router')
self.nodes[BBR_1].enable_backbone_router()
WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(),
'Primary')
# 2) Reset BBR_1 and bring it back soon.
# Verify that it restores Primary State with sequence number
# increased by 1.
self.nodes[BBR_1].reset()
self.nodes[BBR_1].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
self.nodes[BBR_1].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
self.nodes[BBR_1].enable_backbone_router()
self.nodes[BBR_1].start()
WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_1].get_state(), 'router')
WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(),
'Primary')
assert self.nodes[BBR_1].get_backbone_router()['seqno'] == 2
# 3) Reset BBR_1 and bring it back after its original router id is released
# 200s (100s MaxNeighborAge + 90s InfiniteCost + 10s redundance)
# Verify it becomes Primary again.
# Note: To ensure test in next step, here Step 3) will repeat until
# the random sequence number is not the highest value 255.
while True:
self.nodes[BBR_1].reset()
WAIT_TIME = 200
self.simulator.go(WAIT_TIME)
self.nodes[BBR_1].set_router_selection_jitter(
ROUTER_SELECTION_JITTER)
self.nodes[BBR_1].set_bbr_registration_jitter(
BBR_REGISTRATION_JITTER)
self.nodes[BBR_1].enable_backbone_router()
self.nodes[BBR_1].start()
WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_1].get_state(), 'router')
WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(),
'Primary')
BBR_1_SEQNO = self.nodes[BBR_1].get_backbone_router()['seqno']
if (BBR_1_SEQNO != 255):
break
#4) Configure BBR_2 with highest sequence number (255) and
# explicitly trigger SRV_DATA.ntf.
# Verify BBR_2 would become Primary and BBR_1 would change to
# Secondary with sequence number increased by 1.
# Bring up BBR_2, it becomes Router with backbone function disabled
# by default.
self.nodes[BBR_2].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
self.nodes[BBR_2].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
self.nodes[BBR_2].start()
WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_2].get_state(), 'router')
WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(),
'Disabled')
# Enable Backbone function, it will stay at Secondary state as
# there is Primary Backbone Router already.
self.nodes[BBR_2].enable_backbone_router()
self.nodes[BBR_2].set_backbone_router(seqno=255)
WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(),
'Secondary')
# Check no SRV_DATA.ntf.
messages = self.simulator.get_messages_sent_by(BBR_2)
msg = messages.next_coap_message('0.02', '/a/sd', False)
assert (
msg is None
), "Error: %d sent unexpected SRV_DATA.ntf when there is PBbr already"
# Flush relative message queue.
self.flush_nodes([BBR_1])
# BBR_2 registers SRV_DATA.ntf explicitly.
self.nodes[BBR_2].register_backbone_router()
WAIT_TIME = WAIT_REDUNDANCE
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(),
'Primary')
# Verify BBR_1 becomes Secondary and sends SRV_DATA.ntf to deregister
# its service.
messages = self.simulator.get_messages_sent_by(BBR_1)
messages.next_coap_message('0.02', '/a/sd', True)
self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(),
'Secondary')
# Verify Sequence number increases when become Secondary from Primary.
assert self.nodes[BBR_1].get_backbone_router()['seqno'] == (
BBR_1_SEQNO + 1)
# 5) Stop BBR_2, BBR_1 becomes Primary after detecting there is no
# available Backbone Router Service.
self.nodes[BBR_2].reset()
self.nodes[LEADER_1_1].release_router_id(
self.nodes[BBR_2].get_router_id())
# Wait for the dissemination of Network Data without backbone router service
self.simulator.go(10)
# BBR_1 becomes Primary.
self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(),
'Primary')
messages = self.simulator.get_messages_sent_by(BBR_1)
messages.next_coap_message('0.02', '/a/sd', True)
# 6) Bring back BBR_2.
# Verify that BBR_2 stays at Secondary.
self.nodes[BBR_2].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
self.nodes[BBR_2].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
self.nodes[BBR_2].enable_backbone_router()
self.nodes[BBR_2].interface_up()
self.nodes[BBR_2].thread_start()
WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_2].get_state(), 'router')
WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(),
'Secondary')
if __name__ == '__main__':
unittest.main()