blob: e3791347c1b8064019843391944429d82543a678 [file] [log] [blame]
"""Provides a factory method to create a host object."""
import logging
from contextlib import closing
from autotest_lib.client.common_lib import error, global_config
from autotest_lib.server import autotest, utils as server_utils
from autotest_lib.server.hosts import site_factory, cros_host, ssh_host, serial
from autotest_lib.server.hosts import sonic_host
from autotest_lib.server.hosts import adb_host, logfile_monitor
DEFAULT_FOLLOW_PATH = '/var/log/kern.log'
DEFAULT_PATTERNS_PATH = 'console_patterns'
SSH_ENGINE = global_config.global_config.get_config_value('AUTOSERV',
'ssh_engine',
type=str)
# Default ssh options used in creating a host.
DEFAULT_SSH_USER = 'root'
DEFAULT_SSH_PASS = ''
DEFAULT_SSH_PORT = 22
DEFAULT_SSH_VERBOSITY = ''
DEFAULT_SSH_OPTIONS = ''
# for tracking which hostnames have already had job_start called
_started_hostnames = set()
# A list of all the possible host types, ordered according to frequency of
# host types in the lab, so the more common hosts don't incur a repeated ssh
# overhead in checking for less common host types.
host_types = [cros_host.CrosHost, sonic_host.SonicHost, adb_host.ADBHost,]
def _get_host_arguments():
"""Returns parameters needed to ssh into a host.
There are currently 2 use cases for creating a host.
1. Through the server_job, in which case the server_job injects
the appropriate ssh parameters into our name space and they
are available as the variables ssh_user, ssh_pass etc.
2. Directly through factory.create_host, in which case we use
the same defaults as used in the server job to create a host.
@returns: A tuple of parameters needed to create an ssh connection, ordered
as: ssh_user, ssh_pass, ssh_port, ssh_verbosity, ssh_options.
"""
g = globals()
return (g.get('ssh_user', DEFAULT_SSH_USER),
g.get('ssh_pass', DEFAULT_SSH_PASS),
g.get('ssh_port', DEFAULT_SSH_PORT),
g.get('ssh_verbosity_flag', DEFAULT_SSH_VERBOSITY),
g.get('ssh_options', DEFAULT_SSH_OPTIONS))
def _detect_host(connectivity_class, hostname, **args):
"""Detect host type.
Goes through all the possible host classes, calling check_host with a
basic host object. Currently this is an ssh host, but theoretically it
can be any host object that the check_host method of appropriate host
type knows to use.
@param connectivity_class: connectivity class to use to talk to the host
(ParamikoHost or SSHHost)
@param hostname: A string representing the host name of the device.
@param args: Args that will be passed to the constructor of
the host class.
@returns: Class type of the first host class that returns True to the
check_host method.
"""
# TODO crbug.com/302026 (sbasi) - adjust this pathway for ADBHost in
# the future should a host require verify/repair.
with closing(connectivity_class(hostname, **args)) as host:
for host_module in host_types:
if host_module.check_host(host, timeout=10):
return host_module
logging.warning('Unable to apply conventional host detection methods, '
'defaulting to chromeos host.')
return cros_host.CrosHost
def create_host(
hostname, auto_monitor=False, follow_paths=None, pattern_paths=None,
netconsole=False, **args):
"""Create a host object.
This method mixes host classes that are needed into a new subclass
and creates a instance of the new class.
@param hostname: A string representing the host name of the device.
@param auto_monitor: A boolean value, if True, will try to mix
SerialHost in. If the host supports use as SerialHost,
will not mix in LogfileMonitorMixin anymore.
If the host doesn't support it, will
fall back to direct demesg logging and mix
LogfileMonitorMixin in.
@param follow_paths: A list, passed to LogfileMonitorMixin,
remote paths to monitor.
@param pattern_paths: A list, passed to LogfileMonitorMixin,
local paths to alert pattern definition files.
@param netconsole: A boolean value, if True, will mix NetconsoleHost in.
@param args: Args that will be passed to the constructor of
the new host class.
@param adb: If True creates an instance of adb_host not cros_host.
@returns: A host object which is an instance of the newly created
host class.
"""
ssh_user, ssh_pass, ssh_port, ssh_verbosity_flag, ssh_options = \
_get_host_arguments()
hostname, args['user'], args['password'], args['port'] = \
server_utils.parse_machine(hostname, ssh_user, ssh_pass, ssh_port)
args['ssh_verbosity_flag'] = ssh_verbosity_flag
args['ssh_options'] = ssh_options
# by default assume we're using SSH support
if SSH_ENGINE == 'paramiko':
from autotest_lib.server.hosts import paramiko_host
connectivity_class = paramiko_host.ParamikoHost
elif SSH_ENGINE == 'raw_ssh':
connectivity_class = ssh_host.SSHHost
else:
raise error.AutoServError("Unknown SSH engine %s. Please verify the "
"value of the configuration key 'ssh_engine' "
"on autotest's global_config.ini file." %
SSH_ENGINE)
classes = [_detect_host(connectivity_class, hostname, **args),
connectivity_class]
# by default mix in run_test support
classes.append(autotest.AutotestHostMixin)
# if the user really wants to use netconsole, let them
if netconsole:
classes.append(netconsole.NetconsoleHost)
if auto_monitor:
# use serial console support if it's available
conmux_args = {}
for key in ("conmux_server", "conmux_attach"):
if key in args:
conmux_args[key] = args[key]
if serial.SerialHost.host_is_supported(hostname, **conmux_args):
classes.append(serial.SerialHost)
else:
# no serial available, fall back to direct dmesg logging
if follow_paths is None:
follow_paths = [DEFAULT_FOLLOW_PATH]
else:
follow_paths = list(follow_paths) + [DEFAULT_FOLLOW_PATH]
if pattern_paths is None:
pattern_paths = [DEFAULT_PATTERNS_PATH]
else:
pattern_paths = (
list(pattern_paths) + [DEFAULT_PATTERNS_PATH])
logfile_monitor_class = logfile_monitor.NewLogfileMonitorMixin(
follow_paths, pattern_paths)
classes.append(logfile_monitor_class)
elif follow_paths:
logfile_monitor_class = logfile_monitor.NewLogfileMonitorMixin(
follow_paths, pattern_paths)
classes.append(logfile_monitor_class)
# do any site-specific processing of the classes list
site_factory.postprocess_classes(classes, hostname,
auto_monitor=auto_monitor, **args)
# create a custom host class for this machine and return an instance of it
host_class = type("%s_host" % hostname, tuple(classes), {})
host_instance = host_class(hostname, **args)
# call job_start if this is the first time this host is being used
if hostname not in _started_hostnames:
host_instance.job_start()
_started_hostnames.add(hostname)
return host_instance