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