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.common_lib import error, global_config 8from autotest_lib.server import utils as server_utils 9from autotest_lib.server.hosts import cros_host, ssh_host 10from autotest_lib.server.hosts import moblab_host, sonic_host 11from autotest_lib.server.hosts import adb_host, testbed 12 13 14SSH_ENGINE = global_config.global_config.get_config_value('AUTOSERV', 15 'ssh_engine', 16 type=str) 17 18# Default ssh options used in creating a host. 19DEFAULT_SSH_USER = 'root' 20DEFAULT_SSH_PASS = '' 21DEFAULT_SSH_PORT = 22 22DEFAULT_SSH_VERBOSITY = '' 23DEFAULT_SSH_OPTIONS = '' 24 25# for tracking which hostnames have already had job_start called 26_started_hostnames = set() 27 28# A list of all the possible host types, ordered according to frequency of 29# host types in the lab, so the more common hosts don't incur a repeated ssh 30# overhead in checking for less common host types. 31host_types = [cros_host.CrosHost, moblab_host.MoblabHost, sonic_host.SonicHost, 32 adb_host.ADBHost,] 33OS_HOST_DICT = {'cros' : cros_host.CrosHost, 34 'android': adb_host.ADBHost} 35 36 37def _get_host_arguments(): 38 """Returns parameters needed to ssh into a host. 39 40 There are currently 2 use cases for creating a host. 41 1. Through the server_job, in which case the server_job injects 42 the appropriate ssh parameters into our name space and they 43 are available as the variables ssh_user, ssh_pass etc. 44 2. Directly through factory.create_host, in which case we use 45 the same defaults as used in the server job to create a host. 46 47 @returns: A tuple of parameters needed to create an ssh connection, ordered 48 as: ssh_user, ssh_pass, ssh_port, ssh_verbosity, ssh_options. 49 """ 50 g = globals() 51 return (g.get('ssh_user', DEFAULT_SSH_USER), 52 g.get('ssh_pass', DEFAULT_SSH_PASS), 53 g.get('ssh_port', DEFAULT_SSH_PORT), 54 g.get('ssh_verbosity_flag', DEFAULT_SSH_VERBOSITY), 55 g.get('ssh_options', DEFAULT_SSH_OPTIONS)) 56 57 58def _detect_host(connectivity_class, hostname, **args): 59 """Detect host type. 60 61 Goes through all the possible host classes, calling check_host with a 62 basic host object. Currently this is an ssh host, but theoretically it 63 can be any host object that the check_host method of appropriate host 64 type knows to use. 65 66 @param connectivity_class: connectivity class to use to talk to the host 67 (ParamikoHost or SSHHost) 68 @param hostname: A string representing the host name of the device. 69 @param args: Args that will be passed to the constructor of 70 the host class. 71 72 @returns: Class type of the first host class that returns True to the 73 check_host method. 74 """ 75 # TODO crbug.com/302026 (sbasi) - adjust this pathway for ADBHost in 76 # the future should a host require verify/repair. 77 with closing(connectivity_class(hostname, **args)) as host: 78 for host_module in host_types: 79 if host_module.check_host(host, timeout=10): 80 return host_module 81 82 logging.warning('Unable to apply conventional host detection methods, ' 83 'defaulting to chromeos host.') 84 return cros_host.CrosHost 85 86 87def _choose_connectivity_class(hostname, ssh_port): 88 """Choose a connectivity class for this hostname. 89 90 @param hostname: hostname that we need a connectivity class for. 91 @param ssh_port: SSH port to connect to the host. 92 93 @returns a connectivity host class. 94 """ 95 if (hostname == 'localhost' and ssh_port == DEFAULT_SSH_PORT): 96 return local_host.LocalHost 97 # by default assume we're using SSH support 98 elif SSH_ENGINE == 'paramiko': 99 # Not all systems have paramiko installed so only import paramiko host 100 # if the global_config settings call for it. 101 from autotest_lib.server.hosts import paramiko_host 102 return paramiko_host.ParamikoHost 103 elif SSH_ENGINE == 'raw_ssh': 104 return ssh_host.SSHHost 105 else: 106 raise error.AutoServError("Unknown SSH engine %s. Please verify the " 107 "value of the configuration key 'ssh_engine' " 108 "on autotest's global_config.ini file." % 109 SSH_ENGINE) 110 111 112# TODO(kevcheng): Update the creation method so it's not a research project 113# determining the class inheritance model. 114def create_host(machine, host_class=None, connectivity_class=None, **args): 115 """Create a host object. 116 117 This method mixes host classes that are needed into a new subclass 118 and creates a instance of the new class. 119 120 @param machine: A dict representing the device under test or a String 121 representing the DUT hostname (for legacy caller support). 122 If it is a machine dict, the 'hostname' key is required. 123 Optional 'host_attributes' key will pipe in host_attributes 124 from the autoserv runtime or the AFE. 125 @param host_class: Host class to use, if None, will attempt to detect 126 the correct class. 127 @param connectivity_class: Connectivity class to use, if None will decide 128 based off of hostname and config settings. 129 @param args: Args that will be passed to the constructor of 130 the new host class. 131 132 @returns: A host object which is an instance of the newly created 133 host class. 134 """ 135 hostname, host_attributes = server_utils.get_host_info_from_machine( 136 machine) 137 args['host_attributes'] = host_attributes 138 ssh_user, ssh_pass, ssh_port, ssh_verbosity_flag, ssh_options = \ 139 _get_host_arguments() 140 141 hostname, args['user'], args['password'], ssh_port = \ 142 server_utils.parse_machine(hostname, ssh_user, ssh_pass, ssh_port) 143 args['ssh_verbosity_flag'] = ssh_verbosity_flag 144 args['ssh_options'] = ssh_options 145 args['port'] = ssh_port 146 147 if not connectivity_class: 148 connectivity_class = _choose_connectivity_class(hostname, ssh_port) 149 host_attributes = args.get('host_attributes', {}) 150 host_class = host_class or OS_HOST_DICT.get(host_attributes.get('os_type')) 151 if host_class: 152 classes = [host_class, connectivity_class] 153 else: 154 classes = [_detect_host(connectivity_class, hostname, **args), 155 connectivity_class] 156 157 # create a custom host class for this machine and return an instance of it 158 host_class = type("%s_host" % hostname, tuple(classes), {}) 159 host_instance = host_class(hostname, **args) 160 161 # call job_start if this is the first time this host is being used 162 if hostname not in _started_hostnames: 163 host_instance.job_start() 164 _started_hostnames.add(hostname) 165 166 return host_instance 167 168 169def create_testbed(machine, **kwargs): 170 """Create the testbed object. 171 172 @param machine: A dict representing the test bed under test or a String 173 representing the testbed hostname (for legacy caller 174 support). 175 If it is a machine dict, the 'hostname' key is required. 176 Optional 'host_attributes' key will pipe in host_attributes 177 from the autoserv runtime or the AFE. 178 @param kwargs: Keyword args to pass to the testbed initialization. 179 180 @returns: The testbed object with all associated host objects instantiated. 181 """ 182 hostname, host_attributes = server_utils.get_host_info_from_machine( 183 machine) 184 kwargs['host_attributes'] = host_attributes 185 return testbed.TestBed(hostname, **kwargs) 186 187 188def create_target_machine(machine, **kwargs): 189 """Create the target machine which could be a testbed or a *Host. 190 191 @param machine: A dict representing the test bed under test or a String 192 representing the testbed hostname (for legacy caller 193 support). 194 If it is a machine dict, the 'hostname' key is required. 195 Optional 'host_attributes' key will pipe in host_attributes 196 from the autoserv runtime or the AFE. 197 @param kwargs: Keyword args to pass to the testbed initialization. 198 199 @returns: The target machine to be used for verify/repair. 200 """ 201 # TODO(kevcheng): We'll want to have a smarter way of figuring out which 202 # host to create (checking host labels). 203 if server_utils.machine_is_testbed(machine): 204 return create_testbed(machine, **kwargs) 205 return create_host(machine, **kwargs) 206