| # Copyright 2018 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Device-related helper functions/classes.""" |
| |
| import argparse |
| import logging |
| import os |
| import subprocess |
| |
| from chromite.cli.cros import cros_chrome_sdk |
| from chromite.lib import commandline |
| from chromite.lib import remote_access |
| from chromite.lib import retry_util |
| |
| |
| class DeviceError(Exception): |
| """Exception for Device failures.""" |
| |
| |
| class Device: |
| """Class for managing a test device.""" |
| |
| SSH_CONNECT_TIMEOUT = 30 |
| |
| def __init__(self, opts) -> None: |
| """Initialize Device. |
| |
| Args: |
| opts: command line options. |
| """ |
| self.device = opts.device.hostname if opts.device else None |
| self.ssh_port = opts.device.port if opts.device else None |
| self.should_start_vm = not self.device |
| self.board = opts.board |
| |
| self.use_sudo = False |
| self.cmd = opts.args[1:] if opts.cmd else None |
| self.private_key = opts.private_key |
| self.dryrun = opts.dryrun |
| # log_level is only set if --log-level or --debug is specified. |
| self.log_level = getattr(opts, "log_level", None) |
| self.InitRemote() |
| |
| def InitRemote(self, connect_timeout=SSH_CONNECT_TIMEOUT) -> None: |
| """Initialize remote access.""" |
| self.remote = remote_access.ChromiumOSDevice( |
| self.device, |
| port=self.ssh_port, |
| connect_settings=self._ConnectSettings( |
| connect_timeout=connect_timeout |
| ), |
| private_key=self.private_key, |
| include_dev_paths=False, |
| ) |
| |
| self.device_addr = "ssh://%s" % self.device |
| if self.ssh_port: |
| self.device_addr += ":%d" % self.ssh_port |
| |
| def WaitForBoot(self, max_retry=10, sleep=5) -> None: |
| """Wait for the device to boot up. |
| |
| Wait for the ssh connection to become active. |
| """ |
| try: |
| result = retry_util.RetryException( |
| exception=remote_access.SSHConnectionError, |
| max_retry=max_retry, |
| functor=lambda: self.run(cmd=["true"]), |
| sleep=sleep, |
| ) |
| except remote_access.SSHConnectionError: |
| raise DeviceError( |
| "WaitForBoot timed out trying to connect to the device." |
| ) |
| |
| if result.returncode != 0: |
| raise DeviceError("WaitForBoot failed: %s." % result.stderr) |
| |
| def chmod(self, *args, **kwargs) -> None: |
| return self.remote.chmod(*args, **kwargs) |
| |
| def mkdir(self, *args, **kwargs) -> None: |
| return self.remote.mkdir(*args, **kwargs) |
| |
| def run(self, cmd, stream_output=False, **kwargs): |
| """Run a remote command. |
| |
| Args: |
| cmd: command to run. |
| stream_output: Stream output of long-running commands. |
| **kwargs: additional args (see documentation for RemoteDevice.run). |
| |
| Returns: |
| cros_build_lib.CompletedProcess object. |
| """ |
| kwargs.setdefault("check", False) |
| if stream_output: |
| kwargs.setdefault("capture_output", False) |
| else: |
| kwargs.setdefault("stderr", subprocess.STDOUT) |
| kwargs.setdefault("log_output", True) |
| return self.remote.run( |
| cmd, dryrun=self.dryrun, debug_level=logging.INFO, **kwargs |
| ) |
| |
| def _ConnectSettings(self, connect_timeout): |
| """Increase ServerAliveCountMax and ServerAliveInterval. |
| |
| Wait 2 min before dropping the SSH connection. |
| |
| Args: |
| connect_timeout: SSH ConnectTimeout setting. |
| |
| Returns: |
| List of arguments to pass to SSH. |
| """ |
| return remote_access.CompileSSHConnectSettings( |
| ConnectTimeout=connect_timeout, |
| ServerAliveInterval=15, |
| ServerAliveCountMax=8, |
| ) |
| |
| @staticmethod |
| def Create(opts): |
| """Create either a Device or VM based on |opts.device|.""" |
| if not opts.device: |
| from chromite.lib import vm |
| |
| return vm.VM(opts) |
| return Device(opts) |
| |
| @staticmethod |
| def GetParser(parser=None): |
| """Parse a list of args. |
| |
| Returns: |
| List of parsed opts. |
| """ |
| parser = parser or commandline.ArgumentParser( |
| description=__doc__, dryrun=True |
| ) |
| parser.add_argument( |
| "-d", |
| "--device", |
| type=commandline.DeviceParser(commandline.DeviceScheme.SSH), |
| help="Hostname or device IP in format hostname[:port]. If not " |
| "specified, a VM will be launched for the duration of the test.", |
| ) |
| sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV) |
| parser.add_argument( |
| "--board", default=sdk_board_env, help="Board to use." |
| ) |
| parser.add_argument("--private-key", help="Path to ssh private key.") |
| parser.add_argument( |
| "--cmd", action="store_true", default=False, help="Run a command." |
| ) |
| parser.add_argument( |
| "args", nargs=argparse.REMAINDER, help="Command to run." |
| ) |
| return parser |