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 global_config 12from autotest_lib.client.cros.chameleon import chameleon 13from autotest_lib.server.cros import dnsname_mangler 14from autotest_lib.server.cros.dynamic_suite import frontend_wrappers 15from 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. 20CHAMELEON_HOST_ATTR = 'chameleon_host' 21CHAMELEON_PORT_ATTR = 'chameleon_port' 22 23_CONFIG = global_config.global_config 24 25class ChameleonHostError(Exception): 26 """Error in ChameleonHost.""" 27 pass 28 29 30class 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 181def 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