factory.py revision 46dadc9439355f72d394dcc4700902001bd797ff
1"""Provides a factory method to create a host object."""
2
3import logging
4from contextlib import closing
5
6from autotest_lib.client.common_lib import error, global_config
7from autotest_lib.server import autotest, utils as server_utils
8from autotest_lib.server.hosts import site_factory, cros_host, ssh_host, serial
9from autotest_lib.server.hosts import eureka_host
10from autotest_lib.server.hosts import adb_host, logfile_monitor
11
12
13
14DEFAULT_FOLLOW_PATH = '/var/log/kern.log'
15DEFAULT_PATTERNS_PATH = 'console_patterns'
16SSH_ENGINE = global_config.global_config.get_config_value('AUTOSERV',
17                                                          'ssh_engine',
18                                                          type=str)
19
20# Default ssh options used in creating a host.
21DEFAULT_SSH_USER = 'root'
22DEFAULT_SSH_PASS = ''
23DEFAULT_SSH_PORT = 22
24DEFAULT_SSH_VERBOSITY = ''
25DEFAULT_SSH_OPTIONS = ''
26
27# for tracking which hostnames have already had job_start called
28_started_hostnames = set()
29
30# A list of all the possible host types, ordered according to frequency of
31# host types in the lab, so the more common hosts don't incur a repeated ssh
32# overhead in checking for less common host types.
33host_types = [cros_host.CrosHost, eureka_host.EurekaHost, adb_host.ADBHost,]
34
35
36def _get_host_arguments():
37    """Returns parameters needed to ssh into a host.
38
39    There are currently 2 use cases for creating a host.
40    1. Through the server_job, in which case the server_job injects
41       the appropriate ssh parameters into our name space and they
42       are available as the variables ssh_user, ssh_pass etc.
43    2. Directly through factory.create_host, in which case we use
44       the same defaults as used in the server job to create a host.
45
46    @returns: A tuple of parameters needed to create an ssh connection, ordered
47              as: ssh_user, ssh_pass, ssh_port, ssh_verbosity, ssh_options.
48    """
49    g = globals()
50    return (g.get('ssh_user', DEFAULT_SSH_USER),
51            g.get('ssh_pass', DEFAULT_SSH_PASS),
52            g.get('ssh_port', DEFAULT_SSH_PORT),
53            g.get('ssh_verbosity_flag', DEFAULT_SSH_VERBOSITY),
54            g.get('ssh_options', DEFAULT_SSH_OPTIONS))
55
56
57def _detect_host(connectivity_class, hostname, **args):
58    """Detect host type.
59
60    Goes through all the possible host classes, calling check_host with a
61    basic host object. Currently this is an ssh host, but theoretically it
62    can be any host object that the check_host method of appropriate host
63    type knows to use.
64
65    @param connectivity_class: connectivity class to use to talk to the host
66                               (ParamikoHost or SSHHost)
67    @param hostname: A string representing the host name of the device.
68    @param args: Args that will be passed to the constructor of
69                 the host class.
70
71    @returns: Class type of the first host class that returns True to the
72              check_host method.
73    """
74    # TODO crbug.com/302026 (sbasi) - adjust this pathway for ADBHost in
75    # the future should a host require verify/repair.
76    with closing(connectivity_class(hostname, **args)) as host:
77        for host_module in host_types:
78            if host_module.check_host(host, timeout=10):
79                return host_module
80
81    logging.warning('Unable to apply conventional host detection methods, '
82                    'defaulting to chromeos host.')
83    return cros_host.CrosHost
84
85
86def create_host(
87    hostname, auto_monitor=False, follow_paths=None, pattern_paths=None,
88    netconsole=False, **args):
89    """Create a host object.
90
91    This method mixes host classes that are needed into a new subclass
92    and creates a instance of the new class.
93
94    @param hostname: A string representing the host name of the device.
95    @param auto_monitor: A boolean value, if True, will try to mix
96                         SerialHost in. If the host supports use as SerialHost,
97                         will not mix in LogfileMonitorMixin anymore.
98                         If the host doesn't support it, will
99                         fall back to direct demesg logging and mix
100                         LogfileMonitorMixin in.
101    @param follow_paths: A list, passed to LogfileMonitorMixin,
102                         remote paths to monitor.
103    @param pattern_paths: A list, passed to LogfileMonitorMixin,
104                          local paths to alert pattern definition files.
105    @param netconsole: A boolean value, if True, will mix NetconsoleHost in.
106    @param args: Args that will be passed to the constructor of
107                 the new host class.
108    @param adb: If True creates an instance of adb_host not cros_host.
109
110    @returns: A host object which is an instance of the newly created
111              host class.
112    """
113
114    ssh_user, ssh_pass, ssh_port, ssh_verbosity_flag, ssh_options = \
115            _get_host_arguments()
116
117    hostname, args['user'], args['password'], args['port'] = \
118            server_utils.parse_machine(hostname, ssh_user, ssh_pass, ssh_port)
119    args['ssh_verbosity_flag'] = ssh_verbosity_flag
120    args['ssh_options'] = ssh_options
121
122    # by default assume we're using SSH support
123    if SSH_ENGINE == 'paramiko':
124        from autotest_lib.server.hosts import paramiko_host
125        connectivity_class = paramiko_host.ParamikoHost
126    elif SSH_ENGINE == 'raw_ssh':
127        connectivity_class = ssh_host.SSHHost
128    else:
129        raise error.AutoServError("Unknown SSH engine %s. Please verify the "
130                                  "value of the configuration key 'ssh_engine' "
131                                  "on autotest's global_config.ini file." %
132                                  SSH_ENGINE)
133
134    classes = [_detect_host(connectivity_class, hostname, **args),
135               connectivity_class]
136    # by default mix in run_test support
137    classes.append(autotest.AutotestHostMixin)
138
139    # if the user really wants to use netconsole, let them
140    if netconsole:
141        classes.append(netconsole.NetconsoleHost)
142
143    if auto_monitor:
144        # use serial console support if it's available
145        conmux_args = {}
146        for key in ("conmux_server", "conmux_attach"):
147            if key in args:
148                conmux_args[key] = args[key]
149        if serial.SerialHost.host_is_supported(hostname, **conmux_args):
150            classes.append(serial.SerialHost)
151        else:
152            # no serial available, fall back to direct dmesg logging
153            if follow_paths is None:
154                follow_paths = [DEFAULT_FOLLOW_PATH]
155            else:
156                follow_paths = list(follow_paths) + [DEFAULT_FOLLOW_PATH]
157
158            if pattern_paths is None:
159                pattern_paths = [DEFAULT_PATTERNS_PATH]
160            else:
161                pattern_paths = (
162                    list(pattern_paths) + [DEFAULT_PATTERNS_PATH])
163
164            logfile_monitor_class = logfile_monitor.NewLogfileMonitorMixin(
165                follow_paths, pattern_paths)
166            classes.append(logfile_monitor_class)
167
168    elif follow_paths:
169        logfile_monitor_class = logfile_monitor.NewLogfileMonitorMixin(
170            follow_paths, pattern_paths)
171        classes.append(logfile_monitor_class)
172
173    # do any site-specific processing of the classes list
174    site_factory.postprocess_classes(classes, hostname,
175                                     auto_monitor=auto_monitor, **args)
176
177    # create a custom host class for this machine and return an instance of it
178    host_class = type("%s_host" % hostname, tuple(classes), {})
179    host_instance = host_class(hostname, **args)
180
181    # call job_start if this is the first time this host is being used
182    if hostname not in _started_hostnames:
183        host_instance.job_start()
184        _started_hostnames.add(hostname)
185
186    return host_instance
187