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 5import logging 6import os 7import re 8import time 9 10import common 11from autotest_lib.client.common_lib import error, global_config 12from autotest_lib.client.common_lib.cros import retry 13from autotest_lib.server.cros.dynamic_suite import frontend_wrappers 14from autotest_lib.server.hosts import cros_host 15from autotest_lib.server.hosts import cros_repair 16 17from chromite.lib import timeout_util 18 19AUTOTEST_INSTALL_DIR = global_config.global_config.get_config_value( 20 'SCHEDULER', 'drone_installation_directory') 21 22#'/usr/local/autotest' 23SHADOW_CONFIG_PATH = '%s/shadow_config.ini' % AUTOTEST_INSTALL_DIR 24ATEST_PATH = '%s/cli/atest' % AUTOTEST_INSTALL_DIR 25SUBNET_DUT_SEARCH_RE = ( 26 r'/?.*\((?P<ip>192.168.231.*)\) at ' 27 '(?P<mac>[0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])') 28MOBLAB_HOME = '/home/moblab' 29MOBLAB_BOTO_LOCATION = '%s/.boto' % MOBLAB_HOME 30MOBLAB_LAUNCH_CONTROL_KEY_LOCATION = '%s/.launch_control_key' % MOBLAB_HOME 31MOBLAB_SERVICE_ACCOUNT_LOCATION = '%s/.service_account.json' % MOBLAB_HOME 32MOBLAB_AUTODIR = '/usr/local/autodir' 33DHCPD_LEASE_FILE = '/var/lib/dhcp/dhcpd.leases' 34MOBLAB_SERVICES = ['moblab-scheduler-init', 35 'moblab-database-init', 36 'moblab-devserver-init', 37 'moblab-gsoffloader-init', 38 'moblab-gsoffloader_s-init'] 39MOBLAB_PROCESSES = ['apache2', 'dhcpd'] 40DUT_VERIFY_SLEEP_SECS = 5 41DUT_VERIFY_TIMEOUT = 15 * 60 42MOBLAB_TMP_DIR = '/mnt/moblab/tmp' 43MOBLAB_PORT = 80 44 45 46class UpstartServiceNotRunning(error.AutoservError): 47 """An expected upstart service was not in the expected state.""" 48 49 def __init__(self, service_name): 50 """Create us. 51 @param service_name: Name of the service_name that was in the worng 52 state. 53 """ 54 super(UpstartServiceNotRunning, self).__init__( 55 'Upstart service %s not in running state.' % service_name) 56 57 58class MoblabHost(cros_host.CrosHost): 59 """Moblab specific host class.""" 60 61 62 def _initialize_frontend_rpcs(self, timeout_min): 63 """Initialize frontends for AFE and TKO for a moblab host. 64 65 We tunnel all communication to the frontends through an SSH tunnel as 66 many testing environments block everything except SSH access to the 67 moblab DUT. 68 69 @param timeout_min: The timeout minuties for AFE services. 70 """ 71 web_address = self.rpc_server_tracker.tunnel_connect(MOBLAB_PORT) 72 # Pass timeout_min to self.afe 73 self.afe = frontend_wrappers.RetryingAFE(timeout_min=timeout_min, 74 user='moblab', 75 server=web_address) 76 # Use default timeout_min of MoblabHost for self.tko 77 self.tko = frontend_wrappers.RetryingTKO(timeout_min=self.timeout_min, 78 user='moblab', 79 server=web_address) 80 81 82 def _initialize(self, *args, **dargs): 83 super(MoblabHost, self)._initialize(*args, **dargs) 84 # TODO(jrbarnette): Our superclass already initialized 85 # _repair_strategy, and now we're re-initializing it here. 86 # That's awkward, if not actually wrong. 87 self._repair_strategy = cros_repair.create_moblab_repair_strategy() 88 self.timeout_min = dargs.get('rpc_timeout_min', 1) 89 self._initialize_frontend_rpcs(self.timeout_min) 90 91 92 @staticmethod 93 def check_host(host, timeout=10): 94 """ 95 Check if the given host is an moblab host. 96 97 @param host: An ssh host representing a device. 98 @param timeout: The timeout for the run command. 99 100 101 @return: True if the host device has adb. 102 103 @raises AutoservRunError: If the command failed. 104 @raises AutoservSSHTimeout: Ssh connection has timed out. 105 """ 106 try: 107 result = host.run( 108 'grep -q moblab /etc/lsb-release && ' 109 '! test -f /mnt/stateful_partition/.android_tester', 110 ignore_status=True, timeout=timeout) 111 except (error.AutoservRunError, error.AutoservSSHTimeout): 112 return False 113 return result.exit_status == 0 114 115 116 def install_boto_file(self, boto_path=''): 117 """Install a boto file on the Moblab device. 118 119 @param boto_path: Path to the boto file to install. If None, sends the 120 boto file in the current HOME directory. 121 122 @raises error.TestError if the boto file does not exist. 123 """ 124 if not boto_path: 125 boto_path = os.path.join(os.getenv('HOME'), '.boto') 126 if not os.path.exists(boto_path): 127 raise error.TestError('Boto File:%s does not exist.' % boto_path) 128 self.send_file(boto_path, MOBLAB_BOTO_LOCATION) 129 self.run('chown moblab:moblab %s' % MOBLAB_BOTO_LOCATION) 130 131 132 def get_autodir(self): 133 """Return the directory to install autotest for client side tests.""" 134 return self.autodir or MOBLAB_AUTODIR 135 136 137 def run_as_moblab(self, command, **kwargs): 138 """Moblab commands should be ran as the moblab user not root. 139 140 @param command: Command to run as user moblab. 141 """ 142 command = "su - moblab -c '%s'" % command 143 return self.run(command, **kwargs) 144 145 146 def wait_afe_up(self, timeout_min=5): 147 """Wait till the AFE is up and loaded. 148 149 Attempt to reach the Moblab's AFE and database through its RPC 150 interface. 151 152 @param timeout_min: Minutes to wait for the AFE to respond. Default is 153 5 minutes. 154 155 @raises urllib2.HTTPError if AFE does not respond within the timeout. 156 """ 157 # Use moblabhost's own AFE object with a longer timeout to wait for the 158 # AFE to load. Also re-create the ssh tunnel for connections to moblab. 159 # Set the timeout_min to be longer than self.timeout_min for rebooting. 160 self._initialize_frontend_rpcs(timeout_min) 161 # Verify the AFE can handle a simple request. 162 self._check_afe() 163 # Reset the timeout_min after rebooting checks for afe services. 164 self.afe.set_timeout(self.timeout_min) 165 166 167 def _wake_devices(self): 168 """Search the subnet and attempt to ping any available duts. 169 170 Fills up the arp table with entries about devices on the subnet. 171 172 Either uses fping or directly pings devices listed in the dhcpd lease 173 file. 174 """ 175 fping_result = self.run(('fping -g 192.168.231.100 192.168.231.110 ' 176 '-a -c 10 -p 30 -q'), 177 ignore_status=True) 178 # If fping is not on the system, ping entries in the dhcpd lease file. 179 if fping_result.exit_status == 127: 180 leases = set(self.run('grep ^lease %s' % DHCPD_LEASE_FILE, 181 ignore_status=True).stdout.splitlines()) 182 for lease in leases: 183 ip = re.match('lease (?P<ip>.*) {', lease).groups('ip') 184 self.run('ping %s -w 1' % ip, ignore_status=True) 185 186 187 def add_dut(self, hostname): 188 """Add a DUT hostname to the AFE. 189 190 @param hostname: DUT hostname to add. 191 """ 192 result = self.run_as_moblab('%s host create %s' % (ATEST_PATH, 193 hostname)) 194 logging.debug('atest host create output for host %s:\n%s', 195 hostname, result.stdout) 196 197 198 def find_and_add_duts(self): 199 """Discover DUTs on the testing subnet and add them to the AFE. 200 201 Runs 'arp -a' on the Moblab host and parses the output to discover DUTs 202 and if they are not already in the AFE, adds them. 203 """ 204 self._wake_devices() 205 existing_hosts = [host.hostname for host in self.afe.get_hosts()] 206 arp_command = self.run('arp -a') 207 for line in arp_command.stdout.splitlines(): 208 match = re.match(SUBNET_DUT_SEARCH_RE, line) 209 if match: 210 dut_hostname = match.group('ip') 211 if dut_hostname in existing_hosts: 212 break 213 # SSP package ip's start at 150 for the moblab, so it is not 214 # a DUT 215 if int(dut_hostname.split('.')[-1]) > 150: 216 break 217 self.add_dut(dut_hostname) 218 219 220 def verify_software(self): 221 """Create the autodir then do standard verify.""" 222 # In case cleanup or powerwash wiped the autodir, create an empty 223 # directory. 224 # Removing this mkdir command will result in the disk size check 225 # not being performed. 226 self.run('mkdir -p %s' % MOBLAB_AUTODIR) 227 super(MoblabHost, self).verify_software() 228 229 230 def _verify_upstart_service(self, service, timeout_m): 231 """Verify that the given moblab service is running. 232 233 @param service: The upstart service to check for. 234 @timeout_m: Timeout (in minuts) before giving up. 235 @raises TimeoutException or UpstartServiceNotRunning if service isn't 236 running. 237 """ 238 @retry.retry(error.AutoservError, timeout_min=timeout_m, delay_sec=10) 239 def _verify(): 240 if not self.upstart_status(service): 241 raise UpstartServiceNotRunning(service) 242 _verify() 243 244 def verify_moblab_services(self, timeout_m): 245 """Verify the required Moblab services are up and running. 246 247 @param timeout_m: Timeout (in minutes) for how long to wait for services 248 to start. Actual time taken may be slightly more than this. 249 @raises AutoservError if any moblab service is not running. 250 """ 251 if not MOBLAB_SERVICES: 252 return 253 254 service = MOBLAB_SERVICES[0] 255 try: 256 # First service can take a long time to start, especially on first 257 # boot where container setup can take 5-10 minutes, depending on the 258 # device. 259 self._verify_upstart_service(service, timeout_m) 260 except error.TimeoutException: 261 raise error.UpstartServiceNotRunning(service) 262 263 for service in MOBLAB_SERVICES[1:]: 264 try: 265 # Follow up services should come up quickly. 266 self._verify_upstart_service(service, 0.5) 267 except error.TimeoutException: 268 raise error.UpstartServiceNotRunning(service) 269 270 for process in MOBLAB_PROCESSES: 271 try: 272 self.run('pgrep %s' % process) 273 except error.AutoservRunError: 274 raise error.AutoservError('Moblab process: %s is not running.' 275 % process) 276 277 278 def _check_afe(self): 279 """Verify whether afe of moblab works before verifying its DUTs. 280 281 Verifying moblab sometimes happens after a successful provision, in 282 which case moblab is restarted but tunnel of afe is not re-connected. 283 This func is used to check whether afe is working now. 284 285 @return True if afe works. 286 @raises error.AutoservError if AFE is down; other exceptions are passed 287 through. 288 """ 289 try: 290 self.afe.get_hosts() 291 except (error.TimeoutException, timeout_util.TimeoutError) as e: 292 raise error.AutoservError('Moblab AFE is not responding: %s' % 293 str(e)) 294 except Exception as e: 295 logging.error('Unknown exception when checking moblab AFE: %s', e) 296 raise 297 298 return True 299 300 301 def verify_duts(self): 302 """Verify the Moblab DUTs are up and running. 303 304 @raises AutoservError if no DUTs are in the Ready State. 305 """ 306 hosts = self.afe.reverify_hosts() 307 logging.debug('DUTs scheduled for reverification: %s', hosts) 308 309 310 def verify_special_tasks_complete(self): 311 """Wait till the special tasks on the moblab host are complete.""" 312 total_time = 0 313 while (self.afe.get_special_tasks(is_complete=False) and 314 total_time < DUT_VERIFY_TIMEOUT): 315 total_time = total_time + DUT_VERIFY_SLEEP_SECS 316 time.sleep(DUT_VERIFY_SLEEP_SECS) 317 if not self.afe.get_hosts(status='Ready'): 318 for host in self.afe.get_hosts(): 319 logging.error('DUT: %s Status: %s', host, host.status) 320 raise error.AutoservError('Moblab has 0 Ready DUTs') 321 322 323 def get_platform(self): 324 """Determine the correct platform label for this host. 325 326 For Moblab devices '_moblab' is appended. 327 328 @returns a string representing this host's platform. 329 """ 330 return super(MoblabHost, self).get_platform() + '_moblab' 331 332 333 def make_tmp_dir(self, base=MOBLAB_TMP_DIR): 334 """Creates a temporary directory. 335 336 @param base: The directory where it should be created. 337 338 @return Path to a newly created temporary directory. 339 """ 340 self.run('mkdir -p %s' % base) 341 return self.run('mktemp -d -p %s' % base).stdout.strip() 342 343 344 def get_os_type(self): 345 return 'moblab' 346