mirror of
https://github.com/espressif/openthread.git
synced 2026-09-11 03:30:09 +00:00
This commit adds multiple-version support, and uses a unified configuration file to specify all the parameters.
171 lines
6.3 KiB
Python
171 lines
6.3 KiB
Python
#!/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 logging
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import time
|
|
|
|
|
|
class OtbrDocker:
|
|
device_pattern = re.compile('(?<=PTY is )/dev/.+$')
|
|
|
|
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')
|
|
self.logger.setLevel(logging.INFO)
|
|
|
|
self._socat_proc = None
|
|
self._ot_rcp_proc = None
|
|
|
|
self._rcp_device_pty = None
|
|
self._rcp_device = None
|
|
|
|
self._launch()
|
|
|
|
def __repr__(self) -> str:
|
|
return f'OTBR<{self.nodeid}>'
|
|
|
|
def _launch(self):
|
|
self.logger.info('Launching %r ...', self)
|
|
self._launch_socat()
|
|
self._launch_ot_rcp()
|
|
self._launch_docker()
|
|
self.logger.info('Launched %r successfully', self)
|
|
|
|
def close(self):
|
|
self.logger.info('Shutting down %r ...', self)
|
|
self._shutdown_docker()
|
|
self._shutdown_ot_rcp()
|
|
self._shutdown_socat()
|
|
self.logger.info('Shut down %r successfully', self)
|
|
|
|
def _launch_socat(self):
|
|
self._socat_proc = subprocess.Popen(['socat', '-d', '-d', 'pty,raw,echo=0', 'pty,raw,echo=0'],
|
|
stderr=subprocess.PIPE,
|
|
stdin=subprocess.DEVNULL,
|
|
stdout=subprocess.DEVNULL)
|
|
|
|
line = self._socat_proc.stderr.readline().decode('ascii').strip()
|
|
self._rcp_device_pty = self.device_pattern.findall(line)[0]
|
|
line = self._socat_proc.stderr.readline().decode('ascii').strip()
|
|
self._rcp_device = self.device_pattern.findall(line)[0]
|
|
self.logger.info(f"socat running: device PTY: {self._rcp_device_pty}, device: {self._rcp_device}")
|
|
|
|
def _shutdown_socat(self):
|
|
if self._socat_proc is None:
|
|
return
|
|
|
|
self._socat_proc.stderr.close()
|
|
self._socat_proc.terminate()
|
|
self._socat_proc.wait()
|
|
self._socat_proc = None
|
|
|
|
self._rcp_device_pty = None
|
|
self._rcp_device = None
|
|
|
|
def _launch_ot_rcp(self):
|
|
self._ot_rcp_proc = subprocess.Popen(
|
|
f'{self.ot_rcp_path} {self.nodeid} > {self._rcp_device_pty} < {self._rcp_device_pty}',
|
|
shell=True,
|
|
stdin=subprocess.DEVNULL,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL)
|
|
try:
|
|
self._ot_rcp_proc.wait(1)
|
|
except subprocess.TimeoutExpired:
|
|
# We expect ot-rcp not to quit in 1 second.
|
|
pass
|
|
else:
|
|
raise Exception(f"ot-rcp {self.nodeid} exited unexpectedly!")
|
|
|
|
def _shutdown_ot_rcp(self):
|
|
if self._ot_rcp_proc is None:
|
|
return
|
|
|
|
self._ot_rcp_proc.terminate()
|
|
self._ot_rcp_proc.wait()
|
|
self._ot_rcp_proc = None
|
|
|
|
def _launch_docker(self):
|
|
local_cmd_path = f'/tmp/{self.docker_name}'
|
|
os.makedirs(local_cmd_path, exist_ok=True)
|
|
|
|
cmd = [
|
|
'docker',
|
|
'run',
|
|
'--rm',
|
|
'--name',
|
|
self.docker_name,
|
|
'-d',
|
|
'--sysctl',
|
|
'net.ipv6.conf.all.disable_ipv6=0 net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1',
|
|
'--privileged',
|
|
'-v',
|
|
f'{self._rcp_device}:/dev/ttyUSB0',
|
|
'-v',
|
|
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,
|
|
stdin=subprocess.DEVNULL,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL)
|
|
|
|
launch_docker_deadline = time.time() + 60
|
|
launch_ok = False
|
|
time.sleep(5)
|
|
|
|
while time.time() < launch_docker_deadline:
|
|
try:
|
|
subprocess.check_call(['docker', 'exec', self.docker_name, 'ot-ctl', 'state'],
|
|
stdin=subprocess.DEVNULL,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL)
|
|
launch_ok = True
|
|
logging.info("OTBR Docker %s is ready!", self.docker_name)
|
|
break
|
|
except subprocess.CalledProcessError:
|
|
time.sleep(5)
|
|
continue
|
|
|
|
if not launch_ok:
|
|
raise RuntimeError('Cannot start OTBR Docker %s!' % self.docker_name)
|
|
launch_proc.wait()
|
|
|
|
def _shutdown_docker(self):
|
|
subprocess.run(['docker', 'stop', self.docker_name])
|