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