Fang Deng | d1c2b73 | 2013-08-20 12:59:46 -0700 | [diff] [blame] | 1 | """Provides a factory method to create a host object.""" |
| 2 | |
beeps | 46dadc9 | 2013-11-07 14:07:10 -0800 | [diff] [blame] | 3 | import logging |
Aviv Keshet | 5189dd5 | 2013-10-21 12:43:57 -0700 | [diff] [blame] | 4 | from contextlib import closing |
beeps | 46dadc9 | 2013-11-07 14:07:10 -0800 | [diff] [blame] | 5 | |
Simran Basi | 14622bb | 2015-11-25 13:23:40 -0800 | [diff] [blame] | 6 | from autotest_lib.client.bin import local_host |
Dan Shi | 6450e14 | 2016-03-11 11:52:20 -0800 | [diff] [blame] | 7 | from autotest_lib.client.bin import utils |
Prathmesh Prabhu | bbb2456 | 2017-03-10 14:22:59 -0800 | [diff] [blame] | 8 | from autotest_lib.client.common_lib import error |
| 9 | from autotest_lib.client.common_lib import global_config |
J. Richard Barnette | 1c3b853 | 2015-11-18 19:02:02 -0800 | [diff] [blame] | 10 | from autotest_lib.server import utils as server_utils |
Kevin Cheng | 84a71ba | 2016-07-14 11:03:57 -0700 | [diff] [blame] | 11 | from autotest_lib.server.cros.dynamic_suite import constants |
Prathmesh Prabhu | bbb2456 | 2017-03-10 14:22:59 -0800 | [diff] [blame] | 12 | from autotest_lib.server.hosts import adb_host |
| 13 | from autotest_lib.server.hosts import cros_host |
| 14 | from autotest_lib.server.hosts import emulated_adb_host |
| 15 | from autotest_lib.server.hosts import host_info |
Prathmesh Prabhu | bbb2456 | 2017-03-10 14:22:59 -0800 | [diff] [blame] | 16 | from autotest_lib.server.hosts import jetstream_host |
| 17 | from autotest_lib.server.hosts import moblab_host |
| 18 | from autotest_lib.server.hosts import sonic_host |
| 19 | from autotest_lib.server.hosts import ssh_host |
| 20 | from autotest_lib.server.hosts import testbed |
mbligh | e48bcfb | 2008-11-11 17:09:44 +0000 | [diff] [blame] | 21 | |
Fang Deng | d1c2b73 | 2013-08-20 12:59:46 -0700 | [diff] [blame] | 22 | |
Dan Shi | 6450e14 | 2016-03-11 11:52:20 -0800 | [diff] [blame] | 23 | CONFIG = global_config.global_config |
| 24 | |
| 25 | SSH_ENGINE = CONFIG.get_config_value('AUTOSERV', 'ssh_engine', type=str) |
beeps | d067268 | 2013-09-16 17:32:16 -0700 | [diff] [blame] | 26 | |
| 27 | # Default ssh options used in creating a host. |
| 28 | DEFAULT_SSH_USER = 'root' |
| 29 | DEFAULT_SSH_PASS = '' |
| 30 | DEFAULT_SSH_PORT = 22 |
| 31 | DEFAULT_SSH_VERBOSITY = '' |
| 32 | DEFAULT_SSH_OPTIONS = '' |
| 33 | |
jadmanski | d60321a | 2008-10-28 20:32:05 +0000 | [diff] [blame] | 34 | # for tracking which hostnames have already had job_start called |
| 35 | _started_hostnames = set() |
| 36 | |
beeps | 46dadc9 | 2013-11-07 14:07:10 -0800 | [diff] [blame] | 37 | # 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 Goodby | 468de25 | 2017-06-08 17:22:53 -0700 | [diff] [blame] | 40 | host_types = [cros_host.CrosHost, moblab_host.MoblabHost, |
| 41 | jetstream_host.JetstreamHost, sonic_host.SonicHost, |
Simran Basi | e5f7ae4 | 2014-06-26 15:44:06 -0700 | [diff] [blame] | 42 | adb_host.ADBHost,] |
Kevin Cheng | 410b3cf | 2016-08-02 10:21:52 -0700 | [diff] [blame] | 43 | OS_HOST_DICT = {'android': adb_host.ADBHost, |
Justin Giorgi | dd05a94 | 2016-07-05 20:53:12 -0700 | [diff] [blame] | 44 | 'brillo': adb_host.ADBHost, |
Kevin Cheng | 410b3cf | 2016-08-02 10:21:52 -0700 | [diff] [blame] | 45 | 'cros' : cros_host.CrosHost, |
| 46 | 'emulated_brillo': emulated_adb_host.EmulatedADBHost, |
Laurence Goodby | 778c9a4 | 2017-05-24 19:24:07 -0700 | [diff] [blame] | 47 | 'jetstream': jetstream_host.JetstreamHost, |
Kevin Cheng | 410b3cf | 2016-08-02 10:21:52 -0700 | [diff] [blame] | 48 | 'moblab': moblab_host.MoblabHost} |
beeps | 46dadc9 | 2013-11-07 14:07:10 -0800 | [diff] [blame] | 49 | |
Fang Deng | d1c2b73 | 2013-08-20 12:59:46 -0700 | [diff] [blame] | 50 | |
Justin Giorgi | 5208eaa | 2016-07-02 20:12:12 -0700 | [diff] [blame] | 51 | def _get_host_arguments(machine): |
| 52 | """Get parameters to construct a host object. |
beeps | d067268 | 2013-09-16 17:32:16 -0700 | [diff] [blame] | 53 | |
| 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 Giorgi | 5208eaa | 2016-07-02 20:12:12 -0700 | [diff] [blame] | 61 | @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. |
beeps | d067268 | 2013-09-16 17:32:16 -0700 | [diff] [blame] | 65 | """ |
Hidehiko Abe | 0689330 | 2017-06-24 07:32:38 +0900 | [diff] [blame^] | 66 | hostname, afe_host = server_utils.get_host_info_from_machine(machine) |
| 67 | connection_pool = server_utils.get_connection_pool_from_machine(machine) |
Prathmesh Prabhu | bbb2456 | 2017-03-10 14:22:59 -0800 | [diff] [blame] | 68 | host_info_store = host_info.get_store_from_machine(machine) |
| 69 | info = host_info_store.get() |
Justin Giorgi | 5208eaa | 2016-07-02 20:12:12 -0700 | [diff] [blame] | 70 | |
beeps | d067268 | 2013-09-16 17:32:16 -0700 | [diff] [blame] | 71 | g = globals() |
Prathmesh Prabhu | bbb2456 | 2017-03-10 14:22:59 -0800 | [diff] [blame] | 72 | 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 Giorgi | 5208eaa | 2016-07-02 20:12:12 -0700 | [diff] [blame] | 82 | |
| 83 | hostname, user, password, port = server_utils.parse_machine(hostname, user, |
| 84 | password, port) |
| 85 | |
Prathmesh Prabhu | 8b5065d | 2017-01-10 17:13:01 -0800 | [diff] [blame] | 86 | host_args = { |
| 87 | 'hostname': hostname, |
| 88 | 'afe_host': afe_host, |
Prathmesh Prabhu | bbb2456 | 2017-03-10 14:22:59 -0800 | [diff] [blame] | 89 | 'host_info_store': host_info_store, |
Prathmesh Prabhu | 8b5065d | 2017-01-10 17:13:01 -0800 | [diff] [blame] | 90 | 'user': user, |
| 91 | 'password': password, |
| 92 | 'port': int(port), |
Justin Giorgi | 5208eaa | 2016-07-02 20:12:12 -0700 | [diff] [blame] | 93 | 'ssh_verbosity_flag': ssh_verbosity_flag, |
Prathmesh Prabhu | 8b5065d | 2017-01-10 17:13:01 -0800 | [diff] [blame] | 94 | 'ssh_options': ssh_options, |
Hidehiko Abe | 0689330 | 2017-06-24 07:32:38 +0900 | [diff] [blame^] | 95 | 'connection_pool': connection_pool, |
Prathmesh Prabhu | 8b5065d | 2017-01-10 17:13:01 -0800 | [diff] [blame] | 96 | } |
Prathmesh Prabhu | 8b5065d | 2017-01-10 17:13:01 -0800 | [diff] [blame] | 97 | return host_args |
beeps | d067268 | 2013-09-16 17:32:16 -0700 | [diff] [blame] | 98 | |
| 99 | |
Simran Basi | 724b8a5 | 2013-09-30 11:19:31 -0700 | [diff] [blame] | 100 | def _detect_host(connectivity_class, hostname, **args): |
| 101 | """Detect host type. |
| 102 | |
beeps | 46dadc9 | 2013-11-07 14:07:10 -0800 | [diff] [blame] | 103 | 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 Basi | 724b8a5 | 2013-09-30 11:19:31 -0700 | [diff] [blame] | 107 | |
| 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 | |
beeps | 46dadc9 | 2013-11-07 14:07:10 -0800 | [diff] [blame] | 114 | @returns: Class type of the first host class that returns True to the |
| 115 | check_host method. |
Simran Basi | 724b8a5 | 2013-09-30 11:19:31 -0700 | [diff] [blame] | 116 | """ |
beeps | 46dadc9 | 2013-11-07 14:07:10 -0800 | [diff] [blame] | 117 | # 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 Basi | 724b8a5 | 2013-09-30 11:19:31 -0700 | [diff] [blame] | 127 | |
| 128 | |
Gwendal Grignou | 0e72735 | 2016-01-25 14:25:41 -0800 | [diff] [blame] | 129 | def _choose_connectivity_class(hostname, ssh_port): |
Simran Basi | 2a5dc25 | 2015-12-16 13:12:22 -0800 | [diff] [blame] | 130 | """Choose a connectivity class for this hostname. |
| 131 | |
| 132 | @param hostname: hostname that we need a connectivity class for. |
Gwendal Grignou | 0e72735 | 2016-01-25 14:25:41 -0800 | [diff] [blame] | 133 | @param ssh_port: SSH port to connect to the host. |
Simran Basi | 2a5dc25 | 2015-12-16 13:12:22 -0800 | [diff] [blame] | 134 | |
| 135 | @returns a connectivity host class. |
| 136 | """ |
Gwendal Grignou | 0e72735 | 2016-01-25 14:25:41 -0800 | [diff] [blame] | 137 | if (hostname == 'localhost' and ssh_port == DEFAULT_SSH_PORT): |
Simran Basi | 2a5dc25 | 2015-12-16 13:12:22 -0800 | [diff] [blame] | 138 | return local_host.LocalHost |
| 139 | # by default assume we're using SSH support |
Simran Basi | 2a5dc25 | 2015-12-16 13:12:22 -0800 | [diff] [blame] | 140 | elif SSH_ENGINE == 'raw_ssh': |
| 141 | return ssh_host.SSHHost |
| 142 | else: |
Justin Giorgi | 5208eaa | 2016-07-02 20:12:12 -0700 | [diff] [blame] | 143 | raise error.AutoservError("Unknown SSH engine %s. Please verify the " |
Simran Basi | 2a5dc25 | 2015-12-16 13:12:22 -0800 | [diff] [blame] | 144 | "value of the configuration key 'ssh_engine' " |
| 145 | "on autotest's global_config.ini file." % |
| 146 | SSH_ENGINE) |
| 147 | |
| 148 | |
Kevin Cheng | 85e864a | 2015-11-30 11:49:34 -0800 | [diff] [blame] | 149 | # TODO(kevcheng): Update the creation method so it's not a research project |
| 150 | # determining the class inheritance model. |
Simran Basi | 2a5dc25 | 2015-12-16 13:12:22 -0800 | [diff] [blame] | 151 | def create_host(machine, host_class=None, connectivity_class=None, **args): |
Fang Deng | d1c2b73 | 2013-08-20 12:59:46 -0700 | [diff] [blame] | 152 | """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 Basi | 1bf60eb | 2015-12-01 16:39:29 -0800 | [diff] [blame] | 157 | @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 Cheng | 05ae2a4 | 2016-06-06 10:12:48 -0700 | [diff] [blame] | 160 | Optional 'afe_host' key will pipe in afe_host |
Simran Basi | 1bf60eb | 2015-12-01 16:39:29 -0800 | [diff] [blame] | 161 | from the autoserv runtime or the AFE. |
Simran Basi | a5522a3 | 2015-10-06 11:01:24 -0700 | [diff] [blame] | 162 | @param host_class: Host class to use, if None, will attempt to detect |
| 163 | the correct class. |
Simran Basi | 2a5dc25 | 2015-12-16 13:12:22 -0800 | [diff] [blame] | 164 | @param connectivity_class: Connectivity class to use, if None will decide |
| 165 | based off of hostname and config settings. |
Fang Deng | d1c2b73 | 2013-08-20 12:59:46 -0700 | [diff] [blame] | 166 | @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 Giorgi | 5208eaa | 2016-07-02 20:12:12 -0700 | [diff] [blame] | 172 | 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 Cheng | 05ae2a4 | 2016-06-06 10:12:48 -0700 | [diff] [blame] | 176 | |
| 177 | host_os = None |
Kevin Cheng | 84a71ba | 2016-07-14 11:03:57 -0700 | [diff] [blame] | 178 | full_os_prefix = constants.OS_PREFIX + ':' |
Kevin Cheng | 05ae2a4 | 2016-06-06 10:12:48 -0700 | [diff] [blame] | 179 | # Let's grab the os from the labels if we can for host class detection. |
| 180 | for label in afe_host.labels: |
Kevin Cheng | 84a71ba | 2016-07-14 11:03:57 -0700 | [diff] [blame] | 181 | if label.startswith(full_os_prefix): |
| 182 | host_os = label[len(full_os_prefix):] |
Kevin Cheng | 05ae2a4 | 2016-06-06 10:12:48 -0700 | [diff] [blame] | 183 | break |
Simran Basi | 431010f | 2013-09-04 10:42:41 -0700 | [diff] [blame] | 184 | |
Simran Basi | 2a5dc25 | 2015-12-16 13:12:22 -0800 | [diff] [blame] | 185 | if not connectivity_class: |
Justin Giorgi | 5208eaa | 2016-07-02 20:12:12 -0700 | [diff] [blame] | 186 | connectivity_class = _choose_connectivity_class(hostname, args['port']) |
Kevin Cheng | 05ae2a4 | 2016-06-06 10:12:48 -0700 | [diff] [blame] | 187 | # 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 Prabhu | ce4b88e | 2017-01-05 16:53:52 -0800 | [diff] [blame] | 190 | or OS_HOST_DICT.get(host_os) |
| 191 | or _detect_host(connectivity_class, hostname, **args)) |
jadmanski | 635b06f | 2008-09-05 20:26:44 +0000 | [diff] [blame] | 192 | |
| 193 | # create a custom host class for this machine and return an instance of it |
Prathmesh Prabhu | ce4b88e | 2017-01-05 16:53:52 -0800 | [diff] [blame] | 194 | classes = (host_class, connectivity_class) |
| 195 | custom_host_class = type("%s_host" % hostname, classes, {}) |
| 196 | host_instance = custom_host_class(hostname, **args) |
jadmanski | d60321a | 2008-10-28 20:32:05 +0000 | [diff] [blame] | 197 | |
| 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 Cheng | 549beb4 | 2015-11-18 11:42:25 -0800 | [diff] [blame] | 204 | |
| 205 | |
Simran Basi | 52c785e | 2015-12-15 15:25:10 -0800 | [diff] [blame] | 206 | def create_testbed(machine, **kwargs): |
Kevin Cheng | 549beb4 | 2015-11-18 11:42:25 -0800 | [diff] [blame] | 207 | """Create the testbed object. |
| 208 | |
Simran Basi | 52c785e | 2015-12-15 15:25:10 -0800 | [diff] [blame] | 209 | @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 Cheng | 05ae2a4 | 2016-06-06 10:12:48 -0700 | [diff] [blame] | 213 | Optional 'afe_host' key will pipe in afe_host from |
| 214 | the afe_host object from the autoserv runtime or the AFE. |
Simran Basi | debeb68 | 2015-12-01 11:39:00 -0800 | [diff] [blame] | 215 | @param kwargs: Keyword args to pass to the testbed initialization. |
Kevin Cheng | 549beb4 | 2015-11-18 11:42:25 -0800 | [diff] [blame] | 216 | |
| 217 | @returns: The testbed object with all associated host objects instantiated. |
| 218 | """ |
Justin Giorgi | 5208eaa | 2016-07-02 20:12:12 -0700 | [diff] [blame] | 219 | detected_args = _get_host_arguments(machine) |
| 220 | hostname = detected_args.pop('hostname') |
| 221 | kwargs.update(detected_args) |
Kevin Cheng | 3b11181 | 2015-12-15 11:52:08 -0800 | [diff] [blame] | 222 | return testbed.TestBed(hostname, **kwargs) |
| 223 | |
| 224 | |
| 225 | def 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 Cheng | 05ae2a4 | 2016-06-06 10:12:48 -0700 | [diff] [blame] | 232 | Optional 'afe_host' key will pipe in afe_host |
Kevin Cheng | 3b11181 | 2015-12-15 11:52:08 -0800 | [diff] [blame] | 233 | 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 Shi | 6450e14 | 2016-03-11 11:52:20 -0800 | [diff] [blame] | 238 | # 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 Cheng | 3b11181 | 2015-12-15 11:52:08 -0800 | [diff] [blame] | 256 | # 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) |