blob: a8a2da681e798c21a29bfb091dcbb11b55047174 [file] [log] [blame]
Oleg Loskutoff75cc9b22019-10-16 17:28:12 -07001# Copyright (c) 2008 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Fang Dengd1c2b732013-08-20 12:59:46 -07005"""Provides a factory method to create a host object."""
6
beeps46dadc92013-11-07 14:07:10 -08007import logging
Aviv Keshet5189dd52013-10-21 12:43:57 -07008from contextlib import closing
beeps46dadc92013-11-07 14:07:10 -08009
Simran Basi14622bb2015-11-25 13:23:40 -080010from autotest_lib.client.bin import local_host
Dan Shi6450e142016-03-11 11:52:20 -080011from autotest_lib.client.bin import utils
Prathmesh Prabhu88cd44b2018-08-28 14:37:52 -070012from autotest_lib.client.common_lib import deprecation
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080013from autotest_lib.client.common_lib import error
14from autotest_lib.client.common_lib import global_config
J. Richard Barnette1c3b8532015-11-18 19:02:02 -080015from autotest_lib.server import utils as server_utils
Kevin Cheng84a71ba2016-07-14 11:03:57 -070016from autotest_lib.server.cros.dynamic_suite import constants
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080017from autotest_lib.server.hosts import cros_host
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080018from autotest_lib.server.hosts import host_info
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080019from autotest_lib.server.hosts import jetstream_host
20from autotest_lib.server.hosts import moblab_host
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -080021from autotest_lib.server.hosts import gce_host
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080022from autotest_lib.server.hosts import sonic_host
23from autotest_lib.server.hosts import ssh_host
Garry Wange4b6d6e2019-06-17 17:08:46 -070024from autotest_lib.server.hosts import labstation_host
mblighe48bcfb2008-11-11 17:09:44 +000025
Fang Dengd1c2b732013-08-20 12:59:46 -070026
Dan Shi6450e142016-03-11 11:52:20 -080027CONFIG = global_config.global_config
28
beepsd0672682013-09-16 17:32:16 -070029# Default ssh options used in creating a host.
30DEFAULT_SSH_USER = 'root'
31DEFAULT_SSH_PASS = ''
32DEFAULT_SSH_PORT = 22
33DEFAULT_SSH_VERBOSITY = ''
34DEFAULT_SSH_OPTIONS = ''
35
jadmanskid60321a2008-10-28 20:32:05 +000036# for tracking which hostnames have already had job_start called
37_started_hostnames = set()
38
beeps46dadc92013-11-07 14:07:10 -080039# A list of all the possible host types, ordered according to frequency of
40# host types in the lab, so the more common hosts don't incur a repeated ssh
41# overhead in checking for less common host types.
Garry Wange4b6d6e2019-06-17 17:08:46 -070042host_types = [cros_host.CrosHost, labstation_host.LabstationHost,
43 moblab_host.MoblabHost, jetstream_host.JetstreamHost,
Oleg Loskutoff75cc9b22019-10-16 17:28:12 -070044 sonic_host.SonicHost, gce_host.GceHost]
45OS_HOST_DICT = {'cros': cros_host.CrosHost,
Laurence Goodby778c9a42017-05-24 19:24:07 -070046 'jetstream': jetstream_host.JetstreamHost,
Garry Wange4b6d6e2019-06-17 17:08:46 -070047 'moblab': moblab_host.MoblabHost,
48 'labstation': labstation_host.LabstationHost}
beeps46dadc92013-11-07 14:07:10 -080049
Prathmesh Prabhu723ca4f2018-08-28 14:54:22 -070050# Timeout for early connectivity check to the host, in seconds.
51_CONNECTIVITY_CHECK_TIMEOUT_S = 10
52
Fang Dengd1c2b732013-08-20 12:59:46 -070053
Justin Giorgi5208eaa2016-07-02 20:12:12 -070054def _get_host_arguments(machine):
55 """Get parameters to construct a host object.
beepsd0672682013-09-16 17:32:16 -070056
57 There are currently 2 use cases for creating a host.
58 1. Through the server_job, in which case the server_job injects
59 the appropriate ssh parameters into our name space and they
60 are available as the variables ssh_user, ssh_pass etc.
61 2. Directly through factory.create_host, in which case we use
62 the same defaults as used in the server job to create a host.
63
Justin Giorgi5208eaa2016-07-02 20:12:12 -070064 @param machine: machine dict
65 @return: A dictionary containing arguments for host specifically hostname,
66 afe_host, user, password, port, ssh_verbosity_flag and
67 ssh_options.
beepsd0672682013-09-16 17:32:16 -070068 """
Hidehiko Abe06893302017-06-24 07:32:38 +090069 hostname, afe_host = server_utils.get_host_info_from_machine(machine)
70 connection_pool = server_utils.get_connection_pool_from_machine(machine)
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080071 host_info_store = host_info.get_store_from_machine(machine)
72 info = host_info_store.get()
Justin Giorgi5208eaa2016-07-02 20:12:12 -070073
beepsd0672682013-09-16 17:32:16 -070074 g = globals()
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080075 user = info.attributes.get('ssh_user', g.get('ssh_user', DEFAULT_SSH_USER))
76 password = info.attributes.get('ssh_pass', g.get('ssh_pass',
77 DEFAULT_SSH_PASS))
78 port = info.attributes.get('ssh_port', g.get('ssh_port', DEFAULT_SSH_PORT))
79 ssh_verbosity_flag = info.attributes.get('ssh_verbosity_flag',
80 g.get('ssh_verbosity_flag',
81 DEFAULT_SSH_VERBOSITY))
82 ssh_options = info.attributes.get('ssh_options',
83 g.get('ssh_options',
84 DEFAULT_SSH_OPTIONS))
Justin Giorgi5208eaa2016-07-02 20:12:12 -070085
86 hostname, user, password, port = server_utils.parse_machine(hostname, user,
87 password, port)
88
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080089 host_args = {
90 'hostname': hostname,
91 'afe_host': afe_host,
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080092 'host_info_store': host_info_store,
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080093 'user': user,
94 'password': password,
95 'port': int(port),
Justin Giorgi5208eaa2016-07-02 20:12:12 -070096 'ssh_verbosity_flag': ssh_verbosity_flag,
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080097 'ssh_options': ssh_options,
Hidehiko Abe06893302017-06-24 07:32:38 +090098 'connection_pool': connection_pool,
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080099 }
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -0800100 return host_args
beepsd0672682013-09-16 17:32:16 -0700101
102
Simran Basi724b8a52013-09-30 11:19:31 -0700103def _detect_host(connectivity_class, hostname, **args):
104 """Detect host type.
105
beeps46dadc92013-11-07 14:07:10 -0800106 Goes through all the possible host classes, calling check_host with a
107 basic host object. Currently this is an ssh host, but theoretically it
108 can be any host object that the check_host method of appropriate host
109 type knows to use.
Simran Basi724b8a52013-09-30 11:19:31 -0700110
111 @param connectivity_class: connectivity class to use to talk to the host
112 (ParamikoHost or SSHHost)
113 @param hostname: A string representing the host name of the device.
114 @param args: Args that will be passed to the constructor of
115 the host class.
116
beeps46dadc92013-11-07 14:07:10 -0800117 @returns: Class type of the first host class that returns True to the
118 check_host method.
Simran Basi724b8a52013-09-30 11:19:31 -0700119 """
beeps46dadc92013-11-07 14:07:10 -0800120 with closing(connectivity_class(hostname, **args)) as host:
121 for host_module in host_types:
Prathmesh Prabhu723ca4f2018-08-28 14:54:22 -0700122 logging.info('Attempting to autodetect if host is of type %s',
123 host_module.__name__)
beeps46dadc92013-11-07 14:07:10 -0800124 if host_module.check_host(host, timeout=10):
125 return host_module
126
127 logging.warning('Unable to apply conventional host detection methods, '
128 'defaulting to chromeos host.')
129 return cros_host.CrosHost
Simran Basi724b8a52013-09-30 11:19:31 -0700130
131
Gwendal Grignou0e727352016-01-25 14:25:41 -0800132def _choose_connectivity_class(hostname, ssh_port):
Simran Basi2a5dc252015-12-16 13:12:22 -0800133 """Choose a connectivity class for this hostname.
134
135 @param hostname: hostname that we need a connectivity class for.
Gwendal Grignou0e727352016-01-25 14:25:41 -0800136 @param ssh_port: SSH port to connect to the host.
Simran Basi2a5dc252015-12-16 13:12:22 -0800137
138 @returns a connectivity host class.
139 """
Gwendal Grignou0e727352016-01-25 14:25:41 -0800140 if (hostname == 'localhost' and ssh_port == DEFAULT_SSH_PORT):
Simran Basi2a5dc252015-12-16 13:12:22 -0800141 return local_host.LocalHost
Simran Basi2a5dc252015-12-16 13:12:22 -0800142 else:
Prathmesh Prabhuf8a63752018-08-28 13:56:29 -0700143 return ssh_host.SSHHost
Simran Basi2a5dc252015-12-16 13:12:22 -0800144
145
Prathmesh Prabhu723ca4f2018-08-28 14:54:22 -0700146def _verify_connectivity(connectivity_class, hostname, **args):
147 """Verify connectivity to the host.
148
149 Any interaction with an unreachable host is guaranteed to fail later. By
150 checking connectivity first, duplicate errors / timeouts can be avoided.
151 """
152 if connectivity_class == local_host.LocalHost:
153 return True
154
155 assert connectivity_class == ssh_host.SSHHost
156 with closing(ssh_host.SSHHost(hostname, **args)) as host:
157 host.run('test :', timeout=_CONNECTIVITY_CHECK_TIMEOUT_S,
158 ssh_failure_retry_ok=False,
159 ignore_timeout=False)
160
161
Kevin Cheng85e864a2015-11-30 11:49:34 -0800162# TODO(kevcheng): Update the creation method so it's not a research project
163# determining the class inheritance model.
Simran Basi2a5dc252015-12-16 13:12:22 -0800164def create_host(machine, host_class=None, connectivity_class=None, **args):
Fang Dengd1c2b732013-08-20 12:59:46 -0700165 """Create a host object.
166
167 This method mixes host classes that are needed into a new subclass
168 and creates a instance of the new class.
169
Simran Basi1bf60eb2015-12-01 16:39:29 -0800170 @param machine: A dict representing the device under test or a String
171 representing the DUT hostname (for legacy caller support).
172 If it is a machine dict, the 'hostname' key is required.
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700173 Optional 'afe_host' key will pipe in afe_host
Simran Basi1bf60eb2015-12-01 16:39:29 -0800174 from the autoserv runtime or the AFE.
Simran Basia5522a32015-10-06 11:01:24 -0700175 @param host_class: Host class to use, if None, will attempt to detect
176 the correct class.
Prathmesh Prabhu88cd44b2018-08-28 14:37:52 -0700177 @param connectivity_class: DEPRECATED. Connectivity class is determined
178 internally.
Fang Dengd1c2b732013-08-20 12:59:46 -0700179 @param args: Args that will be passed to the constructor of
180 the new host class.
181
182 @returns: A host object which is an instance of the newly created
183 host class.
184 """
Prathmesh Prabhu88cd44b2018-08-28 14:37:52 -0700185 # Argument deprecated
186 if connectivity_class is not None:
187 deprecation.warn('server.create_hosts:connectivity_class')
188 connectivity_class = None
189
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700190 detected_args = _get_host_arguments(machine)
191 hostname = detected_args.pop('hostname')
192 afe_host = detected_args['afe_host']
Garry Wange48473c2019-07-22 17:39:21 -0700193 info_store = detected_args['host_info_store'].get()
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700194 args.update(detected_args)
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700195 host_os = None
Kevin Cheng84a71ba2016-07-14 11:03:57 -0700196 full_os_prefix = constants.OS_PREFIX + ':'
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700197 # Let's grab the os from the labels if we can for host class detection.
Garry Wange48473c2019-07-22 17:39:21 -0700198 for label in info_store.labels:
Kevin Cheng84a71ba2016-07-14 11:03:57 -0700199 if label.startswith(full_os_prefix):
200 host_os = label[len(full_os_prefix):]
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700201 break
Simran Basi431010f2013-09-04 10:42:41 -0700202
Prathmesh Prabhu88cd44b2018-08-28 14:37:52 -0700203 connectivity_class = _choose_connectivity_class(hostname, args['port'])
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700204 # TODO(kevcheng): get rid of the host detection using host attributes.
205 host_class = (host_class
206 or OS_HOST_DICT.get(afe_host.attributes.get('os_type'))
Prathmesh Prabhu723ca4f2018-08-28 14:54:22 -0700207 or OS_HOST_DICT.get(host_os))
208
209 if host_class is None:
210 # TODO(pprabhu) If we fail to verify connectivity, we skip the costly
211 # host autodetection logic. We should ideally just error out in this
212 # case, but there are a couple problems:
213 # - VMs can take a while to boot up post provision, so SSH connections
214 # to moblab vms may not be available for ~2 minutes. This requires
215 # extended timeout in _verify_connectivity() so we don't get speed
216 # benefits from bailing early.
217 # - We need to make sure stopping here does not block repair flows.
218 try:
219 _verify_connectivity(connectivity_class, hostname, **args)
220 host_class = _detect_host(connectivity_class, hostname, **args)
Prathmesh Prabhu11063602018-12-20 11:13:21 -0800221 except (error.AutoservRunError, error.AutoservSSHTimeout):
222 logging.exception('Failed to verify connectivity to host.'
223 ' Skipping host auto detection logic.')
Prathmesh Prabhu723ca4f2018-08-28 14:54:22 -0700224 host_class = cros_host.CrosHost
225 logging.debug('Defaulting to CrosHost.')
jadmanski635b06f2008-09-05 20:26:44 +0000226
227 # create a custom host class for this machine and return an instance of it
Prathmesh Prabhuce4b88e2017-01-05 16:53:52 -0800228 classes = (host_class, connectivity_class)
229 custom_host_class = type("%s_host" % hostname, classes, {})
230 host_instance = custom_host_class(hostname, **args)
jadmanskid60321a2008-10-28 20:32:05 +0000231
232 # call job_start if this is the first time this host is being used
233 if hostname not in _started_hostnames:
234 host_instance.job_start()
235 _started_hostnames.add(hostname)
236
237 return host_instance
Kevin Cheng549beb42015-11-18 11:42:25 -0800238
239
Kevin Cheng3b111812015-12-15 11:52:08 -0800240def create_target_machine(machine, **kwargs):
Richard Barnette9db80682018-04-26 00:55:15 +0000241 """Create the target machine, accounting for containers.
Kevin Cheng3b111812015-12-15 11:52:08 -0800242
243 @param machine: A dict representing the test bed under test or a String
244 representing the testbed hostname (for legacy caller
245 support).
246 If it is a machine dict, the 'hostname' key is required.
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700247 Optional 'afe_host' key will pipe in afe_host
Kevin Cheng3b111812015-12-15 11:52:08 -0800248 from the autoserv runtime or the AFE.
249 @param kwargs: Keyword args to pass to the testbed initialization.
250
251 @returns: The target machine to be used for verify/repair.
252 """
Dan Shi6450e142016-03-11 11:52:20 -0800253 is_moblab = CONFIG.get_config_value('SSP', 'is_moblab', type=bool,
254 default=False)
255 hostname = machine['hostname'] if isinstance(machine, dict) else machine
256 if (utils.is_in_container() and is_moblab and
257 hostname in ['localhost', '127.0.0.1']):
258 hostname = CONFIG.get_config_value('SSP', 'host_container_ip', type=str,
259 default=None)
260 if isinstance(machine, dict):
261 machine['hostname'] = hostname
262 else:
263 machine = hostname
264 logging.debug('Hostname of machine is converted to %s for the test to '
265 'run inside a container.', hostname)
Kevin Cheng3b111812015-12-15 11:52:08 -0800266 return create_host(machine, **kwargs)