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