factory.py revision 3b11181c9ea282d584663d362bcfe2d1b4f9ba51
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): 88 """Choose a connectivity class for this hostname. 89 90 @param hostname: hostname that we need a connectivity class for. 91 92 @returns a connectivity host class. 93 """ 94 if hostname == 'localhost': 95 return local_host.LocalHost 96 # by default assume we're using SSH support 97 elif SSH_ENGINE == 'paramiko': 98 # Not all systems have paramiko installed so only import paramiko host 99 # if the global_config settings call for it. 100 from autotest_lib.server.hosts import paramiko_host 101 return paramiko_host.ParamikoHost 102 elif SSH_ENGINE == 'raw_ssh': 103 return ssh_host.SSHHost 104 else: 105 raise error.AutoServError("Unknown SSH engine %s. Please verify the " 106 "value of the configuration key 'ssh_engine' " 107 "on autotest's global_config.ini file." % 108 SSH_ENGINE) 109 110 111# TODO(kevcheng): Update the creation method so it's not a research project 112# determining the class inheritance model. 113def create_host(machine, host_class=None, connectivity_class=None, **args): 114 """Create a host object. 115 116 This method mixes host classes that are needed into a new subclass 117 and creates a instance of the new class. 118 119 @param machine: A dict representing the device under test or a String 120 representing the DUT hostname (for legacy caller support). 121 If it is a machine dict, the 'hostname' key is required. 122 Optional 'host_attributes' key will pipe in host_attributes 123 from the autoserv runtime or the AFE. 124 @param host_class: Host class to use, if None, will attempt to detect 125 the correct class. 126 @param connectivity_class: Connectivity class to use, if None will decide 127 based off of hostname and config settings. 128 @param args: Args that will be passed to the constructor of 129 the new host class. 130 131 @returns: A host object which is an instance of the newly created 132 host class. 133 """ 134 hostname, host_attributes = server_utils.get_host_info_from_machine( 135 machine) 136 args['host_attributes'] = host_attributes 137 ssh_user, ssh_pass, ssh_port, ssh_verbosity_flag, ssh_options = \ 138 _get_host_arguments() 139 140 hostname, args['user'], args['password'], args['port'] = \ 141 server_utils.parse_machine(hostname, ssh_user, ssh_pass, ssh_port) 142 args['ssh_verbosity_flag'] = ssh_verbosity_flag 143 args['ssh_options'] = ssh_options 144 145 if not connectivity_class: 146 connectivity_class = _choose_connectivity_class(hostname) 147 host_attributes = args.get('host_attributes', {}) 148 host_class = host_class or OS_HOST_DICT.get(host_attributes.get('os_type')) 149 if host_class: 150 classes = [host_class, connectivity_class] 151 else: 152 classes = [_detect_host(connectivity_class, hostname, **args), 153 connectivity_class] 154 155 # create a custom host class for this machine and return an instance of it 156 host_class = type("%s_host" % hostname, tuple(classes), {}) 157 host_instance = host_class(hostname, **args) 158 159 # call job_start if this is the first time this host is being used 160 if hostname not in _started_hostnames: 161 host_instance.job_start() 162 _started_hostnames.add(hostname) 163 164 return host_instance 165 166 167def create_testbed(machine, **kwargs): 168 """Create the testbed object. 169 170 @param machine: A dict representing the test bed under test or a String 171 representing the testbed hostname (for legacy caller 172 support). 173 If it is a machine dict, the 'hostname' key is required. 174 Optional 'host_attributes' key will pipe in host_attributes 175 from the autoserv runtime or the AFE. 176 @param kwargs: Keyword args to pass to the testbed initialization. 177 178 @returns: The testbed object with all associated host objects instantiated. 179 """ 180 hostname, host_attributes = server_utils.get_host_info_from_machine( 181 machine) 182 kwargs['host_attributes'] = host_attributes 183 return testbed.TestBed(hostname, **kwargs) 184 185 186def create_target_machine(machine, **kwargs): 187 """Create the target machine which could be a testbed or a *Host. 188 189 @param machine: A dict representing the test bed under test or a String 190 representing the testbed hostname (for legacy caller 191 support). 192 If it is a machine dict, the 'hostname' key is required. 193 Optional 'host_attributes' key will pipe in host_attributes 194 from the autoserv runtime or the AFE. 195 @param kwargs: Keyword args to pass to the testbed initialization. 196 197 @returns: The target machine to be used for verify/repair. 198 """ 199 # TODO(kevcheng): We'll want to have a smarter way of figuring out which 200 # host to create (checking host labels). 201 if server_utils.machine_is_testbed(machine): 202 return create_testbed(machine, **kwargs) 203 return create_host(machine, **kwargs) 204