• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Provides a factory method to create a host object."""
2
3import logging
4from contextlib import closing
5
6from autotest_lib.client.bin import local_host
7from autotest_lib.client.bin import utils
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.common_lib import global_config
10from autotest_lib.server import utils as server_utils
11from autotest_lib.server.cros.dynamic_suite import constants
12from autotest_lib.server.hosts import adb_host
13from autotest_lib.server.hosts import cros_host
14from autotest_lib.server.hosts import emulated_adb_host
15from autotest_lib.server.hosts import host_info
16from autotest_lib.server.hosts import jetstream_host
17from autotest_lib.server.hosts import moblab_host
18from autotest_lib.server.hosts import gce_host
19from autotest_lib.server.hosts import sonic_host
20from autotest_lib.server.hosts import ssh_host
21from autotest_lib.server.hosts import testbed
22
23
24CONFIG = global_config.global_config
25
26SSH_ENGINE = CONFIG.get_config_value('AUTOSERV', 'ssh_engine', type=str)
27
28# Default ssh options used in creating a host.
29DEFAULT_SSH_USER = 'root'
30DEFAULT_SSH_PASS = ''
31DEFAULT_SSH_PORT = 22
32DEFAULT_SSH_VERBOSITY = ''
33DEFAULT_SSH_OPTIONS = ''
34
35# for tracking which hostnames have already had job_start called
36_started_hostnames = set()
37
38# A list of all the possible host types, ordered according to frequency of
39# host types in the lab, so the more common hosts don't incur a repeated ssh
40# overhead in checking for less common host types.
41host_types = [cros_host.CrosHost, moblab_host.MoblabHost,
42              jetstream_host.JetstreamHost, sonic_host.SonicHost,
43              adb_host.ADBHost, gce_host.GceHost,]
44OS_HOST_DICT = {'android': adb_host.ADBHost,
45                'brillo': adb_host.ADBHost,
46                'cros' : cros_host.CrosHost,
47                'emulated_brillo': emulated_adb_host.EmulatedADBHost,
48                'jetstream': jetstream_host.JetstreamHost,
49                'moblab': moblab_host.MoblabHost}
50
51
52def _get_host_arguments(machine):
53    """Get parameters to construct a host object.
54
55    There are currently 2 use cases for creating a host.
56    1. Through the server_job, in which case the server_job injects
57       the appropriate ssh parameters into our name space and they
58       are available as the variables ssh_user, ssh_pass etc.
59    2. Directly through factory.create_host, in which case we use
60       the same defaults as used in the server job to create a host.
61
62    @param machine: machine dict
63    @return: A dictionary containing arguments for host specifically hostname,
64              afe_host, user, password, port, ssh_verbosity_flag and
65              ssh_options.
66    """
67    hostname, afe_host = server_utils.get_host_info_from_machine(machine)
68    connection_pool = server_utils.get_connection_pool_from_machine(machine)
69    host_info_store = host_info.get_store_from_machine(machine)
70    info = host_info_store.get()
71
72    g = globals()
73    user = info.attributes.get('ssh_user', g.get('ssh_user', DEFAULT_SSH_USER))
74    password = info.attributes.get('ssh_pass', g.get('ssh_pass',
75                                                     DEFAULT_SSH_PASS))
76    port = info.attributes.get('ssh_port', g.get('ssh_port', DEFAULT_SSH_PORT))
77    ssh_verbosity_flag = info.attributes.get('ssh_verbosity_flag',
78                                             g.get('ssh_verbosity_flag',
79                                                   DEFAULT_SSH_VERBOSITY))
80    ssh_options = info.attributes.get('ssh_options',
81                                      g.get('ssh_options',
82                                            DEFAULT_SSH_OPTIONS))
83
84    hostname, user, password, port = server_utils.parse_machine(hostname, user,
85                                                                password, port)
86
87    host_args = {
88            'hostname': hostname,
89            'afe_host': afe_host,
90            'host_info_store': host_info_store,
91            'user': user,
92            'password': password,
93            'port': int(port),
94            'ssh_verbosity_flag': ssh_verbosity_flag,
95            'ssh_options': ssh_options,
96            'connection_pool': connection_pool,
97    }
98    return host_args
99
100
101def _detect_host(connectivity_class, hostname, **args):
102    """Detect host type.
103
104    Goes through all the possible host classes, calling check_host with a
105    basic host object. Currently this is an ssh host, but theoretically it
106    can be any host object that the check_host method of appropriate host
107    type knows to use.
108
109    @param connectivity_class: connectivity class to use to talk to the host
110                               (ParamikoHost or SSHHost)
111    @param hostname: A string representing the host name of the device.
112    @param args: Args that will be passed to the constructor of
113                 the host class.
114
115    @returns: Class type of the first host class that returns True to the
116              check_host method.
117    """
118    # TODO crbug.com/302026 (sbasi) - adjust this pathway for ADBHost in
119    # the future should a host require verify/repair.
120    with closing(connectivity_class(hostname, **args)) as host:
121        for host_module in host_types:
122            if host_module.check_host(host, timeout=10):
123                return host_module
124
125    logging.warning('Unable to apply conventional host detection methods, '
126                    'defaulting to chromeos host.')
127    return cros_host.CrosHost
128
129
130def _choose_connectivity_class(hostname, ssh_port):
131    """Choose a connectivity class for this hostname.
132
133    @param hostname: hostname that we need a connectivity class for.
134    @param ssh_port: SSH port to connect to the host.
135
136    @returns a connectivity host class.
137    """
138    if (hostname == 'localhost' and ssh_port == DEFAULT_SSH_PORT):
139        return local_host.LocalHost
140    # by default assume we're using SSH support
141    elif SSH_ENGINE == 'raw_ssh':
142        return ssh_host.SSHHost
143    else:
144        raise error.AutoservError("Unknown SSH engine %s. Please verify the "
145                                  "value of the configuration key 'ssh_engine' "
146                                  "on autotest's global_config.ini file." %
147                                  SSH_ENGINE)
148
149
150# TODO(kevcheng): Update the creation method so it's not a research project
151# determining the class inheritance model.
152def create_host(machine, host_class=None, connectivity_class=None, **args):
153    """Create a host object.
154
155    This method mixes host classes that are needed into a new subclass
156    and creates a instance of the new class.
157
158    @param machine: A dict representing the device under test or a String
159                    representing the DUT hostname (for legacy caller support).
160                    If it is a machine dict, the 'hostname' key is required.
161                    Optional 'afe_host' key will pipe in afe_host
162                    from the autoserv runtime or the AFE.
163    @param host_class: Host class to use, if None, will attempt to detect
164                       the correct class.
165    @param connectivity_class: Connectivity class to use, if None will decide
166                               based off of hostname and config settings.
167    @param args: Args that will be passed to the constructor of
168                 the new host class.
169
170    @returns: A host object which is an instance of the newly created
171              host class.
172    """
173    detected_args = _get_host_arguments(machine)
174    hostname = detected_args.pop('hostname')
175    afe_host = detected_args['afe_host']
176    args.update(detected_args)
177
178    host_os = None
179    full_os_prefix = constants.OS_PREFIX + ':'
180    # Let's grab the os from the labels if we can for host class detection.
181    for label in afe_host.labels:
182        if label.startswith(full_os_prefix):
183            host_os = label[len(full_os_prefix):]
184            break
185
186    if not connectivity_class:
187        connectivity_class = _choose_connectivity_class(hostname, args['port'])
188    # TODO(kevcheng): get rid of the host detection using host attributes.
189    host_class = (host_class
190                  or OS_HOST_DICT.get(afe_host.attributes.get('os_type'))
191                  or OS_HOST_DICT.get(host_os)
192                  or _detect_host(connectivity_class, hostname, **args))
193
194    # create a custom host class for this machine and return an instance of it
195    classes = (host_class, connectivity_class)
196    custom_host_class = type("%s_host" % hostname, classes, {})
197    host_instance = custom_host_class(hostname, **args)
198
199    # call job_start if this is the first time this host is being used
200    if hostname not in _started_hostnames:
201        host_instance.job_start()
202        _started_hostnames.add(hostname)
203
204    return host_instance
205
206
207def create_testbed(machine, **kwargs):
208    """Create the testbed object.
209
210    @param machine: A dict representing the test bed under test or a String
211                    representing the testbed hostname (for legacy caller
212                    support).
213                    If it is a machine dict, the 'hostname' key is required.
214                    Optional 'afe_host' key will pipe in afe_host from
215                    the afe_host object from the autoserv runtime or the AFE.
216    @param kwargs: Keyword args to pass to the testbed initialization.
217
218    @returns: The testbed object with all associated host objects instantiated.
219    """
220    detected_args = _get_host_arguments(machine)
221    hostname = detected_args.pop('hostname')
222    kwargs.update(detected_args)
223    return testbed.TestBed(hostname, **kwargs)
224
225
226def create_target_machine(machine, **kwargs):
227    """Create the target machine which could be a testbed or a *Host.
228
229    @param machine: A dict representing the test bed under test or a String
230                    representing the testbed hostname (for legacy caller
231                    support).
232                    If it is a machine dict, the 'hostname' key is required.
233                    Optional 'afe_host' key will pipe in afe_host
234                    from the autoserv runtime or the AFE.
235    @param kwargs: Keyword args to pass to the testbed initialization.
236
237    @returns: The target machine to be used for verify/repair.
238    """
239    # For Brillo/Android devices connected to moblab, the `machine` name is
240    # either `localhost` or `127.0.0.1`. It needs to be translated to the host
241    # container IP if the code is running inside a container. This way, autoserv
242    # can ssh to the moblab and run actual adb/fastboot commands.
243    is_moblab = CONFIG.get_config_value('SSP', 'is_moblab', type=bool,
244                                        default=False)
245    hostname = machine['hostname'] if isinstance(machine, dict) else machine
246    if (utils.is_in_container() and is_moblab and
247        hostname in ['localhost', '127.0.0.1']):
248        hostname = CONFIG.get_config_value('SSP', 'host_container_ip', type=str,
249                                           default=None)
250        if isinstance(machine, dict):
251            machine['hostname'] = hostname
252        else:
253            machine = hostname
254        logging.debug('Hostname of machine is converted to %s for the test to '
255                      'run inside a container.', hostname)
256
257    # TODO(kevcheng): We'll want to have a smarter way of figuring out which
258    # host to create (checking host labels).
259    if server_utils.machine_is_testbed(machine):
260        return create_testbed(machine, **kwargs)
261    return create_host(machine, **kwargs)
262