| # 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 |
| ENABLE_SSH_TUNNEL_FOR_CHAMELEON = _CONFIG.get_config_value( |
| 'CROS', 'enable_ssh_tunnel_for_chameleon', type=bool, default=False) |
| |
| 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 |
| |
| try: |
| if self._is_in_lab and not ENABLE_SSH_TUNNEL_FOR_CHAMELEON: |
| self._chameleon_connection = chameleon.ChameleonConnection( |
| self.hostname, chameleon_port) |
| else: |
| # A proxy generator is passed as an argument so that a proxy |
| # could be re-created on demand in ChameleonConnection |
| # whenever needed, e.g., after a reboot. |
| proxy_generator = ( |
| lambda: self.rpc_server_tracker.xmlrpc_connect( |
| None, chameleon_port, |
| ready_test_name=chameleon.CHAMELEON_READY_TEST, |
| timeout_seconds=60)) |
| self._chameleon_connection = chameleon.ChameleonConnection( |
| None, proxy_generator=proxy_generator) |
| |
| except Exception as e: |
| raise ChameleonHostError('Can not connect to Chameleon: %s(%s)', |
| e.__class__, e) |
| |
| |
| 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 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 with error recovery. |
| |
| This function will reboot the chameleon board once and retry if we can't |
| create chameleon board. |
| |
| @return A ChameleonBoard object. |
| """ |
| # TODO(waihong): Add verify and repair logic which are required while |
| # deploying to Cros Lab. |
| chameleon_board = None |
| try: |
| chameleon_board = chameleon.ChameleonBoard( |
| self._chameleon_connection, self) |
| return chameleon_board |
| except: |
| self.reboot() |
| chameleon_board = chameleon.ChameleonBoard( |
| self._chameleon_connection, self) |
| return chameleon_board |
| |
| |
| 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): |
| # Be more tolerant on chameleon in the lab because |
| # we don't want dead chameleon blocks non-chameleon tests. |
| if utils.ping(chameleon_hostname, deadline=3): |
| logging.warning( |
| 'Chameleon %s is not accessible. Please file a bug' |
| ' to test lab', chameleon_hostname) |
| return None |
| 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 |
| |
| |
| def create_btpeer_host(dut, btpeer_args_list): |
| """Create a ChameleonHost object for a Bluetooth peer |
| |
| This is similar to create_chameleon_host but unlike chameleon board |
| there can be multiple btpeers with a single DUT |
| |
| There four possible cases: |
| 1) If the DUT is in Cros Lab then assume that it can have up to 4 bluetooth |
| peers. Ping the url and create a Chameleon host for each Bluetooth peer |
| present. btpeer_args_list is ignored. |
| 2) If not case 1) and btpeer_args_list is not empty, then |
| create a BtpeerHost object for each host specified in btpeer_args_list. |
| 3) If neither case 1) or 2) applies, return None. |
| 4) This DUT is controlled by moblab. This case is not implemented. |
| |
| |
| @param dut: host name of the host that btpeer connects. It can be used |
| to lookup the btpeer in test lab using naming convention. |
| If dut is an IP address, it can not be used to lookup the |
| btpeer in test lab. Naming convention in the lab is |
| <hostname>-btpeer[1-4] |
| @param btpeer_args_list: A list of dictionaries that contains args for |
| creating a BtpeerHost object, |
| e.g. {'btpeer_host': '172.11.11.112', |
| 'btpeer_port': 9992}. |
| |
| @returns: A list of BtpeerHost objects |
| |
| """ |
| def _convert_btpeer_args(args): |
| """Convert btpeer args to format accepted by ChameleonHost.""" |
| ret_args = {} |
| if 'btpeer_host' in args: |
| ret_args['chameleon_host'] = args['btpeer_host'] |
| if 'btpeer_port' in args: |
| ret_args['chameleon_port'] = args['btpeer_port'] |
| if 'btpeer_ssh_port' in args: |
| ret_args['port'] = int(args['btpeer_ssh_port']) |
| return ret_args |
| |
| 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) |
| |
| btpeer_hosts = [] |
| |
| if not is_moblab: |
| if (not dnsname_mangler.is_ip_address(dut) and |
| utils.host_is_in_lab_zone(dut)): |
| # This is a device in the lab. Ignore any arguments passed and |
| # derive peer hostnames from the DUT hostname |
| btpeer_hostnames = chameleon.make_btpeer_hostnames(dut) |
| for btpeer_hostname in btpeer_hostnames: |
| # Not all test bed have 4 Bluetooth peers |
| if utils.ping(btpeer_hostname, deadline=3): |
| logging.warning('Btpeer %s is not accessible. This maybe ' |
| 'expected or it maybe an issue with the ' |
| 'Bluetooth peer. Please Check the test bed.' |
| , btpeer_hostname) |
| continue |
| else: |
| logging.debug("Creating btpeer from %s",btpeer_hostname) |
| btpeer_hosts.append( |
| ChameleonHost(chameleon_host=btpeer_hostname)) |
| return btpeer_hosts |
| else: |
| # IP address given or DNS address is not in lab. |
| # Create the Bluetooth peers from the arguments passed |
| return [ ChameleonHost(**_convert_btpeer_args(btpeer_args)) |
| for btpeer_args in btpeer_args_list] |
| else: |
| # TODO(b:149606762) |
| # moblab still create Bluetooth peer from chameleon_args |
| 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 [] |