Source code for tbot.machine.board_linux

"""
Board machine for Linux interaction
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
"""
import pathlib
import random
import time
import typing
import paramiko
import tbot
from . import machine
from . import board
from . import shell_utils


# pylint: disable=too-many-instance-attributes
[docs]class MachineBoardLinux(board.MachineBoard): """ Board machine for Linux interaction :param str name: Name of the shell (eg ``someboard-bash``), defaults to ``tb.config["linux.shell.name"]`` :param str boardname: Name of the board, defaults to ``tb.config["board.name"]`` :param str boot_command: U-Boot command to boot linux. May be multiple commands separated by newlines, defaults to ``tb.config["linux.boot_command"]`` :param str login_prompt: The string to wait for before sending the username, defaults to ``tb.config["linux.shell.login_prompt"]`` :param float login_timeout: The time to wait after entering login credentials, defaults to ``tb.config["linux.shell.login_timeout"]`` :param str username: Username, defaults to ``tb.config["linux.shell.username"]`` :param str password: Password, use ``""`` if no password is required, defaults to ``tb.config["linux.shell.password"]`` """ # pylint: disable=too-many-arguments def __init__( self, *, name: typing.Optional[str] = None, boardname: typing.Optional[str] = None, boot_command: typing.Optional[str] = None, login_prompt: typing.Optional[str] = None, login_timeout: typing.Optional[float] = None, username: typing.Optional[str] = None, password: typing.Optional[str] = None, ) -> None: super().__init__() self.name = name self.boardname = boardname self.boot_command = boot_command self.prompt = f"TBOT-LINUX-{random.randint(11111,99999)}>" self.login_prompt = login_prompt self.login_timeout = login_timeout self.username = username self.password = password self.channel: typing.Optional[paramiko.Channel] = None self.ub_machine: typing.Optional[tbot.machine.MachineBoardUBoot] = None self.own_ub = False def _setup( self, tb: "tbot.TBot", previous: typing.Optional[machine.Machine] = None ) -> "MachineBoardLinux": self.name = self.name or tb.config["board.serial.name", "unknown-linux"] # Check if the previous machine is also a MachineBoardUBoot, # if this is the case, prevent reinitialisation if ( previous is not self and isinstance(previous, MachineBoardLinux) and previous.unique_machine_name == self.unique_machine_name ): return previous if isinstance(previous, tbot.machine.MachineBoardUBoot): self.ub_machine = previous else: # Create our own U-Boot ub_machine = tbot.machine.MachineBoardUBoot() # pylint: disable=protected-access self.ub_machine = ub_machine._setup(tb, previous) self.own_ub = True if not isinstance(self.ub_machine, tbot.machine.MachineBoardUBoot): raise tbot.InvalidUsageException( "Can't switch to linux if U-Boot is not yet present" ) self.powerup = False super()._setup(tb, previous) tbot.log.event( ty=["board", "boot-linux"], msg=f"{tbot.log.has_color('1')}LINUX BOOT{tbot.log.has_color('0')} ({self.boardname})", verbosity=tbot.log.Verbosity.INFO, dct={"board": self.boardname}, ) self.channel = self.ub_machine.channel if self.channel is None: raise tbot.InvalidUsageException( "U-Boot machine is not initialized correctly" ) self.boot_command = self.boot_command or tb.config["linux.boot_command"] self.login_prompt = ( self.login_prompt or tb.config["linux.shell.login_prompt", "login: "] ) self.login_timeout = ( self.login_timeout or tb.config["linux.shell.login_timeout", 2] ) self.username = self.username or tb.config["linux.shell.username", "root"] self.password = self.password or tb.config["linux.shell.password", ""] try: # Boot cmds = self.boot_command.split("\n") for cmd in cmds[:-1]: self.ub_machine.exec0(cmd) self.channel.send(f"{cmds[-1]}\n") stdout_handler = tbot.log_events.shell_command( machine=self.ub_machine.unique_machine_name.split("-"), command=cmds[-1], show=True, show_stdout=False, ) stdout_handler.prefix = " >> " boot_stdout = shell_utils.read_to_prompt( self.channel, self.login_prompt, stdout_handler ) # Login self.channel.send(f"{self.username}\n") login_str = self.login_prompt + self.username stdout_handler.print(login_str) boot_stdout += login_str + "\n" time.sleep(0.2) self.channel.send(f"{self.password}\n") time.sleep(self.login_timeout) # Make linux aware of new terminal size self.channel.send("unset HISTFILE\nstty cols 200\nstty rows 200\nsh") # Set PROMPT self.prompt = f"TBOT-LINUX-{random.randint(11111,99999)}>" self.channel.send( f"\nunset HISTFILE\nPROMPT_COMMAND=\nPS1='{self.prompt}'\n" ) boot_stdout += shell_utils.read_to_prompt( self.channel, self.prompt, stdout_handler ) stdout_handler.dct["exit_code"] = 0 except: # noqa: E722 # If anything goes wrong, turn off again self._destruct(tb) raise return self def _destruct(self, tb: "tbot.TBot") -> None: tbot.log.event( ty=["board", "linux-shutdown"], msg=f"{tbot.log.has_color('1')}LINUX SHUTDOWN{tbot.log.has_color('0')} ({self.boardname})", verbosity=tbot.log.Verbosity.INFO, dct={"board": self.boardname}, ) if isinstance(self.ub_machine, tbot.machine.MachineBoardUBoot): if self.own_ub: # pylint: disable=protected-access self.ub_machine._destruct(tb) else: # Reset to U-Boot self.ub_machine.powercycle() else: raise Exception( "U-Boot not initialized correctly, board might still be on!" ) def _exec( self, command: str, stdout_handler: typing.Optional[tbot.log.LogStdoutHandler] ) -> typing.Tuple[int, str]: if isinstance(stdout_handler, tbot.log.LogStdoutHandler): stdout_handler.prefix = " >< " return shell_utils.command_and_retval( self.channel, self.prompt, command, stdout_handler ) @property def workdir(self) -> pathlib.PurePosixPath: return pathlib.PurePosixPath("/tmp") @property def unique_machine_name(self) -> str: """ Unique name of this machine, ``"board-linux-<boardshell-name>"`` """ return f"board-linux-{self.name}"