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