| """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 eureka_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, eureka_host.EurekaHost, 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 |