Home | History | Annotate | Download | only in hosts
      1 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 #
      5 
      6 """This file provides core logic for connecting a Chameleon Daemon."""
      7 
      8 import logging
      9 
     10 from autotest_lib.client.bin import utils
     11 from autotest_lib.client.common_lib import global_config
     12 from autotest_lib.client.cros.chameleon import chameleon
     13 from autotest_lib.server.cros import dnsname_mangler
     14 from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
     15 from autotest_lib.server.hosts import ssh_host
     16 
     17 
     18 # Names of the host attributes in the database that represent the values for
     19 # the chameleon_host and chameleon_port for a servo connected to the DUT.
     20 CHAMELEON_HOST_ATTR = 'chameleon_host'
     21 CHAMELEON_PORT_ATTR = 'chameleon_port'
     22 
     23 _CONFIG = global_config.global_config
     24 
     25 class ChameleonHostError(Exception):
     26     """Error in ChameleonHost."""
     27     pass
     28 
     29 
     30 class ChameleonHost(ssh_host.SSHHost):
     31     """Host class for a host that controls a Chameleon."""
     32 
     33     # Chameleond process name.
     34     CHAMELEOND_PROCESS = 'chameleond'
     35 
     36 
     37     # TODO(waihong): Add verify and repair logic which are required while
     38     # deploying to Cros Lab.
     39 
     40 
     41     def _initialize(self, chameleon_host='localhost', chameleon_port=9992,
     42                     *args, **dargs):
     43         """Initialize a ChameleonHost instance.
     44 
     45         A ChameleonHost instance represents a host that controls a Chameleon.
     46 
     47         @param chameleon_host: Name of the host where the chameleond process
     48                                is running.
     49                                If this is passed in by IP address, it will be
     50                                treated as not in lab.
     51         @param chameleon_port: Port the chameleond process is listening on.
     52 
     53         """
     54         super(ChameleonHost, self)._initialize(hostname=chameleon_host,
     55                                                *args, **dargs)
     56 
     57         self._is_in_lab = None
     58         self._check_if_is_in_lab()
     59 
     60         self._chameleon_port = chameleon_port
     61         self._local_port = None
     62         self._tunneling_process = None
     63 
     64         if self._is_in_lab:
     65             self._chameleon_connection = chameleon.ChameleonConnection(
     66                     self.hostname, chameleon_port)
     67         else:
     68             self._create_connection_through_tunnel()
     69 
     70 
     71     def _check_if_is_in_lab(self):
     72         """Checks if Chameleon host is in lab and set self._is_in_lab.
     73 
     74         If self.hostname is an IP address, we treat it as is not in lab zone.
     75 
     76         """
     77         self._is_in_lab = (False if dnsname_mangler.is_ip_address(self.hostname)
     78                            else utils.host_is_in_lab_zone(self.hostname))
     79 
     80 
     81     def _create_connection_through_tunnel(self):
     82         """Creates Chameleon connection through SSH tunnel.
     83 
     84         For developers to run server side test on corp device against
     85         testing device on Google Test Network, it is required to use
     86         SSH tunneling to access ports other than SSH port.
     87 
     88         """
     89         try:
     90             self._local_port = utils.get_unused_port()
     91             self._tunneling_process = self._create_ssh_tunnel(
     92                     self._chameleon_port, self._local_port)
     93 
     94             self._wait_for_connection_established()
     95 
     96         # Always close tunnel when fail to create connection.
     97         except:
     98             logging.exception('Error in creating connection through tunnel.')
     99             self._disconnect_tunneling()
    100             raise
    101 
    102 
    103     def _wait_for_connection_established(self):
    104         """Wait for ChameleonConnection through tunnel being established."""
    105 
    106         def _create_connection():
    107             """Create ChameleonConnection.
    108 
    109             @returns: True if success. False otherwise.
    110 
    111             """
    112             try:
    113                 self._chameleon_connection = chameleon.ChameleonConnection(
    114                         'localhost', self._local_port)
    115             except chameleon.ChameleonConnectionError:
    116                 logging.debug('Connection is not ready yet ...')
    117                 return False
    118 
    119             logging.debug('Connection is up')
    120             return True
    121 
    122         success = utils.wait_for_value(
    123             _create_connection, expected_value=True, timeout_sec=30)
    124 
    125         if not success:
    126             raise ChameleonHostError('Can not connect to Chameleon')
    127 
    128 
    129     def is_in_lab(self):
    130         """Check whether the chameleon host is a lab device.
    131 
    132         @returns: True if the chameleon host is in Cros Lab, otherwise False.
    133 
    134         """
    135         return self._is_in_lab
    136 
    137 
    138     def get_wait_up_processes(self):
    139         """Get the list of local processes to wait for in wait_up.
    140 
    141         Override get_wait_up_processes in
    142         autotest_lib.client.common_lib.hosts.base_classes.Host.
    143         Wait for chameleond process to go up. Called by base class when
    144         rebooting the device.
    145 
    146         """
    147         processes = [self.CHAMELEOND_PROCESS]
    148         return processes
    149 
    150 
    151     def create_chameleon_board(self):
    152         """Create a ChameleonBoard object."""
    153         # TODO(waihong): Add verify and repair logic which are required while
    154         # deploying to Cros Lab.
    155         return chameleon.ChameleonBoard(self._chameleon_connection, self)
    156 
    157 
    158     def _disconnect_tunneling(self):
    159         """Disconnect the SSH tunnel."""
    160         if not self._tunneling_process:
    161             return
    162 
    163         if self._tunneling_process.poll() is None:
    164             self._tunneling_process.terminate()
    165             logging.debug(
    166                     'chameleon_host terminated tunnel, pid %d',
    167                     self._tunneling_process.pid)
    168         else:
    169             logging.debug(
    170                     'chameleon_host tunnel pid %d terminated early, status %d',
    171                     self._tunneling_process.pid,
    172                     self._tunneling_process.returncode)
    173 
    174 
    175     def close(self):
    176         """Cleanup function when ChameleonHost is to be destroyed."""
    177         self._disconnect_tunneling()
    178         super(ChameleonHost, self).close()
    179 
    180 
    181 def create_chameleon_host(dut, chameleon_args):
    182     """Create a ChameleonHost object.
    183 
    184     There three possible cases:
    185     1) If the DUT is in Cros Lab and has a chameleon board, then create
    186        a ChameleonHost object pointing to the board. chameleon_args
    187        is ignored.
    188     2) If not case 1) and chameleon_args is neither None nor empty, then
    189        create a ChameleonHost object using chameleon_args.
    190     3) If neither case 1) or 2) applies, return None.
    191 
    192     @param dut: host name of the host that chameleon connects. It can be used
    193                 to lookup the chameleon in test lab using naming convention.
    194                 If dut is an IP address, it can not be used to lookup the
    195                 chameleon in test lab.
    196     @param chameleon_args: A dictionary that contains args for creating
    197                            a ChameleonHost object,
    198                            e.g. {'chameleon_host': '172.11.11.112',
    199                                  'chameleon_port': 9992}.
    200 
    201     @returns: A ChameleonHost object or None.
    202 
    203     """
    204     if not utils.is_in_container():
    205         is_moblab = utils.is_moblab()
    206     else:
    207         is_moblab = _CONFIG.get_config_value(
    208                 'SSP', 'is_moblab', type=bool, default=False)
    209 
    210     if not is_moblab:
    211         dut_is_hostname = not dnsname_mangler.is_ip_address(dut)
    212         if dut_is_hostname:
    213             chameleon_hostname = chameleon.make_chameleon_hostname(dut)
    214             if utils.host_is_in_lab_zone(chameleon_hostname):
    215                 return ChameleonHost(chameleon_host=chameleon_hostname)
    216         if chameleon_args:
    217             return ChameleonHost(**chameleon_args)
    218         else:
    219             return None
    220     else:
    221         afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
    222         hosts = afe.get_hosts(hostname=dut)
    223         if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes:
    224             return ChameleonHost(
    225                 chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR],
    226                 chameleon_port=hosts[0].attributes.get(
    227                     CHAMELEON_PORT_ATTR, 9992)
    228             )
    229         else:
    230             return None
    231