| # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # |
| |
| """This file provides core logic for connecting a Chameleon Daemon.""" |
| |
| import logging |
| |
| from autotest_lib.client.bin import utils |
| from autotest_lib.client.common_lib import global_config |
| from autotest_lib.client.cros.chameleon import chameleon |
| from autotest_lib.server.cros import dnsname_mangler |
| from autotest_lib.server.cros.dynamic_suite import frontend_wrappers |
| from autotest_lib.server.hosts import ssh_host |
| |
| |
| # Names of the host attributes in the database that represent the values for |
| # the chameleon_host and chameleon_port for a servo connected to the DUT. |
| CHAMELEON_HOST_ATTR = 'chameleon_host' |
| CHAMELEON_PORT_ATTR = 'chameleon_port' |
| |
| _CONFIG = global_config.global_config |
| |
| class ChameleonHostError(Exception): |
| """Error in ChameleonHost.""" |
| pass |
| |
| |
| class ChameleonHost(ssh_host.SSHHost): |
| """Host class for a host that controls a Chameleon.""" |
| |
| # Chameleond process name. |
| CHAMELEOND_PROCESS = 'chameleond' |
| |
| |
| # TODO(waihong): Add verify and repair logic which are required while |
| # deploying to Cros Lab. |
| |
| |
| def _initialize(self, chameleon_host='localhost', chameleon_port=9992, |
| *args, **dargs): |
| """Initialize a ChameleonHost instance. |
| |
| A ChameleonHost instance represents a host that controls a Chameleon. |
| |
| @param chameleon_host: Name of the host where the chameleond process |
| is running. |
| If this is passed in by IP address, it will be |
| treated as not in lab. |
| @param chameleon_port: Port the chameleond process is listening on. |
| |
| """ |
| super(ChameleonHost, self)._initialize(hostname=chameleon_host, |
| *args, **dargs) |
| |
| self._is_in_lab = None |
| self._check_if_is_in_lab() |
| |
| self._chameleon_port = chameleon_port |
| self._local_port = None |
| self._tunneling_process = None |
| |
| if self._is_in_lab: |
| self._chameleon_connection = chameleon.ChameleonConnection( |
| self.hostname, chameleon_port) |
| else: |
| self._create_connection_through_tunnel() |
| |
| |
| def _check_if_is_in_lab(self): |
| """Checks if Chameleon host is in lab and set self._is_in_lab. |
| |
| If self.hostname is an IP address, we treat it as is not in lab zone. |
| |
| """ |
| self._is_in_lab = (False if dnsname_mangler.is_ip_address(self.hostname) |
| else utils.host_is_in_lab_zone(self.hostname)) |
| |
| |
| def _create_connection_through_tunnel(self): |
| """Creates Chameleon connection through SSH tunnel. |
| |
| For developers to run server side test on corp device against |
| testing device on Google Test Network, it is required to use |
| SSH tunneling to access ports other than SSH port. |
| |
| """ |
| try: |
| self._local_port = utils.get_unused_port() |
| self._tunneling_process = self._create_ssh_tunnel( |
| self._chameleon_port, self._local_port) |
| |
| self._wait_for_connection_established() |
| |
| # Always close tunnel when fail to create connection. |
| except: |
| logging.exception('Error in creating connection through tunnel.') |
| self._disconnect_tunneling() |
| raise |
| |
| |
| def _wait_for_connection_established(self): |
| """Wait for ChameleonConnection through tunnel being established.""" |
| |
| def _create_connection(): |
| """Create ChameleonConnection. |
| |
| @returns: True if success. False otherwise. |
| |
| """ |
| try: |
| self._chameleon_connection = chameleon.ChameleonConnection( |
| 'localhost', self._local_port) |
| except chameleon.ChameleonConnectionError: |
| logging.debug('Connection is not ready yet ...') |
| return False |
| |
| logging.debug('Connection is up') |
| return True |
| |
| success = utils.wait_for_value( |
| _create_connection, expected_value=True, timeout_sec=30) |
| |
| if not success: |
| raise ChameleonHostError('Can not connect to Chameleon') |
| |
| |
| def is_in_lab(self): |
| """Check whether the chameleon host is a lab device. |
| |
| @returns: True if the chameleon host is in Cros Lab, otherwise False. |
| |
| """ |
| return self._is_in_lab |
| |
| |
| def get_wait_up_processes(self): |
| """Get the list of local processes to wait for in wait_up. |
| |
| Override get_wait_up_processes in |
| autotest_lib.client.common_lib.hosts.base_classes.Host. |
| Wait for chameleond process to go up. Called by base class when |
| rebooting the device. |
| |
| """ |
| processes = [self.CHAMELEOND_PROCESS] |
| return processes |
| |
| |
| def create_chameleon_board(self): |
| """Create a ChameleonBoard object.""" |
| # TODO(waihong): Add verify and repair logic which are required while |
| # deploying to Cros Lab. |
| return chameleon.ChameleonBoard(self._chameleon_connection, self) |
| |
| |
| def _disconnect_tunneling(self): |
| """Disconnect the SSH tunnel.""" |
| if not self._tunneling_process: |
| return |
| |
| if self._tunneling_process.poll() is None: |
| self._tunneling_process.terminate() |
| logging.debug( |
| 'chameleon_host terminated tunnel, pid %d', |
| self._tunneling_process.pid) |
| else: |
| logging.debug( |
| 'chameleon_host tunnel pid %d terminated early, status %d', |
| self._tunneling_process.pid, |
| self._tunneling_process.returncode) |
| |
| |
| def close(self): |
| """Cleanup function when ChameleonHost is to be destroyed.""" |
| self._disconnect_tunneling() |
| super(ChameleonHost, self).close() |
| |
| |
| def create_chameleon_host(dut, chameleon_args): |
| """Create a ChameleonHost object. |
| |
| There three possible cases: |
| 1) If the DUT is in Cros Lab and has a chameleon board, then create |
| a ChameleonHost object pointing to the board. chameleon_args |
| is ignored. |
| 2) If not case 1) and chameleon_args is neither None nor empty, then |
| create a ChameleonHost object using chameleon_args. |
| 3) If neither case 1) or 2) applies, return None. |
| |
| @param dut: host name of the host that chameleon connects. It can be used |
| to lookup the chameleon in test lab using naming convention. |
| If dut is an IP address, it can not be used to lookup the |
| chameleon in test lab. |
| @param chameleon_args: A dictionary that contains args for creating |
| a ChameleonHost object, |
| e.g. {'chameleon_host': '172.11.11.112', |
| 'chameleon_port': 9992}. |
| |
| @returns: A ChameleonHost object or None. |
| |
| """ |
| if not utils.is_in_container(): |
| is_moblab = utils.is_moblab() |
| else: |
| is_moblab = _CONFIG.get_config_value( |
| 'SSP', 'is_moblab', type=bool, default=False) |
| |
| if not is_moblab: |
| dut_is_hostname = not dnsname_mangler.is_ip_address(dut) |
| if dut_is_hostname: |
| chameleon_hostname = chameleon.make_chameleon_hostname(dut) |
| if utils.host_is_in_lab_zone(chameleon_hostname): |
| return ChameleonHost(chameleon_host=chameleon_hostname) |
| if chameleon_args: |
| return ChameleonHost(**chameleon_args) |
| else: |
| return None |
| else: |
| afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10) |
| hosts = afe.get_hosts(hostname=dut) |
| if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes: |
| return ChameleonHost( |
| chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR], |
| chameleon_port=hosts[0].attributes.get( |
| CHAMELEON_PORT_ATTR, 9992) |
| ) |
| else: |
| return None |