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 5import logging 6 7from autotest_lib.server import autotest 8from autotest_lib.server.cros import tradefed_constants as constants 9 10 11class 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 # DUT power off -> on cycle will still adhere DUT's reboot preference. 46 self._hard_reboot_on_failure = False 47 if kwargs.get('hard_reboot_on_failure') and self._need_reboot: 48 self._hard_reboot_on_failure = True 49 50 def _cmd_builder(self, verbose=False): 51 """Gets remote command to start browser with ARC enabled.""" 52 # If autotest is not installed on the host, as with moblab at times, 53 # getting the autodir will raise an exception. 54 cmd = autotest.Autotest.get_installed_autodir(self._host) 55 cmd += '/bin/autologin.py --arc' 56 if self._cts_helper_kwargs.get('dont_override_profile'): 57 logging.info('Using --dont_override_profile to start Chrome.') 58 cmd += ' --dont_override_profile' 59 else: 60 logging.info('Not using --dont_override_profile to start Chrome.') 61 if not verbose: 62 cmd += ' > /dev/null 2>&1' 63 return cmd 64 65 def login(self, timeout=None, raise_exception=False, verbose=False): 66 """Logs into Chrome.""" 67 if not timeout: 68 timeout = self._timeout 69 try: 70 # We used to call cheets_StartAndroid, but it is a little faster to 71 # call a script on the DUT. This also saves CPU time on the server. 72 self._host.run( 73 self._cmd_builder(), 74 ignore_status=False, 75 verbose=verbose, 76 timeout=timeout) 77 return True 78 except autotest.AutodirNotFoundError: 79 # Autotest is not installed (can happen on moblab after image 80 # install). Run dummy_Pass to foce autotest install, before trying 81 # to login again. 82 logging.warning( 83 'Autotest not installed, forcing install using dummy_Pass...') 84 try: 85 autotest.Autotest(self._host).run_timed_test( 86 'dummy_Pass', 87 timeout=2 * timeout, 88 check_client_result=True, 89 **self._cts_helper_kwargs) 90 self._host.run( 91 self._cmd_builder(), 92 ignore_status=False, 93 verbose=verbose, 94 timeout=timeout) 95 return True 96 except: 97 # We were unable to start the browser/Android. Maybe we can 98 # salvage the DUT by rebooting. This can hide some failures. 99 self.reboot() 100 if raise_exception: 101 raise 102 except: 103 # We were unable to start the browser/Android. Maybe we can 104 # salvage the DUT by rebooting. This can hide some failures. 105 self.reboot() 106 if raise_exception: 107 raise 108 return False 109 110 def enter(self): 111 """Logs into Chrome with retry.""" 112 timeout = self._timeout 113 logging.info('Ensure Android is running (timeout=%d)...', timeout) 114 if not self.login(timeout=timeout): 115 timeout *= 2 116 # The DUT reboots after unsuccessful login, try with more time. 117 logging.info('Retrying failed login (timeout=%d)...', timeout) 118 self.login(timeout=timeout, raise_exception=True, verbose=True) 119 return self 120 121 def __enter__(self): 122 """Logs into Chrome with retry.""" 123 return self.enter() 124 125 def exit(self, exc_type=None, exc_value=None, traceback=None): 126 """On exit restart the browser or reboot the machine. 127 128 @param exc_type: Exception type if an exception is raised from the 129 with-block. 130 @param exc_value: Exception instance if an exception is raised from 131 the with-block. 132 @param traceback: Stack trace info if an exception is raised from 133 the with-block. 134 @return None, indicating not to ignore an exception from the with-block 135 if raised. 136 """ 137 if not self._need_reboot: 138 logging.info('Skipping reboot, restarting browser.') 139 try: 140 self.restart() 141 except: 142 logging.error('Restarting browser has failed.') 143 self.need_reboot() 144 if self._need_reboot: 145 self.reboot(exc_type, exc_value, traceback) 146 147 def __exit__(self, exc_type, exc_value, traceback): 148 """On exit restart the browser or reboot the machine. 149 150 @param exc_type: Exception type if an exception is raised from the 151 with-block. 152 @param exc_value: Exception instance if an exception is raised from 153 the with-block. 154 @param traceback: Stack trace info if an exception is raised from 155 the with-block. 156 @return None, indicating not to ignore an exception from the with-block 157 if raised. 158 """ 159 self.exit(exc_type, exc_value, traceback) 160 161 def restart(self): 162 """Restart Chrome browser.""" 163 # We clean up /tmp (which is memory backed) from crashes and 164 # other files. A reboot would have cleaned /tmp as well. 165 # TODO(ihf): Remove "start ui" which is a nicety to non-ARC tests (i.e. 166 # now we wait on login screen, but login() above will 'stop ui' again 167 # before launching Chrome with ARC enabled). 168 script = 'stop ui' 169 script += '&& find /tmp/ -mindepth 1 -delete ' 170 script += '&& start ui' 171 self._host.run(script, ignore_status=False, verbose=False, timeout=120) 172 173 def reboot(self, exc_type=None, exc_value=None, traceback=None): 174 """Reboot the machine. 175 176 @param exc_type: Exception type if an exception is raised from the 177 with-block. 178 @param exc_value: Exception instance if an exception is raised from 179 the with-block. 180 @param traceback: Stack trace info if an exception is raised from 181 the with-block. 182 @return None, indicating not to ignore an exception from the with-block 183 if raised. 184 """ 185 logging.info('Rebooting...') 186 try: 187 if self._hard_reboot_on_failure and self._host.servo: 188 logging.info('Powering OFF the DUT: %s', self._host) 189 self._host.servo.get_power_state_controller().power_off() 190 logging.info('Powering ON the DUT: %s', self._host) 191 self._host.servo.get_power_state_controller().power_on() 192 self._hard_reboot_on_failure = False 193 else: 194 self._host.reboot() 195 self._need_reboot = False 196 except Exception: 197 if exc_type is None: 198 raise 199 # If an exception is raise from the with-block, just record the 200 # exception for the rebooting to avoid ignoring the original 201 # exception. 202 logging.exception('Rebooting failed.') 203