1*9c5db199SXin Li# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li# 5*9c5db199SXin Li 6*9c5db199SXin Li"""This file provides core logic for connecting a Chameleon Daemon.""" 7*9c5db199SXin Li 8*9c5db199SXin Liimport logging 9*9c5db199SXin Li 10*9c5db199SXin Lifrom autotest_lib.client.bin import utils 11*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 12*9c5db199SXin Lifrom autotest_lib.client.common_lib import global_config 13*9c5db199SXin Lifrom autotest_lib.client.cros.chameleon import chameleon 14*9c5db199SXin Lifrom autotest_lib.server.cros import dnsname_mangler 15*9c5db199SXin Lifrom autotest_lib.server.cros.dynamic_suite import frontend_wrappers 16*9c5db199SXin Lifrom autotest_lib.server.hosts import ssh_host 17*9c5db199SXin Li 18*9c5db199SXin Li# Names of the host attributes in the database that represent the values for 19*9c5db199SXin Li# the chameleon_host and chameleon_port for a servo connected to the DUT. 20*9c5db199SXin LiCHAMELEON_HOST_ATTR = 'chameleon_host' 21*9c5db199SXin LiCHAMELEON_PORT_ATTR = 'chameleon_port' 22*9c5db199SXin Li 23*9c5db199SXin Li_CONFIG = global_config.global_config 24*9c5db199SXin LiENABLE_SSH_TUNNEL_FOR_CHAMELEON = _CONFIG.get_config_value( 25*9c5db199SXin Li 'CROS', 'enable_ssh_tunnel_for_chameleon', type=bool, default=False) 26*9c5db199SXin Li 27*9c5db199SXin Liclass ChameleonHostError(Exception): 28*9c5db199SXin Li """Error in ChameleonHost.""" 29*9c5db199SXin Li pass 30*9c5db199SXin Li 31*9c5db199SXin Li 32*9c5db199SXin Liclass ChameleonHost(ssh_host.SSHHost): 33*9c5db199SXin Li """Host class for a host that controls a Chameleon.""" 34*9c5db199SXin Li 35*9c5db199SXin Li # Chameleond process name. 36*9c5db199SXin Li CHAMELEOND_PROCESS = 'chameleond' 37*9c5db199SXin Li 38*9c5db199SXin Li 39*9c5db199SXin Li # TODO(waihong): Add verify and repair logic which are required while 40*9c5db199SXin Li # deploying to Cros Lab. 41*9c5db199SXin Li 42*9c5db199SXin Li 43*9c5db199SXin Li def _initialize(self, chameleon_host='localhost', chameleon_port=9992, 44*9c5db199SXin Li *args, **dargs): 45*9c5db199SXin Li """Initialize a ChameleonHost instance. 46*9c5db199SXin Li 47*9c5db199SXin Li A ChameleonHost instance represents a host that controls a Chameleon. 48*9c5db199SXin Li 49*9c5db199SXin Li @param chameleon_host: Name of the host where the chameleond process 50*9c5db199SXin Li is running. 51*9c5db199SXin Li If this is passed in by IP address, it will be 52*9c5db199SXin Li treated as not in lab. 53*9c5db199SXin Li @param chameleon_port: Port the chameleond process is listening on. 54*9c5db199SXin Li 55*9c5db199SXin Li """ 56*9c5db199SXin Li super(ChameleonHost, self)._initialize(hostname=chameleon_host, 57*9c5db199SXin Li *args, **dargs) 58*9c5db199SXin Li 59*9c5db199SXin Li self._is_in_lab = None 60*9c5db199SXin Li self._check_if_is_in_lab() 61*9c5db199SXin Li 62*9c5db199SXin Li self._chameleon_port = chameleon_port 63*9c5db199SXin Li self._local_port = None 64*9c5db199SXin Li self._tunneling_process = None 65*9c5db199SXin Li 66*9c5db199SXin Li try: 67*9c5db199SXin Li if self._is_in_lab and not ENABLE_SSH_TUNNEL_FOR_CHAMELEON: 68*9c5db199SXin Li self._chameleon_connection = chameleon.ChameleonConnection( 69*9c5db199SXin Li self.hostname, chameleon_port) 70*9c5db199SXin Li else: 71*9c5db199SXin Li # A proxy generator is passed as an argument so that a proxy 72*9c5db199SXin Li # could be re-created on demand in ChameleonConnection 73*9c5db199SXin Li # whenever needed, e.g., after a reboot. 74*9c5db199SXin Li proxy_generator = ( 75*9c5db199SXin Li lambda: self.rpc_server_tracker.xmlrpc_connect( 76*9c5db199SXin Li None, chameleon_port, 77*9c5db199SXin Li ready_test_name=chameleon.CHAMELEON_READY_TEST, 78*9c5db199SXin Li timeout_seconds=60)) 79*9c5db199SXin Li self._chameleon_connection = chameleon.ChameleonConnection( 80*9c5db199SXin Li None, proxy_generator=proxy_generator) 81*9c5db199SXin Li 82*9c5db199SXin Li except Exception as e: 83*9c5db199SXin Li raise ChameleonHostError('Can not connect to Chameleon: %s(%s)', 84*9c5db199SXin Li e.__class__, e) 85*9c5db199SXin Li 86*9c5db199SXin Li 87*9c5db199SXin Li def _check_if_is_in_lab(self): 88*9c5db199SXin Li """Checks if Chameleon host is in lab and set self._is_in_lab. 89*9c5db199SXin Li 90*9c5db199SXin Li If self.hostname is an IP address, we treat it as is not in lab zone. 91*9c5db199SXin Li 92*9c5db199SXin Li """ 93*9c5db199SXin Li self._is_in_lab = (False if dnsname_mangler.is_ip_address(self.hostname) 94*9c5db199SXin Li else utils.host_is_in_lab_zone(self.hostname)) 95*9c5db199SXin Li 96*9c5db199SXin Li 97*9c5db199SXin Li def is_in_lab(self): 98*9c5db199SXin Li """Check whether the chameleon host is a lab device. 99*9c5db199SXin Li 100*9c5db199SXin Li @returns: True if the chameleon host is in Cros Lab, otherwise False. 101*9c5db199SXin Li 102*9c5db199SXin Li """ 103*9c5db199SXin Li return self._is_in_lab 104*9c5db199SXin Li 105*9c5db199SXin Li 106*9c5db199SXin Li def get_wait_up_processes(self): 107*9c5db199SXin Li """Get the list of local processes to wait for in wait_up. 108*9c5db199SXin Li 109*9c5db199SXin Li Override get_wait_up_processes in 110*9c5db199SXin Li autotest_lib.client.common_lib.hosts.base_classes.Host. 111*9c5db199SXin Li Wait for chameleond process to go up. Called by base class when 112*9c5db199SXin Li rebooting the device. 113*9c5db199SXin Li 114*9c5db199SXin Li """ 115*9c5db199SXin Li processes = [self.CHAMELEOND_PROCESS] 116*9c5db199SXin Li return processes 117*9c5db199SXin Li 118*9c5db199SXin Li 119*9c5db199SXin Li def create_chameleon_board(self): 120*9c5db199SXin Li """Create a ChameleonBoard object with error recovery. 121*9c5db199SXin Li 122*9c5db199SXin Li This function will reboot the chameleon board once and retry if we can't 123*9c5db199SXin Li create chameleon board. 124*9c5db199SXin Li 125*9c5db199SXin Li @return A ChameleonBoard object. 126*9c5db199SXin Li """ 127*9c5db199SXin Li # TODO(waihong): Add verify and repair logic which are required while 128*9c5db199SXin Li # deploying to Cros Lab. 129*9c5db199SXin Li try: 130*9c5db199SXin Li chameleon_board = chameleon.ChameleonBoard( 131*9c5db199SXin Li self._chameleon_connection, self) 132*9c5db199SXin Li return chameleon_board 133*9c5db199SXin Li except Exception as e: 134*9c5db199SXin Li raise ChameleonHostError('Can not create chameleon board: %s(%s)', 135*9c5db199SXin Li e.__class__, e) 136*9c5db199SXin Li 137*9c5db199SXin Li 138*9c5db199SXin Lidef create_chameleon_host(dut, chameleon_args): 139*9c5db199SXin Li """Create a ChameleonHost object. 140*9c5db199SXin Li 141*9c5db199SXin Li There three possible cases: 142*9c5db199SXin Li 1) If the DUT is in Cros Lab and has a chameleon board, then create 143*9c5db199SXin Li a ChameleonHost object pointing to the board. chameleon_args 144*9c5db199SXin Li is ignored. 145*9c5db199SXin Li 2) If not case 1) and chameleon_args is neither None nor empty, then 146*9c5db199SXin Li create a ChameleonHost object using chameleon_args. 147*9c5db199SXin Li 3) If neither case 1) or 2) applies, return None. 148*9c5db199SXin Li 149*9c5db199SXin Li @param dut: host name of the host that chameleon connects. It can be used 150*9c5db199SXin Li to lookup the chameleon in test lab using naming convention. 151*9c5db199SXin Li If dut is an IP address, it can not be used to lookup the 152*9c5db199SXin Li chameleon in test lab. 153*9c5db199SXin Li @param chameleon_args: A dictionary that contains args for creating 154*9c5db199SXin Li a ChameleonHost object, 155*9c5db199SXin Li e.g. {'chameleon_host': '172.11.11.112', 156*9c5db199SXin Li 'chameleon_port': 9992}. 157*9c5db199SXin Li 158*9c5db199SXin Li @returns: A ChameleonHost object or None. 159*9c5db199SXin Li 160*9c5db199SXin Li """ 161*9c5db199SXin Li if not utils.is_in_container(): 162*9c5db199SXin Li is_moblab = utils.is_moblab() 163*9c5db199SXin Li else: 164*9c5db199SXin Li is_moblab = _CONFIG.get_config_value( 165*9c5db199SXin Li 'SSP', 'is_moblab', type=bool, default=False) 166*9c5db199SXin Li 167*9c5db199SXin Li if not is_moblab: 168*9c5db199SXin Li dut_is_hostname = not dnsname_mangler.is_ip_address(dut) 169*9c5db199SXin Li if dut_is_hostname: 170*9c5db199SXin Li chameleon_hostname = chameleon.make_chameleon_hostname(dut) 171*9c5db199SXin Li if utils.host_is_in_lab_zone(chameleon_hostname): 172*9c5db199SXin Li # Be more tolerant on chameleon in the lab because 173*9c5db199SXin Li # we don't want dead chameleon blocks non-chameleon tests. 174*9c5db199SXin Li # We use ssh ping here as BeyondCorp-only hosts cannot make ICMP 175*9c5db199SXin Li # ping to chameleon test devices. 176*9c5db199SXin Li try: 177*9c5db199SXin Li ssh_host.SSHHost(chameleon_hostname).ssh_ping() 178*9c5db199SXin Li except (error.AutoservSSHTimeout, 179*9c5db199SXin Li error.AutoservSshPermissionDeniedError, 180*9c5db199SXin Li error.AutoservSshPingHostError) as e: 181*9c5db199SXin Li logging.warning( 182*9c5db199SXin Li 'Chameleon %s is not accessible. Please file a bug' 183*9c5db199SXin Li ' to test lab: %s', chameleon_hostname, e) 184*9c5db199SXin Li return None 185*9c5db199SXin Li return ChameleonHost(chameleon_host=chameleon_hostname) 186*9c5db199SXin Li if chameleon_args: 187*9c5db199SXin Li return ChameleonHost(**chameleon_args) 188*9c5db199SXin Li else: 189*9c5db199SXin Li return None 190*9c5db199SXin Li else: 191*9c5db199SXin Li afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10) 192*9c5db199SXin Li hosts = afe.get_hosts(hostname=dut) 193*9c5db199SXin Li if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes: 194*9c5db199SXin Li return ChameleonHost( 195*9c5db199SXin Li chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR], 196*9c5db199SXin Li chameleon_port=hosts[0].attributes.get( 197*9c5db199SXin Li CHAMELEON_PORT_ATTR, 9992) 198*9c5db199SXin Li ) 199*9c5db199SXin Li else: 200*9c5db199SXin Li return None 201*9c5db199SXin Li 202*9c5db199SXin Li 203*9c5db199SXin Lidef create_btpeer_host(dut, btpeer_args_list): 204*9c5db199SXin Li """Create a ChameleonHost object for a Bluetooth peer 205*9c5db199SXin Li 206*9c5db199SXin Li This is similar to create_chameleon_host but unlike chameleon board 207*9c5db199SXin Li there can be multiple btpeers with a single DUT 208*9c5db199SXin Li 209*9c5db199SXin Li There four possible cases: 210*9c5db199SXin Li 1) If the DUT is in Cros Lab then assume that it can have up to 4 bluetooth 211*9c5db199SXin Li peers. Ping the url and create a Chameleon host for each Bluetooth peer 212*9c5db199SXin Li present. btpeer_args_list is ignored. 213*9c5db199SXin Li 2) If not case 1) and btpeer_args_list is not empty, then 214*9c5db199SXin Li create a BtpeerHost object for each host specified in btpeer_args_list. 215*9c5db199SXin Li 3) If neither case 1) or 2) applies, return None. 216*9c5db199SXin Li 4) This DUT is controlled by moblab. This case is not implemented. 217*9c5db199SXin Li 218*9c5db199SXin Li 219*9c5db199SXin Li @param dut: host name of the host that btpeer connects. It can be used 220*9c5db199SXin Li to lookup the btpeer in test lab using naming convention. 221*9c5db199SXin Li If dut is an IP address, it can not be used to lookup the 222*9c5db199SXin Li btpeer in test lab. Naming convention in the lab is 223*9c5db199SXin Li <hostname>-btpeer[1-4] 224*9c5db199SXin Li @param btpeer_args_list: A list of dictionaries that contains args for 225*9c5db199SXin Li creating a BtpeerHost object, 226*9c5db199SXin Li e.g. {'btpeer_host': '172.11.11.112', 227*9c5db199SXin Li 'btpeer_port': 9992}. 228*9c5db199SXin Li 229*9c5db199SXin Li @returns: A list of BtpeerHost objects 230*9c5db199SXin Li 231*9c5db199SXin Li """ 232*9c5db199SXin Li def _convert_btpeer_args(args): 233*9c5db199SXin Li """Convert btpeer args to format accepted by ChameleonHost.""" 234*9c5db199SXin Li ret_args = {} 235*9c5db199SXin Li if 'btpeer_host' in args: 236*9c5db199SXin Li ret_args['chameleon_host'] = args['btpeer_host'] 237*9c5db199SXin Li if 'btpeer_port' in args: 238*9c5db199SXin Li ret_args['chameleon_port'] = int(args['btpeer_port']) 239*9c5db199SXin Li if 'btpeer_ssh_port' in args: 240*9c5db199SXin Li ret_args['port'] = int(args['btpeer_ssh_port']) 241*9c5db199SXin Li return ret_args 242*9c5db199SXin Li 243*9c5db199SXin Li if not utils.is_in_container(): 244*9c5db199SXin Li is_moblab = utils.is_moblab() 245*9c5db199SXin Li else: 246*9c5db199SXin Li is_moblab = _CONFIG.get_config_value( 247*9c5db199SXin Li 'SSP', 'is_moblab', type=bool, default=False) 248*9c5db199SXin Li 249*9c5db199SXin Li btpeer_hosts = [] 250*9c5db199SXin Li 251*9c5db199SXin Li if not is_moblab: 252*9c5db199SXin Li if (not dnsname_mangler.is_ip_address(dut) and 253*9c5db199SXin Li utils.host_is_in_lab_zone(dut)): 254*9c5db199SXin Li # This is a device in the lab. Ignore any arguments passed and 255*9c5db199SXin Li # derive peer hostnames from the DUT hostname 256*9c5db199SXin Li btpeer_hostnames = chameleon.make_btpeer_hostnames(dut) 257*9c5db199SXin Li for btpeer_hostname in btpeer_hostnames: 258*9c5db199SXin Li # Not all test bed have 4 Bluetooth peers 259*9c5db199SXin Li if utils.ping(btpeer_hostname, deadline=3): 260*9c5db199SXin Li logging.warning('Btpeer %s is not accessible. This maybe ' 261*9c5db199SXin Li 'expected or it maybe an issue with the ' 262*9c5db199SXin Li 'Bluetooth peer. Please Check the test bed.' 263*9c5db199SXin Li , btpeer_hostname) 264*9c5db199SXin Li continue 265*9c5db199SXin Li else: 266*9c5db199SXin Li logging.debug("Creating btpeer from %s",btpeer_hostname) 267*9c5db199SXin Li btpeer_hosts.append( 268*9c5db199SXin Li ChameleonHost(chameleon_host=btpeer_hostname)) 269*9c5db199SXin Li return btpeer_hosts 270*9c5db199SXin Li else: 271*9c5db199SXin Li # IP address given or DNS address is not in lab. 272*9c5db199SXin Li # Create the Bluetooth peers from the arguments passed 273*9c5db199SXin Li return [ ChameleonHost(**_convert_btpeer_args(btpeer_args)) 274*9c5db199SXin Li for btpeer_args in btpeer_args_list] 275*9c5db199SXin Li else: 276*9c5db199SXin Li # TODO(b:149606762) 277*9c5db199SXin Li # moblab still create Bluetooth peer from chameleon_args 278*9c5db199SXin Li afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10) 279*9c5db199SXin Li hosts = afe.get_hosts(hostname=dut) 280*9c5db199SXin Li if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes: 281*9c5db199SXin Li return [ChameleonHost( 282*9c5db199SXin Li chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR], 283*9c5db199SXin Li chameleon_port=hosts[0].attributes.get( 284*9c5db199SXin Li CHAMELEON_PORT_ATTR, 9992) 285*9c5db199SXin Li )] 286*9c5db199SXin Li else: 287*9c5db199SXin Li return [] 288