[harness-simulation] add multiple-version support (#8075)

This commit adds multiple-version support, and uses a unified
configuration file to specify all the parameters.
This commit is contained in:
Jiachen Dong
2022-09-09 13:32:17 -07:00
committed by GitHub
parent 2a27a15c1a
commit 4311d1931a
11 changed files with 381 additions and 275 deletions
+16 -40
View File
@@ -10,33 +10,19 @@ Platform developers should modify the THCI implementation and/or the SI implemen
## POSIX Environment Setup
1. Build OpenThread to generate standalone OpenThread simulation `ot-cli-ftd`. For example, to run OpenThread 1.2 test cases, run the following command in the top directory of OpenThread:
1. Open the JSON format configuration file `tools/harness-simulation/posix/simulation.conf`:
```bash
$ CFLAGS='-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8' \
CXXFLAGS='-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8' \
script/cmake-build simulation \
-DOT_THREAD_VERSION=1.2 \
-DOT_DUA=ON \
-DOT_MLR=ON \
-DOT_COMMISSIONER=ON \
-DOT_CSL_RECEIVER=ON \
-DOT_SIMULATION_MAX_NETWORK_SIZE=64
```
- Edit the value of `ot_path` to the absolute path where the top directory of the OpenThread repository is located. For example, change the value of `ot_path` to `/home/<username>/repo/openthread`.
- For each entry in `ot_build.ot`, update the value of `number` to be the number of OT FTD simulations needed with the corresponding version.
- For each entry in `ot_build.otbr`, update the value of `number` to be the number of OTBR simulations needed with the corresponding version.
- The numbers above can be adjusted according to the requirement of test cases.
- Edit the value of `ssh.username` to the username to be used for connecting to the remote POSIX environment.
- Edit the value of `ssh.password` to the password corresponding to the username above.
- Edit the value of `discovery_ifname` to the network interface that the Harness will connect to.
Then `ot-cli-ftd` is built in the directory `build/simulation/examples/apps/cli/`.
Note that it may be time-consuming to build all versions of `ot-cli-ftd`s and OTBR Docker images especially on devices such as Raspberry Pis.
2. Open the configuration file `config.py`:
- Edit the value of `OT_PATH` to the absolute path where the top directory of the OpenThread repository is located. For example, change the value of `OT_PATH` to `/home/<username>/repo/openthread`.
3. Run the `build_docker_image.sh` with the environment variable `OT_PATH` set properly. `OT_PATH` should be the same as that in the previous step. For example run the following command:
```bash
$ OT_PATH=~/repo/openthread ./build_docker_image.sh
```
4. Run the installation script.
2. Run the installation script.
```bash
$ tools/harness-simulation/posix/install.sh
@@ -44,33 +30,23 @@ Platform developers should modify the THCI implementation and/or the SI implemen
## Test Harness Environment Setup
1. Double click the file `harness\install.bat` on the machine which installed Harness.
1. Copy the directory `tools/harness-simulation` from the POSIX machine to the Windows machine, and then switch to that directory.
2. Check the configuration file `C:\GRL\Thread1.2\Thread_Harness\simulation\config.py`
- Edit the value of `REMOTE_USERNAME` to the username expected to connect to on the remote POSIX environment.
- Edit the value of `REMOTE_PASSWORD` to the password corresponding to the username above.
- Edit the value of `REMOTE_OT_PATH` to the absolute path where the top directory of the OpenThread repository is located.
2. Double click the file `harness\install.bat` on Windows.
3. Add the additional simulation device information in `harness\Web\data\deviceInputFields.xml` to `C:\GRL\Thread1.2\Web\data\deviceInputFields.xml`.
## Run Test Harness on Simulation
1. On POSIX machine, change directory to the top of OpenThread repository, and run the following commands.
1. On the POSIX machine, change directory to the top of the OpenThread repository, and run the following commands.
```bash
$ cd tools/harness-simulation/posix
$ python3 launch_testbed.py \
--interface=eth0 \
--ot=6 \
--otbr=4 \
--sniffer=2
$ ./launch_testbed.py -c simulation.conf
```
This example starts 6 OT FTD simulations, 4 OTBR simulations, and 2 sniffer simulations and can be discovered on `eth0`.
This example starts several OT FTD simulations, OTBR simulations, and sniffer simulations and can be discovered on `eth0`. The number of each type of simulation is specified in the configuration file `simulation.conf`.
The arguments can be adjusted according to the requirement of test cases.
2. Run Test Harness. The information field of the device is encoded as `<node_id>@<ip_addr>` for FTDs and `otbr_<node_id>@<ip_addr>` for BRs. Choose the proper device as the DUT accordingly.
2. Run the Test Harness. The information field of the device is encoded as `<tag>_<node_id>@<ip_addr>`. Choose the desired device as the DUT.
3. Select one or more test cases to start the test.
@@ -39,12 +39,15 @@ import pipes
import sys
import time
from THCI.IThci import IThci
from THCI.OpenThread import watched
from THCI.OpenThread_BR import OpenThread_BR
from simulation.config import (REMOTE_USERNAME, REMOTE_PASSWORD, REMOTE_PORT)
from simulation.config import load_config
logging.getLogger('paramiko').setLevel(logging.WARNING)
config = load_config()
class SSHHandle(object):
# Unit: second
@@ -109,11 +112,8 @@ class SSHHandle(object):
class OpenThread_BR_Sim(OpenThread_BR):
def _getHandle(self):
assert self.connectType == 'ip'
assert '@' in self.telnetIp
self.log('SSH connecting ...')
docker_name, ssh_ip = self.telnetIp.split('@')
return SSHHandle(ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword, docker_name)
return SSHHandle(self.ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword, self.docker_name)
@watched
def _parseConnectionParams(self, params):
@@ -121,15 +121,19 @@ class OpenThread_BR_Sim(OpenThread_BR):
if '@' not in discovery_add:
raise ValueError('%r in the field `add` is invalid' % discovery_add)
docker_name, ssh_ip = discovery_add.split('@')
self.docker_name, self.ssh_ip = discovery_add.split('@')
self.tag, self.node_id = self.docker_name.split('_')
# Let it crash if it is an invalid IP address
ipaddress.ip_address(ssh_ip)
ipaddress.ip_address(self.ssh_ip)
self.connectType = 'ip'
self.telnetIp = self.port = discovery_add
self.telnetPort = REMOTE_PORT
self.telnetUsername = REMOTE_USERNAME
self.telnetPassword = REMOTE_PASSWORD
global config
ssh = config['ssh']
self.telnetPort = ssh['port']
self.telnetUsername = ssh['username']
self.telnetPassword = ssh['password']
self.extraParams = {
'cmd-start-otbr-agent': 'service otbr-agent start',
@@ -137,3 +141,6 @@ class OpenThread_BR_Sim(OpenThread_BR):
'cmd-restart-otbr-agent': 'service otbr-agent restart',
'cmd-restart-radvd': 'service radvd stop; service radvd start',
}
assert issubclass(OpenThread_BR_Sim, IThci)
@@ -38,14 +38,12 @@ import socket
import time
import win32api
from simulation.config import load_config
from THCI.IThci import IThci
from THCI.OpenThread import OpenThreadTHCI, watched
from simulation.config import (
REMOTE_USERNAME,
REMOTE_PASSWORD,
REMOTE_PORT,
REMOTE_OT_PATH,
)
config = load_config()
ot_subpath = {item['tag']: item['subpath'] for item in config['ot_build']['ot']}
class SSHHandle(object):
@@ -133,21 +131,15 @@ class SSHHandle(object):
class OpenThread_Sim(OpenThreadTHCI, IThci):
__handle = None
# Do not use `os.path.join` as it uses backslash as the separator on Windows
device = REMOTE_OT_PATH + '/build/simulation/examples/apps/cli/ot-cli-ftd'
@watched
def _connect(self):
self.__lines = []
# Only actually connect once.
if self.__handle is None:
assert self.connectType == 'ip'
assert '@' in self.telnetIp
self.log('SSH connecting ...')
node_id, ssh_ip = self.telnetIp.split('@')
self.__handle = SSHHandle(ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword, self.device,
node_id)
self.__handle = SSHHandle(self.ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword,
self.device, self.node_id)
self.log('connected to %s successfully', self.telnetIp)
@@ -161,15 +153,22 @@ class OpenThread_Sim(OpenThreadTHCI, IThci):
if '@' not in discovery_add:
raise ValueError('%r in the field `add` is invalid' % discovery_add)
node_id, ssh_ip = discovery_add.split('@')
prefix, self.ssh_ip = discovery_add.split('@')
self.tag, self.node_id = prefix.split('_')
# Let it crash if it is an invalid IP address
ipaddress.ip_address(ssh_ip)
ipaddress.ip_address(self.ssh_ip)
# Do not use `os.path.join` as it uses backslash as the separator on Windows
self.device = '/'.join([config['ot_path'], ot_subpath[self.tag], 'examples/apps/cli/ot-cli-ftd'])
self.connectType = 'ip'
self.telnetIp = self.port = discovery_add
self.telnetPort = REMOTE_PORT
self.telnetUsername = REMOTE_USERNAME
self.telnetPassword = REMOTE_PASSWORD
global config
ssh = config['ssh']
self.telnetPort = ssh['port']
self.telnetUsername = ssh['username']
self.telnetPassword = ssh['password']
def _cliReadLine(self):
if len(self.__lines) > 1:
@@ -27,8 +27,13 @@
# POSSIBILITY OF SUCH DAMAGE.
#
REMOTE_USERNAME = 'pi'
REMOTE_PASSWORD = 'raspberry'
REMOTE_PORT = 22
import json
import os
REMOTE_OT_PATH = '/home/pi/repo/openthread'
CONFIG_PATH = r'%s\GRL\Thread1.2\Thread_Harness\simulation\simulation.conf' % os.environ['systemdrive']
def load_config():
with open(CONFIG_PATH, 'rt') as f:
config = json.load(f)
return config
+11 -10
View File
@@ -25,18 +25,19 @@
:: POSSIBILITY OF SUCH DAMAGE.
::
xcopy /E /Y Thread_Harness %systemdrive%\GRL\Thread1.2\Thread_Harness
copy /Y ..\..\harness-thci\OpenThread.py %systemdrive%\GRL\Thread1.2\Thread_Harness\THCI
copy /Y ..\..\harness-thci\OpenThread_BR.py %systemdrive%\GRL\Thread1.2\Thread_Harness\THCI
copy /Y ..\..\harness-thci\OpenThread.png %systemdrive%\GRL\Thread1.2\Web\images
copy /Y ..\..\harness-thci\OpenThread_BR.png %systemdrive%\GRL\Thread1.2\Web\images
xcopy /E /Y ..\posix\sniffer_sim\proto %systemdrive%\GRL\Thread1.2\Thread_Harness\simulation\Sniffer\proto
set THREADDIR=%systemdrive%\GRL\Thread1.2
xcopy /E /Y Thread_Harness %THREADDIR%\Thread_Harness
copy /Y ..\..\harness-thci\OpenThread.py %THREADDIR%\Thread_Harness\THCI
copy /Y ..\..\harness-thci\OpenThread_BR.py %THREADDIR%\Thread_Harness\THCI
copy /Y ..\..\harness-thci\OpenThread.png %THREADDIR%\Web\images
copy /Y ..\..\harness-thci\OpenThread_BR.png %THREADDIR%\Web\images
copy /Y ..\posix\simulation.conf %THREADDIR%\Thread_Harness\simulation
xcopy /E /Y ..\posix\sniffer_sim\proto %THREADDIR%\Thread_Harness\simulation\Sniffer\proto
%systemdrive%\GRL\Thread1.2\Python27\python.exe -m pip install --upgrade pip
%systemdrive%\GRL\Thread1.2\Python27\python.exe -m pip install -r requirements.txt
set BASEDIR=%systemdrive%\GRL\Thread1.2\Thread_Harness
%THREADDIR%\Python27\python.exe -m pip install --upgrade pip
%THREADDIR%\Python27\python.exe -m pip install -r requirements.txt
set BASEDIR=%THREADDIR%\Thread_Harness
%systemdrive%\GRL\Thread1.2\Python27\python.exe -m grpc_tools.protoc -I%BASEDIR% --python_out=%BASEDIR% --grpc_python_out=%BASEDIR% simulation/Sniffer/proto/sniffer.proto
pause
@@ -1,74 +0,0 @@
#!/bin/bash
#
# 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.
#
# NOTE: This script has not been fully tested up to now
set -euxo pipefail
OT_PATH=${OT_PATH:-"/home/pi/repo/openthread"}
OTBR_DOCKER_IMAGE=${OTBR_DOCKER_IMAGE:-"otbr-reference-device-1.2"}
DOCKER_BUILD_OTBR_OPTIONS=(
"-DOTBR_DUA_ROUTING=ON"
"-DOT_DUA=ON"
"-DOT_MLR=ON"
"-DOT_THREAD_VERSION=1.2"
"-DOT_SIMULATION_MAX_NETWORK_SIZE=64"
)
git clone https://github.com/openthread/ot-br-posix.git --recurse-submodules --shallow-submodules --depth=1
ETC_PATH="${OT_PATH}/tools/harness-simulation/posix/etc"
(
cd ot-br-posix
# Use system V `service` command instead
mkdir -p root/etc/init.d
cp "${ETC_PATH}/commissionerd" root/etc/init.d/commissionerd
sudo chown root:root root/etc/init.d/commissionerd
sudo chmod +x root/etc/init.d/commissionerd
cp "${ETC_PATH}/server.patch" script/server.patch
patch script/server script/server.patch
mkdir -p root/tmp
cp "${ETC_PATH}/requirements.txt" root/tmp/requirements.txt
docker build . \
-t "${OTBR_DOCKER_IMAGE}" \
-f "${ETC_PATH}/Dockerfile" \
--build-arg REFERENCE_DEVICE=1 \
--build-arg BORDER_ROUTING=0 \
--build-arg BACKBONE_ROUTER=1 \
--build-arg NAT64=0 \
--build-arg WEB_GUI=0 \
--build-arg REST_API=0 \
--build-arg EXTERNAL_COMMISSIONER=1 \
--build-arg OTBR_OPTIONS="${DOCKER_BUILD_OTBR_OPTIONS[*]}"
)
rm -rf ot-br-posix
-36
View File
@@ -1,36 +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.
#
MAX_NODES_NUM = 64
MAX_SNIFFER_NUM = 2
SNIFFER_SERVER_PORT_BASE = 50051
OT_PATH = '/home/pi/work/src/openthread-pr'
OTBR_DOCKER_IMAGE = 'otbr-reference-device-1.2'
OTBR_DOCKER_NAME_PREFIX = 'otbr_'
+117 -3
View File
@@ -29,9 +29,123 @@
set -euxo pipefail
BASE_DIR=$(dirname "$0")
SNIFFER_DIR="${BASE_DIR}/sniffer_sim"
POSIX_DIR="$(cd "$(dirname "$0")" && pwd)"
OT_DIR="${POSIX_DIR}/../../.."
ETC_DIR="${POSIX_DIR}/etc"
SNIFFER_DIR="${POSIX_DIR}/sniffer_sim"
pip3 install -r "${BASE_DIR}/requirements.txt"
CONFIG=${1:-"${POSIX_DIR}/simulation.conf"}
# Use absolute path in case of changing current working directory
if [[ ${CONFIG:0:1} != '/' ]]; then
CONFIG="${POSIX_DIR}/${CONFIG}"
fi
MAX_NETWORK_SIZE=$(jq -r '.ot_build.max_number' "$CONFIG")
PACKAGES=(
"docker.io"
"git"
"jq"
"socat"
"tshark"
)
sudo apt install -y "${PACKAGES[@]}"
pip3 install -r "${POSIX_DIR}/requirements.txt"
python3 -m grpc_tools.protoc -I"${SNIFFER_DIR}" --python_out="${SNIFFER_DIR}" --grpc_python_out="${SNIFFER_DIR}" proto/sniffer.proto
build_ot()
{
# SC2155: Declare and assign separately to avoid masking return values
local target build_dir cflags version options
target="ot-cli-ftd"
build_dir=$(jq -r '.subpath' <<<"$1")
cflags=$(jq -r '.cflags | join(" ")' <<<"$1")
version=$(jq -r '.version' <<<"$1")
options=$(jq -r '.options | join(" ")' <<<"$1")
# Intended splitting of options
read -ra options <<<"$options"
(
cd "$OT_DIR"
OT_CMAKE_NINJA_TARGET="$target" \
OT_CMAKE_BUILD_DIR="$build_dir" \
CFLAGS="$cflags" \
CXXFLAGS="$cflags" \
script/cmake-build \
simulation \
"${options[@]}" \
-DOT_THREAD_VERSION="$version" \
-DOT_SIMULATION_MAX_NETWORK_SIZE="$MAX_NETWORK_SIZE"
)
}
build_otbr()
{
# SC2155: Declare and assign separately to avoid masking return values
local target build_dir version rcp_options
target="ot-rcp"
build_dir=$(jq -r '.rcp_subpath' <<<"$1")
version=$(jq -r '.version' <<<"$1")
rcp_options=$(jq -r '.rcp_options | join(" ")' <<<"$1")
# Intended splitting of rcp_options
read -ra rcp_options <<<"$rcp_options"
(
cd "$OT_DIR"
OT_CMAKE_NINJA_TARGET="$target" \
OT_CMAKE_BUILD_DIR="$build_dir" \
script/cmake-build \
simulation \
"${rcp_options[@]}" \
-DOT_THREAD_VERSION="$version" \
-DOT_SIMULATION_MAX_NETWORK_SIZE="$MAX_NETWORK_SIZE"
)
# SC2155: Declare and assign separately to avoid masking return values
local otbr_docker_image build_args options
otbr_docker_image=$(jq -r '.docker_image' <<<"$1")
build_args=$(jq -r '.build_args | map("--build-arg " + .) | join(" ")' <<<"$1")
# Intended splitting of build_args
read -ra build_args <<<"$build_args"
options=$(jq -r '.options | join(" ")' <<<"$1")
local otbr_options=(
"$options"
"-DOT_THREAD_VERSION=$version"
"-DOT_SIMULATION_MAX_NETWORK_SIZE=$MAX_NETWORK_SIZE"
)
docker build . \
-t "${otbr_docker_image}" \
-f "${ETC_DIR}/Dockerfile" \
"${build_args[@]}" \
--build-arg OTBR_OPTIONS="${otbr_options[*]}"
}
for item in $(jq -c '.ot_build.ot | .[]' "$CONFIG"); do
build_ot "$item"
done
git clone https://github.com/openthread/ot-br-posix.git --recurse-submodules --shallow-submodules --depth=1
(
cd ot-br-posix
# Use system V `service` command instead
mkdir -p root/etc/init.d
cp "${ETC_DIR}/commissionerd" root/etc/init.d/commissionerd
sudo chown root:root root/etc/init.d/commissionerd
sudo chmod +x root/etc/init.d/commissionerd
cp "${ETC_DIR}/server.patch" script/server.patch
patch script/server script/server.patch
mkdir -p root/tmp
cp "${ETC_DIR}/requirements.txt" root/tmp/requirements.txt
for item in $(jq -c '.ot_build.otbr | .[]' "$CONFIG"); do
build_otbr "$item"
done
)
rm -rf ot-br-posix
+66 -72
View File
@@ -41,12 +41,6 @@ import subprocess
import sys
from typing import Iterable
from config import (
MAX_NODES_NUM,
MAX_SNIFFER_NUM,
SNIFFER_SERVER_PORT_BASE,
OTBR_DOCKER_NAME_PREFIX,
)
from otbr_sim import otbr_docker
GROUP = 'ff02::114'
@@ -94,34 +88,37 @@ def _advertise(s: socket.socket, dst, info):
s.sendto(json.dumps(info).encode('utf-8'), dst)
def advertise_devices(s: socket.socket, dst, ven: str, add: str, nodeids: Iterable[int], prefix: str = ''):
def advertise_devices(s: socket.socket, dst, ven: str, add: str, nodeids: Iterable[int], tag: str):
for nodeid in nodeids:
info = {
'ven': ven,
'mod': 'OpenThread',
'ver': '4',
'add': f'{prefix}{nodeid}@{add}',
'add': f'{tag}_{nodeid}@{add}',
'por': 22,
}
_advertise(s, dst, info)
def advertise_sniffers(s: socket.socket, dst, add: str, number: int):
for i in range(number):
def advertise_sniffers(s: socket.socket, dst, add: str, ports: Iterable[int]):
for port in ports:
info = {
'add': add,
'por': i + SNIFFER_SERVER_PORT_BASE,
'por': port,
}
_advertise(s, dst, info)
def start_sniffer(addr: str, port: int) -> subprocess.Popen:
def start_sniffer(addr: str, port: int, ot_path: str) -> subprocess.Popen:
if isinstance(ipaddress.ip_address(addr), ipaddress.IPv6Address):
server = f'[{addr}]:{port}'
else:
server = f'{addr}:{port}'
cmd = ['python3', 'sniffer_sim/sniffer.py', '--grpc-server', server]
cmd = [
'python3',
os.path.join(ot_path, 'tools/harness-simulation/posix/sniffer_sim/sniffer.py'), '--grpc-server', server
]
logging.info('Executing command: %s', ' '.join(cmd))
return subprocess.Popen(cmd)
@@ -131,79 +128,74 @@ def main():
# Parse arguments
parser = argparse.ArgumentParser()
# Determine the interface
parser.add_argument('-i',
'--interface',
dest='ifname',
parser.add_argument('-c',
'--config',
dest='config',
type=str,
required=True,
help='the interface used for discovery')
# Determine the number of OpenThread FTD simulations to be "detected" and then started
parser.add_argument('--ot',
dest='ot_num',
type=int,
required=False,
default=0,
help=f'the number of OpenThread FTD simulations')
# Determine the number of OpenThread BR simulations to be initiated and then detected
parser.add_argument('--otbr',
dest='otbr_num',
type=int,
required=False,
default=0,
help=f'the number of OpenThread BR simulations')
# Determine the number of sniffer simulations to be started and then detected
parser.add_argument('--sniffer',
dest='sniffer_num',
type=int,
required=False,
default=0,
help=f'the number of sniffer simulations, no more than {MAX_SNIFFER_NUM}')
help='the path of the configuration JSON file')
args = parser.parse_args()
with open(args.config, 'rt') as f:
config = json.load(f)
# Check validation of arguments
if args.ot_num < 0:
raise ValueError(f'The number of FTDs should be non-negative')
ot_path = config['ot_path']
ot_build = config['ot_build']
max_nodes_num = ot_build['max_number']
# No test case requires more than 2 sniffers
MAX_SNIFFER_NUM = 2
if args.otbr_num < 0:
raise ValueError(f'The number of OTBRs should be non-negative')
ot_devices = [(item['tag'], item['number']) for item in ot_build['ot']]
otbr_devices = [(item['tag'], item['number']) for item in ot_build['otbr']]
ot_nodes_num = sum(x[1] for x in ot_devices)
otbr_nodes_num = sum(x[1] for x in otbr_devices)
nodes_num = ot_nodes_num + otbr_nodes_num
sniffer_num = config['sniffer']['number']
if not 0 <= args.sniffer_num <= MAX_SNIFFER_NUM:
raise ValueError(f'The number of sniffers should be between 0 and {MAX_SNIFFER_NUM}')
# Check validation of numbers
if not all(0 <= x[1] <= max_nodes_num for x in ot_devices):
raise ValueError(f'The number of devices of each OT version should be between 0 and {max_nodes_num}')
if args.ot_num == args.otbr_num == 0:
raise ValueError('At least one device is required')
if not all(0 <= x[1] <= max_nodes_num for x in otbr_devices):
raise ValueError(f'The number of devices of each OTBR version should be between 0 and {max_nodes_num}')
if args.sniffer_num == 0:
raise ValueError('At least one sniffer is required')
if not 1 <= nodes_num <= max_nodes_num:
raise ValueError(f'The number of devices should be between 1 and {max_nodes_num}')
if args.ot_num + args.otbr_num > MAX_NODES_NUM:
raise ValueError(f'The number of all devices should be no more than {MAX_NODES_NUM}')
if not 1 <= sniffer_num <= MAX_SNIFFER_NUM:
raise ValueError(f'The number of sniffers should be between 1 and {MAX_SNIFFER_NUM}')
# Get the local IP address on the specified interface
addr = get_ipaddr(args.ifname)
ifname = config['discovery_ifname']
addr = get_ipaddr(ifname)
# Start the sniffer
sniffer_server_port_base = config['sniffer']['server_port_base']
sniffer_procs = []
for i in range(args.sniffer_num):
sniffer_procs.append(start_sniffer(addr, i + SNIFFER_SERVER_PORT_BASE))
for i in range(sniffer_num):
sniffer_procs.append(start_sniffer(addr, i + sniffer_server_port_base, ot_path))
# OTBR firewall scripts create rules inside the Docker container
# Run modprobe to load the kernel modules for iptables
subprocess.run(['sudo', 'modprobe', 'ip6table_filter'])
# Start the BRs
otbr_dockers = []
for nodeid in range(args.ot_num + 1, args.ot_num + args.otbr_num + 1):
otbr_dockers.append(otbr_docker.OtbrDocker(nodeid, OTBR_DOCKER_NAME_PREFIX + str(nodeid)))
nodeid = ot_nodes_num
for item in ot_build['otbr']:
tag = item['tag']
ot_rcp_path = os.path.join(ot_path, item['rcp_subpath'], 'examples/apps/ncp/ot-rcp')
docker_image = item['docker_image']
for _ in range(item['number']):
nodeid += 1
otbr_dockers.append(
otbr_docker.OtbrDocker(nodeid=nodeid,
ot_path=ot_path,
ot_rcp_path=ot_rcp_path,
docker_image=docker_image,
docker_name=f'{tag}_{nodeid}'))
s = init_socket(args.ifname, GROUP, PORT)
s = init_socket(ifname, GROUP, PORT)
logging.info('Advertising on interface %s group %s ...', args.ifname, GROUP)
logging.info('Advertising on interface %s group %s ...', ifname, GROUP)
# Terminate all sniffer simulation server processes and then exit
def exit_handler(signum, context):
@@ -227,17 +219,19 @@ def main():
if data == b'BBR':
logging.info('Received OpenThread simulation query, advertising')
advertise_devices(s, src, ven='OpenThread_Sim', add=addr, nodeids=range(1, args.ot_num + 1))
advertise_devices(s,
src,
ven='OpenThread_BR_Sim',
add=addr,
nodeids=range(args.ot_num + 1, args.ot_num + args.otbr_num + 1),
prefix=OTBR_DOCKER_NAME_PREFIX)
nodeid = 1
for ven, devices in [('OpenThread_Sim', ot_devices), ('OpenThread_BR_Sim', otbr_devices)]:
for tag, number in devices:
advertise_devices(s, src, ven=ven, add=addr, nodeids=range(nodeid, nodeid + number), tag=tag)
nodeid += number
elif data == b'Sniffer':
logging.info('Received sniffer simulation query, advertising')
advertise_sniffers(s, src, add=addr, number=args.sniffer_num)
advertise_sniffers(s,
src,
add=addr,
ports=range(sniffer_server_port_base, sniffer_server_port_base + sniffer_num))
else:
logging.warning('Received %r, but ignored', data)
@@ -33,14 +33,15 @@ import re
import subprocess
import time
from config import (OT_PATH, OTBR_DOCKER_IMAGE)
class OtbrDocker:
device_pattern = re.compile('(?<=PTY is )/dev/.+$')
def __init__(self, nodeid: int, docker_name: str):
def __init__(self, nodeid: int, ot_path: str, ot_rcp_path: str, docker_image: str, docker_name: str):
self.nodeid = nodeid
self.ot_path = ot_path
self.ot_rcp_path = ot_rcp_path
self.docker_image = docker_image
self.docker_name = docker_name
self.logger = logging.getLogger('otbr_docker.OtbrDocker')
@@ -96,9 +97,8 @@ class OtbrDocker:
self._rcp_device = None
def _launch_ot_rcp(self):
ot_rcp_path = os.path.join(OT_PATH, 'build/simulation/examples/apps/ncp/ot-rcp')
self._ot_rcp_proc = subprocess.Popen(
f'{ot_rcp_path} {self.nodeid} > {self._rcp_device_pty} < {self._rcp_device_pty}',
f'{self.ot_rcp_path} {self.nodeid} > {self._rcp_device_pty} < {self._rcp_device_pty}',
shell=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
@@ -136,8 +136,8 @@ class OtbrDocker:
'-v',
f'{self._rcp_device}:/dev/ttyUSB0',
'-v',
f'{OT_PATH.rstrip("/")}:/home/pi/repo/openthread',
OTBR_DOCKER_IMAGE,
f'{self.ot_path.rstrip("/")}:/home/pi/repo/openthread',
self.docker_image,
]
self.logger.info('Launching docker: %s', ' '.join(cmd))
launch_proc = subprocess.Popen(cmd,
@@ -0,0 +1,120 @@
{
"ot_path": "/home/pi/work/src/openthread-pr",
"ot_build": {
"max_number": 64,
"ot": [
{
"tag": "OT11",
"version": "1.1",
"number": 33,
"subpath": "build/ot11/simulation",
"cflags": [
"-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8"
],
"options": [
"-DOT_REFERENCE_DEVICE=ON",
"-DOT_COMMISSIONER=ON",
"-DOT_JOINER=ON"
]
},
{
"tag": "OT12",
"version": "1.2",
"number": 10,
"subpath": "build/ot12/simulation",
"cflags": [
"-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8"
],
"options": [
"-DOT_REFERENCE_DEVICE=ON",
"-DOT_DUA=ON",
"-DOT_MLR=ON",
"-DOT_COMMISSIONER=ON",
"-DOT_JOINER=ON",
"-DOT_CSL_RECEIVER=ON",
"-DOT_LINK_METRICS_SUBJECT=ON",
"-DOT_LINK_METRICS_INITIATOR=ON"
]
},
{
"tag": "OT13",
"version": "1.3",
"number": 10,
"subpath": "build/ot13/simulation",
"cflags": [
"-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8"
],
"options": [
"-DOT_REFERENCE_DEVICE=ON",
"-DOT_DUA=ON",
"-DOT_MLR=ON",
"-DOT_COMMISSIONER=ON",
"-DOT_JOINER=ON",
"-DOT_CSL_RECEIVER=ON",
"-DOT_LINK_METRICS_SUBJECT=ON",
"-DOT_LINK_METRICS_INITIATOR=ON"
]
}
],
"otbr": [
{
"tag": "OTBR12",
"version": "1.2",
"number": 4,
"docker_image": "otbr-reference-device-1.2",
"build_args": [
"REFERENCE_DEVICE=1",
"BORDER_ROUTING=0",
"BACKBONE_ROUTER=1",
"NAT64=0",
"WEB_GUI=0",
"REST_API=0",
"OT_COMMISSIONER=1"
],
"options": [
"-DOTBR_DUA_ROUTING=ON",
"-DOT_DUA=ON",
"-DOT_MLR=ON"
],
"rcp_subpath": "build/ot12/simulation",
"rcp_options": [
"-DOT_LINK_METRICS_SUBJECT=ON"
]
},
{
"tag": "OTBR13",
"version": "1.3",
"number": 4,
"docker_image": "otbr-reference-device-1.3",
"build_args": [
"REFERENCE_DEVICE=1",
"BORDER_ROUTING=1",
"BACKBONE_ROUTER=1",
"NAT64=0",
"WEB_GUI=0",
"REST_API=0",
"EXTERNAL_COMMISSIONER=1"
],
"options": [
"-DOTBR_DUA_ROUTING=ON",
"-DOT_DUA=ON",
"-DOT_MLR=ON"
],
"rcp_subpath": "build/ot13/simulation",
"rcp_options": [
"-DOT_LINK_METRICS_SUBJECT=ON"
]
}
]
},
"ssh": {
"username": "pi",
"password": "raspberry",
"port": 22
},
"sniffer": {
"number": 2,
"server_port_base": 50051
},
"discovery_ifname": "eth0"
}