1 # Copyright 2018 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 import logging 6 7 from autotest_lib.server import autotest 8 from autotest_lib.server.cros import tradefed_constants as constants 9 10 11 class ChromeLogin(object): 12 """Context manager to handle Chrome login state.""" 13 14 def need_reboot(self): 15 """Marks state as "dirty" - reboot needed during/after test.""" 16 logging.info('Will reboot DUT when Chrome stops.') 17 self._need_reboot = True 18 19 def need_restart(self): 20 """Marks state as "dirty" - restart needed after test.""" 21 self._need_restart = True 22 23 def __init__(self, host, kwargs): 24 """Initializes the _ChromeLogin object. 25 26 @param reboot: indicate if a reboot before destruction is required. 27 @param restart: indicate if a restart before destruction is required. 28 @param board: optional parameter to extend timeout for login for slow 29 DUTs. Used in particular for virtual machines. 30 """ 31 self._host = host 32 self._cts_helper_kwargs = kwargs 33 # We will override reboot/restart options to some degree. Keep track 34 # of them in a local copy. 35 self._need_reboot = False 36 if kwargs.get('reboot'): 37 self.need_reboot() 38 self._need_restart = False 39 if kwargs.get('restart'): 40 self.need_restart() 41 self._timeout = constants.LOGIN_DEFAULT_TIMEOUT 42 board = kwargs.get('board') 43 if board in constants.LOGIN_BOARD_TIMEOUT: 44 self._timeout = constants.LOGIN_BOARD_TIMEOUT[board] 45 46 def _cmd_builder(self, verbose=False): 47 """Gets remote command to start browser with ARC enabled.""" 48 # If autotest is not installed on the host, as with moblab at times, 49 # getting the autodir will raise an exception. 50 cmd = autotest.Autotest.get_installed_autodir(self._host) 51 cmd += '/bin/autologin.py --arc' 52 if self._cts_helper_kwargs.get('dont_override_profile'): 53 logging.info('Using --dont_override_profile to start Chrome.') 54 cmd += ' --dont_override_profile' 55 else: 56 logging.info('Not using --dont_override_profile to start Chrome.') 57 if not verbose: 58 cmd += ' > /dev/null 2>&1' 59 return cmd 60 61 def login(self, timeout=None, raise_exception=False, verbose=False): 62 """Logs into Chrome.""" 63 if not timeout: 64 timeout = self._timeout 65 try: 66 # We used to call cheets_StartAndroid, but it is a little faster to 67 # call a script on the DUT. This also saves CPU time on the server. 68 self._host.run( 69 self._cmd_builder(), 70 ignore_status=False, 71 verbose=verbose, 72 timeout=timeout) 73 return True 74 except autotest.AutodirNotFoundError: 75 # Autotest is not installed (can happen on moblab after image 76 # install). Fall back to logging in via client test, which as a side 77 # effect installs autotest in the right place, so we should not hit 78 # this slow path repeatedly. 79 logging.warning('Autotest not installed, fallback to slow path...') 80 try: 81 autotest.Autotest(self._host).run_timed_test( 82 'cheets_StartAndroid', 83 timeout=2 * timeout, 84 check_client_result=True, 85 **self._cts_helper_kwargs) 86 return True 87 except: 88 # We were unable to start the browser/Android. Maybe we can 89 # salvage the DUT by rebooting. This can hide some failures. 90 self.reboot() 91 if raise_exception: 92 raise 93 except: 94 # We were unable to start the browser/Android. Maybe we can 95 # salvage the DUT by rebooting. This can hide some failures. 96 self.reboot() 97 if raise_exception: 98 raise 99 return False 100 101 def __enter__(self): 102 """Logs into Chrome with retry.""" 103 timeout = self._timeout 104 logging.info('Ensure Android is running (timeout=%d)...', timeout) 105 if not self.login(timeout=timeout): 106 timeout *= 2 107 # The DUT reboots after unsuccessful login, try with more time. 108 logging.info('Retrying failed login (timeout=%d)...', timeout) 109 self.login(timeout=timeout, raise_exception=True, verbose=True) 110 return self 111 112 def __exit__(self, exc_type, exc_value, traceback): 113 """On exit restart the browser or reboot the machine. 114 115 @param exc_type: Exception type if an exception is raised from the 116 with-block. 117 @param exc_value: Exception instance if an exception is raised from 118 the with-block. 119 @param traceback: Stack trace info if an exception is raised from 120 the with-block. 121 @return None, indicating not to ignore an exception from the with-block 122 if raised. 123 """ 124 if not self._need_reboot: 125 logging.info('Skipping reboot, restarting browser.') 126 try: 127 self.restart() 128 except: 129 logging.error('Restarting browser has failed.') 130 self.need_reboot() 131 if self._need_reboot: 132 self.reboot(exc_type, exc_value, traceback) 133 134 def restart(self): 135 # We clean up /tmp (which is memory backed) from crashes and 136 # other files. A reboot would have cleaned /tmp as well. 137 # TODO(ihf): Remove "start ui" which is a nicety to non-ARC tests (i.e. 138 # now we wait on login screen, but login() above will 'stop ui' again 139 # before launching Chrome with ARC enabled). 140 script = 'stop ui' 141 script += '&& find /tmp/ -mindepth 1 -delete ' 142 script += '&& start ui' 143 self._host.run(script, ignore_status=False, verbose=False, timeout=120) 144 145 def reboot(self, exc_type=None, exc_value=None, traceback=None): 146 """Reboot the machine. 147 148 @param exc_type: Exception type if an exception is raised from the 149 with-block. 150 @param exc_value: Exception instance if an exception is raised from 151 the with-block. 152 @param traceback: Stack trace info if an exception is raised from 153 the with-block. 154 @return None, indicating not to ignore an exception from the with-block 155 if raised. 156 """ 157 logging.info('Rebooting...') 158 try: 159 self._host.reboot() 160 self._need_reboot = False 161 except Exception: 162 if exc_type is None: 163 raise 164 # If an exception is raise from the with-block, just record the 165 # exception for the rebooting to avoid ignoring the original 166 # exception. 167 logging.exception('Rebooting failed.') 168