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 gce_host
     19 from autotest_lib.server.hosts import sonic_host
     20 from autotest_lib.server.hosts import ssh_host
     21 from autotest_lib.server.hosts import testbed
     22 
     23 
     24 CONFIG = global_config.global_config
     25 
     26 SSH_ENGINE = CONFIG.get_config_value('AUTOSERV', 'ssh_engine', type=str)
     27 
     28 # Default ssh options used in creating a host.
     29 DEFAULT_SSH_USER = 'root'
     30 DEFAULT_SSH_PASS = ''
     31 DEFAULT_SSH_PORT = 22
     32 DEFAULT_SSH_VERBOSITY = ''
     33 DEFAULT_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.
     41 host_types = [cros_host.CrosHost, moblab_host.MoblabHost,
     42               jetstream_host.JetstreamHost, sonic_host.SonicHost,
     43               adb_host.ADBHost, gce_host.GceHost,]
     44 OS_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 
     52 def _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 
    101 def _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 
    130 def _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.
    152 def 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 
    207 def 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 
    226 def 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