Home | History | Annotate | Download | only in hosts
      1 """Provides a factory method to create a host object."""
      2 
      3 import logging
      4 from contextlib import closing
      5 
      6 from autotest_lib.client.bin import local_host
      7 from autotest_lib.client.bin import utils
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.common_lib import global_config
     10 from autotest_lib.server import utils as server_utils
     11 from autotest_lib.server.cros.dynamic_suite import constants
     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
     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
     21 
     22 
     23 CONFIG = global_config.global_config
     24 
     25 SSH_ENGINE = CONFIG.get_config_value('AUTOSERV', 'ssh_engine', type=str)
     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 
     34 # for tracking which hostnames have already had job_start called
     35 _started_hostnames = set()
     36 
     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.
     40 host_types = [cros_host.CrosHost, moblab_host.MoblabHost,
     41               jetstream_host.JetstreamHost, sonic_host.SonicHost,
     42               adb_host.ADBHost,]
     43 OS_HOST_DICT = {'android': adb_host.ADBHost,
     44                 'brillo': adb_host.ADBHost,
     45                 'cros' : cros_host.CrosHost,
     46                 'emulated_brillo': emulated_adb_host.EmulatedADBHost,
     47                 'jetstream': jetstream_host.JetstreamHost,
     48                 'moblab': moblab_host.MoblabHost}
     49 
     50 
     51 def _get_host_arguments(machine):
     52     """Get parameters to construct a host object.
     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 
     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.
     65     """
     66     hostname, afe_host = server_utils.get_host_info_from_machine(machine)
     67     connection_pool = server_utils.get_connection_pool_from_machine(machine)
     68     host_info_store = host_info.get_store_from_machine(machine)
     69     info = host_info_store.get()
     70 
     71     g = globals()
     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))
     82 
     83     hostname, user, password, port = server_utils.parse_machine(hostname, user,
     84                                                                 password, port)
     85 
     86     host_args = {
     87             'hostname': hostname,
     88             'afe_host': afe_host,
     89             'host_info_store': host_info_store,
     90             'user': user,
     91             'password': password,
     92             'port': int(port),
     93             'ssh_verbosity_flag': ssh_verbosity_flag,
     94             'ssh_options': ssh_options,
     95             'connection_pool': connection_pool,
     96     }
     97     return host_args
     98 
     99 
    100 def _detect_host(connectivity_class, hostname, **args):
    101     """Detect host type.
    102 
    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.
    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 
    114     @returns: Class type of the first host class that returns True to the
    115               check_host method.
    116     """
    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
    127 
    128 
    129 def _choose_connectivity_class(hostname, ssh_port):
    130     """Choose a connectivity class for this hostname.
    131 
    132     @param hostname: hostname that we need a connectivity class for.
    133     @param ssh_port: SSH port to connect to the host.
    134 
    135     @returns a connectivity host class.
    136     """
    137     if (hostname == 'localhost' and ssh_port == DEFAULT_SSH_PORT):
    138         return local_host.LocalHost
    139     # by default assume we're using SSH support
    140     elif SSH_ENGINE == 'raw_ssh':
    141         return ssh_host.SSHHost
    142     else:
    143         raise error.AutoservError("Unknown SSH engine %s. Please verify the "
    144                                   "value of the configuration key 'ssh_engine' "
    145                                   "on autotest's global_config.ini file." %
    146                                   SSH_ENGINE)
    147 
    148 
    149 # TODO(kevcheng): Update the creation method so it's not a research project
    150 # determining the class inheritance model.
    151 def create_host(machine, host_class=None, connectivity_class=None, **args):
    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 
    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.
    160                     Optional 'afe_host' key will pipe in afe_host
    161                     from the autoserv runtime or the AFE.
    162     @param host_class: Host class to use, if None, will attempt to detect
    163                        the correct class.
    164     @param connectivity_class: Connectivity class to use, if None will decide
    165                                based off of hostname and config settings.
    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     """
    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)
    176 
    177     host_os = None
    178     full_os_prefix = constants.OS_PREFIX + ':'
    179     # Let's grab the os from the labels if we can for host class detection.
    180     for label in afe_host.labels:
    181         if label.startswith(full_os_prefix):
    182             host_os = label[len(full_os_prefix):]
    183             break
    184 
    185     if not connectivity_class:
    186         connectivity_class = _choose_connectivity_class(hostname, args['port'])
    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'))
    190                   or OS_HOST_DICT.get(host_os)
    191                   or _detect_host(connectivity_class, hostname, **args))
    192 
    193     # create a custom host class for this machine and return an instance of it
    194     classes = (host_class, connectivity_class)
    195     custom_host_class = type("%s_host" % hostname, classes, {})
    196     host_instance = custom_host_class(hostname, **args)
    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
    204 
    205 
    206 def create_testbed(machine, **kwargs):
    207     """Create the testbed object.
    208 
    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.
    213                     Optional 'afe_host' key will pipe in afe_host from
    214                     the afe_host object from the autoserv runtime or the AFE.
    215     @param kwargs: Keyword args to pass to the testbed initialization.
    216 
    217     @returns: The testbed object with all associated host objects instantiated.
    218     """
    219     detected_args = _get_host_arguments(machine)
    220     hostname = detected_args.pop('hostname')
    221     kwargs.update(detected_args)
    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.
    232                     Optional 'afe_host' key will pipe in afe_host
    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     """
    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 
    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)
    261