blob: 09a34a25882ce3c845b280cae6e24d53eb43ddf5 [file] [log] [blame]
Fang Dengd1c2b732013-08-20 12:59:46 -07001"""Provides a factory method to create a host object."""
2
beeps46dadc92013-11-07 14:07:10 -08003import logging
Aviv Keshet5189dd52013-10-21 12:43:57 -07004from contextlib import closing
beeps46dadc92013-11-07 14:07:10 -08005
Simran Basi14622bb2015-11-25 13:23:40 -08006from autotest_lib.client.bin import local_host
Dan Shi6450e142016-03-11 11:52:20 -08007from autotest_lib.client.bin import utils
Prathmesh Prabhubbb24562017-03-10 14:22:59 -08008from autotest_lib.client.common_lib import error
9from autotest_lib.client.common_lib import global_config
J. Richard Barnette1c3b8532015-11-18 19:02:02 -080010from autotest_lib.server import utils as server_utils
Kevin Cheng84a71ba2016-07-14 11:03:57 -070011from autotest_lib.server.cros.dynamic_suite import constants
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080012from 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
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080016from autotest_lib.server.hosts import jetstream_host
17from autotest_lib.server.hosts import moblab_host
18from autotest_lib.server.hosts import sonic_host
19from autotest_lib.server.hosts import ssh_host
20from autotest_lib.server.hosts import testbed
mblighe48bcfb2008-11-11 17:09:44 +000021
Fang Dengd1c2b732013-08-20 12:59:46 -070022
Dan Shi6450e142016-03-11 11:52:20 -080023CONFIG = global_config.global_config
24
25SSH_ENGINE = CONFIG.get_config_value('AUTOSERV', 'ssh_engine', type=str)
beepsd0672682013-09-16 17:32:16 -070026
27# Default ssh options used in creating a host.
28DEFAULT_SSH_USER = 'root'
29DEFAULT_SSH_PASS = ''
30DEFAULT_SSH_PORT = 22
31DEFAULT_SSH_VERBOSITY = ''
32DEFAULT_SSH_OPTIONS = ''
33
jadmanskid60321a2008-10-28 20:32:05 +000034# for tracking which hostnames have already had job_start called
35_started_hostnames = set()
36
beeps46dadc92013-11-07 14:07:10 -080037# A list of all the possible host types, ordered according to frequency of
38# host types in the lab, so the more common hosts don't incur a repeated ssh
39# overhead in checking for less common host types.
Laurence Goodby468de252017-06-08 17:22:53 -070040host_types = [cros_host.CrosHost, moblab_host.MoblabHost,
41 jetstream_host.JetstreamHost, sonic_host.SonicHost,
Simran Basie5f7ae42014-06-26 15:44:06 -070042 adb_host.ADBHost,]
Kevin Cheng410b3cf2016-08-02 10:21:52 -070043OS_HOST_DICT = {'android': adb_host.ADBHost,
Justin Giorgidd05a942016-07-05 20:53:12 -070044 'brillo': adb_host.ADBHost,
Kevin Cheng410b3cf2016-08-02 10:21:52 -070045 'cros' : cros_host.CrosHost,
46 'emulated_brillo': emulated_adb_host.EmulatedADBHost,
Laurence Goodby778c9a42017-05-24 19:24:07 -070047 'jetstream': jetstream_host.JetstreamHost,
Kevin Cheng410b3cf2016-08-02 10:21:52 -070048 'moblab': moblab_host.MoblabHost}
beeps46dadc92013-11-07 14:07:10 -080049
Fang Dengd1c2b732013-08-20 12:59:46 -070050
Justin Giorgi5208eaa2016-07-02 20:12:12 -070051def _get_host_arguments(machine):
52 """Get parameters to construct a host object.
beepsd0672682013-09-16 17:32:16 -070053
54 There are currently 2 use cases for creating a host.
55 1. Through the server_job, in which case the server_job injects
56 the appropriate ssh parameters into our name space and they
57 are available as the variables ssh_user, ssh_pass etc.
58 2. Directly through factory.create_host, in which case we use
59 the same defaults as used in the server job to create a host.
60
Justin Giorgi5208eaa2016-07-02 20:12:12 -070061 @param machine: machine dict
62 @return: A dictionary containing arguments for host specifically hostname,
63 afe_host, user, password, port, ssh_verbosity_flag and
64 ssh_options.
beepsd0672682013-09-16 17:32:16 -070065 """
Hidehiko Abe06893302017-06-24 07:32:38 +090066 hostname, afe_host = server_utils.get_host_info_from_machine(machine)
67 connection_pool = server_utils.get_connection_pool_from_machine(machine)
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080068 host_info_store = host_info.get_store_from_machine(machine)
69 info = host_info_store.get()
Justin Giorgi5208eaa2016-07-02 20:12:12 -070070
beepsd0672682013-09-16 17:32:16 -070071 g = globals()
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080072 user = info.attributes.get('ssh_user', g.get('ssh_user', DEFAULT_SSH_USER))
73 password = info.attributes.get('ssh_pass', g.get('ssh_pass',
74 DEFAULT_SSH_PASS))
75 port = info.attributes.get('ssh_port', g.get('ssh_port', DEFAULT_SSH_PORT))
76 ssh_verbosity_flag = info.attributes.get('ssh_verbosity_flag',
77 g.get('ssh_verbosity_flag',
78 DEFAULT_SSH_VERBOSITY))
79 ssh_options = info.attributes.get('ssh_options',
80 g.get('ssh_options',
81 DEFAULT_SSH_OPTIONS))
Justin Giorgi5208eaa2016-07-02 20:12:12 -070082
83 hostname, user, password, port = server_utils.parse_machine(hostname, user,
84 password, port)
85
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080086 host_args = {
87 'hostname': hostname,
88 'afe_host': afe_host,
Prathmesh Prabhubbb24562017-03-10 14:22:59 -080089 'host_info_store': host_info_store,
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080090 'user': user,
91 'password': password,
92 'port': int(port),
Justin Giorgi5208eaa2016-07-02 20:12:12 -070093 'ssh_verbosity_flag': ssh_verbosity_flag,
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080094 'ssh_options': ssh_options,
Hidehiko Abe06893302017-06-24 07:32:38 +090095 'connection_pool': connection_pool,
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080096 }
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080097 return host_args
beepsd0672682013-09-16 17:32:16 -070098
99
Simran Basi724b8a52013-09-30 11:19:31 -0700100def _detect_host(connectivity_class, hostname, **args):
101 """Detect host type.
102
beeps46dadc92013-11-07 14:07:10 -0800103 Goes through all the possible host classes, calling check_host with a
104 basic host object. Currently this is an ssh host, but theoretically it
105 can be any host object that the check_host method of appropriate host
106 type knows to use.
Simran Basi724b8a52013-09-30 11:19:31 -0700107
108 @param connectivity_class: connectivity class to use to talk to the host
109 (ParamikoHost or SSHHost)
110 @param hostname: A string representing the host name of the device.
111 @param args: Args that will be passed to the constructor of
112 the host class.
113
beeps46dadc92013-11-07 14:07:10 -0800114 @returns: Class type of the first host class that returns True to the
115 check_host method.
Simran Basi724b8a52013-09-30 11:19:31 -0700116 """
beeps46dadc92013-11-07 14:07:10 -0800117 # TODO crbug.com/302026 (sbasi) - adjust this pathway for ADBHost in
118 # the future should a host require verify/repair.
119 with closing(connectivity_class(hostname, **args)) as host:
120 for host_module in host_types:
121 if host_module.check_host(host, timeout=10):
122 return host_module
123
124 logging.warning('Unable to apply conventional host detection methods, '
125 'defaulting to chromeos host.')
126 return cros_host.CrosHost
Simran Basi724b8a52013-09-30 11:19:31 -0700127
128
Gwendal Grignou0e727352016-01-25 14:25:41 -0800129def _choose_connectivity_class(hostname, ssh_port):
Simran Basi2a5dc252015-12-16 13:12:22 -0800130 """Choose a connectivity class for this hostname.
131
132 @param hostname: hostname that we need a connectivity class for.
Gwendal Grignou0e727352016-01-25 14:25:41 -0800133 @param ssh_port: SSH port to connect to the host.
Simran Basi2a5dc252015-12-16 13:12:22 -0800134
135 @returns a connectivity host class.
136 """
Gwendal Grignou0e727352016-01-25 14:25:41 -0800137 if (hostname == 'localhost' and ssh_port == DEFAULT_SSH_PORT):
Simran Basi2a5dc252015-12-16 13:12:22 -0800138 return local_host.LocalHost
139 # by default assume we're using SSH support
Simran Basi2a5dc252015-12-16 13:12:22 -0800140 elif SSH_ENGINE == 'raw_ssh':
141 return ssh_host.SSHHost
142 else:
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700143 raise error.AutoservError("Unknown SSH engine %s. Please verify the "
Simran Basi2a5dc252015-12-16 13:12:22 -0800144 "value of the configuration key 'ssh_engine' "
145 "on autotest's global_config.ini file." %
146 SSH_ENGINE)
147
148
Kevin Cheng85e864a2015-11-30 11:49:34 -0800149# TODO(kevcheng): Update the creation method so it's not a research project
150# determining the class inheritance model.
Simran Basi2a5dc252015-12-16 13:12:22 -0800151def create_host(machine, host_class=None, connectivity_class=None, **args):
Fang Dengd1c2b732013-08-20 12:59:46 -0700152 """Create a host object.
153
154 This method mixes host classes that are needed into a new subclass
155 and creates a instance of the new class.
156
Simran Basi1bf60eb2015-12-01 16:39:29 -0800157 @param machine: A dict representing the device under test or a String
158 representing the DUT hostname (for legacy caller support).
159 If it is a machine dict, the 'hostname' key is required.
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700160 Optional 'afe_host' key will pipe in afe_host
Simran Basi1bf60eb2015-12-01 16:39:29 -0800161 from the autoserv runtime or the AFE.
Simran Basia5522a32015-10-06 11:01:24 -0700162 @param host_class: Host class to use, if None, will attempt to detect
163 the correct class.
Simran Basi2a5dc252015-12-16 13:12:22 -0800164 @param connectivity_class: Connectivity class to use, if None will decide
165 based off of hostname and config settings.
Fang Dengd1c2b732013-08-20 12:59:46 -0700166 @param args: Args that will be passed to the constructor of
167 the new host class.
168
169 @returns: A host object which is an instance of the newly created
170 host class.
171 """
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700172 detected_args = _get_host_arguments(machine)
173 hostname = detected_args.pop('hostname')
174 afe_host = detected_args['afe_host']
175 args.update(detected_args)
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700176
177 host_os = None
Kevin Cheng84a71ba2016-07-14 11:03:57 -0700178 full_os_prefix = constants.OS_PREFIX + ':'
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700179 # Let's grab the os from the labels if we can for host class detection.
180 for label in afe_host.labels:
Kevin Cheng84a71ba2016-07-14 11:03:57 -0700181 if label.startswith(full_os_prefix):
182 host_os = label[len(full_os_prefix):]
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700183 break
Simran Basi431010f2013-09-04 10:42:41 -0700184
Simran Basi2a5dc252015-12-16 13:12:22 -0800185 if not connectivity_class:
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700186 connectivity_class = _choose_connectivity_class(hostname, args['port'])
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700187 # TODO(kevcheng): get rid of the host detection using host attributes.
188 host_class = (host_class
189 or OS_HOST_DICT.get(afe_host.attributes.get('os_type'))
Prathmesh Prabhuce4b88e2017-01-05 16:53:52 -0800190 or OS_HOST_DICT.get(host_os)
191 or _detect_host(connectivity_class, hostname, **args))
jadmanski635b06f2008-09-05 20:26:44 +0000192
193 # create a custom host class for this machine and return an instance of it
Prathmesh Prabhuce4b88e2017-01-05 16:53:52 -0800194 classes = (host_class, connectivity_class)
195 custom_host_class = type("%s_host" % hostname, classes, {})
196 host_instance = custom_host_class(hostname, **args)
jadmanskid60321a2008-10-28 20:32:05 +0000197
198 # call job_start if this is the first time this host is being used
199 if hostname not in _started_hostnames:
200 host_instance.job_start()
201 _started_hostnames.add(hostname)
202
203 return host_instance
Kevin Cheng549beb42015-11-18 11:42:25 -0800204
205
Simran Basi52c785e2015-12-15 15:25:10 -0800206def create_testbed(machine, **kwargs):
Kevin Cheng549beb42015-11-18 11:42:25 -0800207 """Create the testbed object.
208
Simran Basi52c785e2015-12-15 15:25:10 -0800209 @param machine: A dict representing the test bed under test or a String
210 representing the testbed hostname (for legacy caller
211 support).
212 If it is a machine dict, the 'hostname' key is required.
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700213 Optional 'afe_host' key will pipe in afe_host from
214 the afe_host object from the autoserv runtime or the AFE.
Simran Basidebeb682015-12-01 11:39:00 -0800215 @param kwargs: Keyword args to pass to the testbed initialization.
Kevin Cheng549beb42015-11-18 11:42:25 -0800216
217 @returns: The testbed object with all associated host objects instantiated.
218 """
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700219 detected_args = _get_host_arguments(machine)
220 hostname = detected_args.pop('hostname')
221 kwargs.update(detected_args)
Kevin Cheng3b111812015-12-15 11:52:08 -0800222 return testbed.TestBed(hostname, **kwargs)
223
224
225def create_target_machine(machine, **kwargs):
226 """Create the target machine which could be a testbed or a *Host.
227
228 @param machine: A dict representing the test bed under test or a String
229 representing the testbed hostname (for legacy caller
230 support).
231 If it is a machine dict, the 'hostname' key is required.
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700232 Optional 'afe_host' key will pipe in afe_host
Kevin Cheng3b111812015-12-15 11:52:08 -0800233 from the autoserv runtime or the AFE.
234 @param kwargs: Keyword args to pass to the testbed initialization.
235
236 @returns: The target machine to be used for verify/repair.
237 """
Dan Shi6450e142016-03-11 11:52:20 -0800238 # For Brillo/Android devices connected to moblab, the `machine` name is
239 # either `localhost` or `127.0.0.1`. It needs to be translated to the host
240 # container IP if the code is running inside a container. This way, autoserv
241 # can ssh to the moblab and run actual adb/fastboot commands.
242 is_moblab = CONFIG.get_config_value('SSP', 'is_moblab', type=bool,
243 default=False)
244 hostname = machine['hostname'] if isinstance(machine, dict) else machine
245 if (utils.is_in_container() and is_moblab and
246 hostname in ['localhost', '127.0.0.1']):
247 hostname = CONFIG.get_config_value('SSP', 'host_container_ip', type=str,
248 default=None)
249 if isinstance(machine, dict):
250 machine['hostname'] = hostname
251 else:
252 machine = hostname
253 logging.debug('Hostname of machine is converted to %s for the test to '
254 'run inside a container.', hostname)
255
Kevin Cheng3b111812015-12-15 11:52:08 -0800256 # TODO(kevcheng): We'll want to have a smarter way of figuring out which
257 # host to create (checking host labels).
258 if server_utils.machine_is_testbed(machine):
259 return create_testbed(machine, **kwargs)
260 return create_host(machine, **kwargs)