blob: 61cdf931f2fe007c4c3155ae89a2aa038f40de9b [file] [log] [blame]
# 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