mirror of
https://github.com/espressif/openthread.git
synced 2026-07-25 13:34:06 +00:00
[test] support multiple backbone interfaces in otbr docker test (#10550)
In previous otbr docker tests, when creating docker containers, all the
containers(otbr nodes) are connected to the same docker network bridge `backbone0`
(when the env PORT_OFFSET is not set or set to 0), this means all the
otbr nodes are connected to the same infrastructures.
This commit adds support to enable user to config otbr instance
infrastructures seperately in `TOPOLOGY` when defining test cases, this
provides flexibility to run multi-ail related test cases.
The format to define backbone interface per node is:
```
class NewTestCase(thread_cert.TestCase):
...
BR = 1
...
TOPOLOGY = {
BR: {
...
'is_otbr': True,
'backbone': <backbone-name>,
...
}
...
}
...
```
`'backbone': <backbone-name>` is optional, when it's not given when
defining a otbr node, the backbone is default as
`BACKBONE_DOCKER_NETWORK_NAME`. The `<backbone-name>` is suggested to be
defined as `backbone[0-9]` to make it more easy to read and understand.
This commit also adds test_multi_ail.py as an example test case to use
this new method, this test case checks the two otbr nodes are connected
to the different infra and are in the same Thread mesh network.
This commit is contained in:
+101
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import unittest
|
||||
import ipaddress
|
||||
|
||||
import config
|
||||
import thread_cert
|
||||
|
||||
IPV4_CIDR_ADDR_CMD = f'ip addr show {config.BACKBONE_IFNAME} | grep -w inet | grep -Eo "[0-9.]+/[0-9]+"'
|
||||
|
||||
|
||||
class TwoBorderRoutersOnTwoInfrastructures(thread_cert.TestCase):
|
||||
"""
|
||||
Test that two border routers on different infrastructures can ping each other via Thread interface.
|
||||
|
||||
Topology:
|
||||
|
||||
-------(backbone0)-------- | ---------(backbone1)-------
|
||||
| |
|
||||
BR1 (Leader) .............. BR2 (Router)
|
||||
|
||||
"""
|
||||
USE_MESSAGE_FACTORY = False
|
||||
|
||||
BR1 = 1
|
||||
BR2 = 2
|
||||
|
||||
TOPOLOGY = {
|
||||
BR1: {
|
||||
'name': 'BR_1',
|
||||
'backbone_network': 'backbone0',
|
||||
'allowlist': [BR2],
|
||||
'is_otbr': True,
|
||||
'version': '1.3',
|
||||
},
|
||||
BR2: {
|
||||
'name': 'BR_2',
|
||||
'backbone_network': 'backbone1',
|
||||
'allowlist': [BR1],
|
||||
'is_otbr': True,
|
||||
'version': '1.3',
|
||||
}
|
||||
}
|
||||
|
||||
def test(self):
|
||||
br1 = self.nodes[self.BR1]
|
||||
br2 = self.nodes[self.BR2]
|
||||
|
||||
# start nodes
|
||||
br1.start()
|
||||
self.simulator.go(2)
|
||||
br2.start()
|
||||
self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY)
|
||||
|
||||
# check roles
|
||||
self.assertEqual('leader', br1.get_state())
|
||||
self.assertEqual('router', br2.get_state())
|
||||
|
||||
# check two BRs AIL are in different subnets
|
||||
br1_infra_ip_addr = br1.bash(IPV4_CIDR_ADDR_CMD)
|
||||
br2_infra_ip_addr = br2.bash(IPV4_CIDR_ADDR_CMD)
|
||||
|
||||
self.assertEqual(len(br1_infra_ip_addr), 1)
|
||||
self.assertEqual(len(br2_infra_ip_addr), 1)
|
||||
self.assertNotEqual(ipaddress.ip_network(br1_infra_ip_addr[0].strip(), strict=False),
|
||||
ipaddress.ip_network(br2_infra_ip_addr[0].strip(), strict=False))
|
||||
|
||||
# ping each other
|
||||
self.assertTrue(br1.ping(br2.get_ip6_address(config.ADDRESS_TYPE.ML_EID)))
|
||||
self.assertTrue(br2.ping(br1.get_ip6_address(config.ADDRESS_TYPE.ML_EID)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
@@ -64,8 +64,10 @@ DOMAIN_PREFIX_REGEX_PATTERN = '^fd00:7d03:7d03:7d03:'
|
||||
DOMAIN_PREFIX_ALTER = 'fd00:7d04:7d04:7d04::/64'
|
||||
|
||||
PORT_OFFSET = int(os.getenv('PORT_OFFSET', '0'))
|
||||
BACKBONE_PREFIX = f'{0x9100 + PORT_OFFSET:04x}::/64'
|
||||
BACKBONE_PREFIX_REGEX_PATTERN = f'^{0x9100 + PORT_OFFSET:04x}:'
|
||||
BACKBONE_IPV6_ADDR_START_BASE = 0x9100
|
||||
BACKBONE_IPV6_ADDR_START = BACKBONE_IPV6_ADDR_START_BASE + PORT_OFFSET
|
||||
BACKBONE_PREFIX = f'{BACKBONE_IPV6_ADDR_START:04x}::/64'
|
||||
BACKBONE_PREFIX_REGEX_PATTERN = f'^{BACKBONE_IPV6_ADDR_START:04x}:'
|
||||
BACKBONE_DOCKER_NETWORK_NAME = f'backbone{PORT_OFFSET}'
|
||||
BACKBONE_IFNAME = 'eth0'
|
||||
THREAD_IFNAME = 'wpan0'
|
||||
|
||||
@@ -64,8 +64,9 @@ class OtbrDocker:
|
||||
_docker_proc = None
|
||||
_border_routing_counters = None
|
||||
|
||||
def __init__(self, nodeid: int, **kwargs):
|
||||
def __init__(self, nodeid: int, backbone_network: str, **kwargs):
|
||||
self.verbose = int(float(os.getenv('VERBOSE', 0)))
|
||||
self.backbone_network = backbone_network
|
||||
try:
|
||||
self._docker_name = config.OTBR_DOCKER_NAME_PREFIX + str(nodeid)
|
||||
self._prepare_ot_rcp_sim(nodeid)
|
||||
@@ -121,7 +122,7 @@ class OtbrDocker:
|
||||
'--name',
|
||||
self._docker_name,
|
||||
'--network',
|
||||
config.BACKBONE_DOCKER_NETWORK_NAME,
|
||||
self.backbone_network,
|
||||
] + dns + [
|
||||
'-i',
|
||||
'--sysctl',
|
||||
@@ -151,7 +152,7 @@ class OtbrDocker:
|
||||
try:
|
||||
subprocess.check_call(f'docker exec -i {self._docker_name} ot-ctl state', shell=True)
|
||||
launch_ok = True
|
||||
logging.info("OTBR Docker %s Is Ready!", self._docker_name)
|
||||
logging.info("OTBR Docker %s on %s Is Ready!", self._docker_name, self.backbone_network)
|
||||
break
|
||||
except subprocess.CalledProcessError:
|
||||
time.sleep(5)
|
||||
|
||||
@@ -162,14 +162,13 @@ class TestCase(NcpSupportMixin, unittest.TestCase):
|
||||
else:
|
||||
nodeclass = Node
|
||||
|
||||
node = nodeclass(
|
||||
i,
|
||||
is_mtd=params['is_mtd'],
|
||||
simulator=self.simulator,
|
||||
name=params.get('name'),
|
||||
version=params['version'],
|
||||
is_bbr=params['is_bbr'],
|
||||
)
|
||||
node = nodeclass(i,
|
||||
is_mtd=params['is_mtd'],
|
||||
simulator=self.simulator,
|
||||
name=params.get('name'),
|
||||
version=params['version'],
|
||||
is_bbr=params['is_bbr'],
|
||||
backbone_network=params['backbone_network'])
|
||||
if 'boot_delay' in params:
|
||||
self.simulator.go(params['boot_delay'])
|
||||
|
||||
@@ -501,6 +500,9 @@ class TestCase(NcpSupportMixin, unittest.TestCase):
|
||||
assert params.get('version', '') == '', params
|
||||
params['version'] = ''
|
||||
|
||||
# set default backbone network for bbr/otbr/host if not specified
|
||||
params.setdefault('backbone_network', config.BACKBONE_DOCKER_NETWORK_NAME)
|
||||
|
||||
# use 1.3 node for 1.2 tests
|
||||
if params.get('version') == '1.2':
|
||||
params['version'] = '1.3'
|
||||
@@ -524,10 +526,30 @@ class TestCase(NcpSupportMixin, unittest.TestCase):
|
||||
return False
|
||||
|
||||
def _prepare_backbone_network(self):
|
||||
network_name = config.BACKBONE_DOCKER_NETWORK_NAME
|
||||
self.assure_run_ok(
|
||||
f'docker network create --driver bridge --ipv6 --subnet {config.BACKBONE_PREFIX} -o "com.docker.network.bridge.name"="{network_name}" {network_name} || true',
|
||||
shell=True)
|
||||
"""
|
||||
Prepares one or multiple backbone network(s).
|
||||
|
||||
This method creates the backbone network based on the `backbone` values defined in the TOPOLOGY in each test.
|
||||
If no backbone values are defined, it sets the default backbone network as BACKBONE_DOCKER_NETWORK_NAME
|
||||
from the config module.
|
||||
"""
|
||||
# Use backbone_set to store all the backbone values in TOPOLOGY
|
||||
backbone_set = set()
|
||||
for node in self.TOPOLOGY:
|
||||
backbone = self.TOPOLOGY[node].get('backbone_network')
|
||||
if backbone:
|
||||
backbone_set.add(backbone)
|
||||
|
||||
# Set default backbone network if backbone_set is empty
|
||||
if not backbone_set:
|
||||
backbone_set.add(config.BACKBONE_DOCKER_NETWORK_NAME)
|
||||
|
||||
# Iterate over the backbone_set and create backbone network(s)
|
||||
for offset, backbone in enumerate(backbone_set, start=PORT_OFFSET):
|
||||
backbone_prefix = f'{config.BACKBONE_IPV6_ADDR_START_BASE + offset:04x}::/64'
|
||||
self.assure_run_ok(
|
||||
f'docker network create --driver bridge --ipv6 --subnet {backbone_prefix} -o "com.docker.network.bridge.name"="{backbone}" {backbone} || true',
|
||||
shell=True)
|
||||
|
||||
def _remove_backbone_network(self):
|
||||
network_name = config.BACKBONE_DOCKER_NETWORK_NAME
|
||||
|
||||
Reference in New Issue
Block a user