1# Lint as: python2, python3 2# Copyright 2018 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 6import contextlib 7import logging 8import os 9 10from autotest_lib.client.common_lib import error 11from autotest_lib.server import autotest 12from autotest_lib.server.cros.tradefed import tradefed_constants as constants 13 14 15class ChromeLogin(object): 16 """Context manager to handle Chrome login state.""" 17 18 def need_reboot(self, hard_reboot=False): 19 """Marks state as "dirty" - reboot needed during/after test.""" 20 logging.info('Will reboot DUT when Chrome stops.') 21 self._need_reboot = True 22 if hard_reboot and self._host.servo: 23 self._hard_reboot_on_failure = True 24 25 def __init__(self, 26 host, 27 board=None, 28 dont_override_profile=False, 29 enable_default_apps=False, 30 toggle_ndk=False, 31 log_dir=None): 32 """Initializes the ChromeLogin object. 33 34 @param board: optional parameter to extend timeout for login for slow 35 DUTs. Used in particular for virtual machines. 36 @param dont_override_profile: reuses the existing test profile if any 37 @param enable_default_apps: enables default apps (like Files app) 38 @param toggle_ndk: toggles native bridge engine switch. 39 @param log_dir: Any log files for this Chrome session is written to this 40 directory. 41 """ 42 self._host = host 43 self._timeout = constants.LOGIN_BOARD_TIMEOUT.get( 44 board, constants.LOGIN_DEFAULT_TIMEOUT) 45 self._dont_override_profile = dont_override_profile 46 self._enable_default_apps = enable_default_apps 47 self._need_reboot = False 48 self._hard_reboot_on_failure = False 49 self._toggle_ndk = toggle_ndk 50 self._log_dir = log_dir 51 52 def _cmd_builder(self, verbose=False): 53 """Gets remote command to start browser with ARC enabled.""" 54 # If autotest is not installed on the host, as with moblab at times, 55 # getting the autodir will raise an exception. 56 cmd = autotest.Autotest.get_installed_autodir(self._host) 57 cmd += '/bin/autologin.py --arc' 58 59 # We want to suppress the Google doodle as it is not part of the image 60 # and can be different content every day interacting with testing. 61 cmd += ' --no-startup-window' 62 # Disable several forms of auto-installation to stablize the tests. 63 cmd += ' --no-arc-syncs' 64 # TODO(b/196460968) delete after M96 branch, or after finishing the 65 # experiment. 66 cmd += ' --feature=NotificationsRefresh' 67 # Toggle the translation from houdini to ndk 68 if self._toggle_ndk: 69 cmd += ' --toggle_ndk' 70 if self._dont_override_profile: 71 logging.info('Starting Chrome with a possibly reused profile.') 72 cmd += ' --dont_override_profile' 73 else: 74 logging.info('Starting Chrome with a fresh profile.') 75 if self._enable_default_apps: 76 logging.info('Using --enable_default_apps to start Chrome.') 77 cmd += ' --enable_default_apps' 78 if not verbose: 79 cmd += ' > /dev/null 2>&1' 80 return cmd 81 82 def _login_by_script(self, timeout, verbose): 83 """Runs the autologin.py script on the DUT to log in.""" 84 self._host.run( 85 self._cmd_builder(verbose=verbose), 86 ignore_status=False, 87 verbose=verbose, 88 timeout=timeout) 89 90 def _login(self, timeout, verbose=False, install_autotest=False): 91 """Logs into Chrome. Raises an exception on failure.""" 92 if not install_autotest: 93 try: 94 # Assume autotest to be already installed. 95 self._login_by_script(timeout=timeout, verbose=verbose) 96 except autotest.AutodirNotFoundError: 97 logging.warning('Autotest not installed, forcing install...') 98 install_autotest = True 99 100 if install_autotest: 101 # Installs the autotest client to the DUT by running a no-op test. 102 autotest.Autotest(self._host).run_timed_test( 103 'stub_Pass', timeout=2 * timeout, check_client_result=True) 104 # The (re)run the login script. 105 self._login_by_script(timeout=timeout, verbose=verbose) 106 107 # Quick check if Android has really started. When autotest client 108 # installed on the DUT was partially broken, the script may succeed 109 # without actually logging into Chrome/Android. See b/129382439. 110 self._host.run( 111 # "/data/anr" is an arbitrary directory accessible only after 112 # proper login and data mount. 113 'android-sh -c "ls /data/anr"', 114 ignore_status=False, timeout=9) 115 116 def enter(self): 117 """Logs into Chrome with retry.""" 118 timeout = self._timeout 119 try: 120 logging.info('Ensure Android is running (timeout=%d)...', timeout) 121 self._login(timeout=timeout) 122 except Exception as e: 123 logging.error('Login failed.', exc_info=e) 124 # Retry with more time, with refreshed client autotest installation, 125 # and the DUT cleanup by rebooting. This can hide some failures. 126 self._reboot() 127 timeout *= 2 128 logging.info('Retrying failed login (timeout=%d)...', timeout) 129 try: 130 self._login(timeout=timeout, 131 verbose=True, 132 install_autotest=True) 133 except Exception as e2: 134 logging.error('Failed to login to Chrome', exc_info=e2) 135 raise error.TestError('Failed to login to Chrome') 136 137 def exit(self): 138 """On exit restart the browser or reboot the machine. 139 140 If self._log_dir is set, the VM kernel log is written 141 to a file. 142 143 """ 144 if self._log_dir: 145 self._write_kernel_log() 146 147 if not self._need_reboot: 148 try: 149 self._restart() 150 except: 151 logging.error('Restarting browser has failed.') 152 self.need_reboot() 153 if self._need_reboot: 154 self._reboot() 155 156 def _write_kernel_log(self): 157 """Writes ARCVM kernel logs.""" 158 if not os.path.exists(self._log_dir): 159 os.makedirs(self._log_dir) 160 161 output_path = os.path.join( 162 self._log_dir, '%s_vm_pstore_dump.txt' % self._host.hostname) 163 164 with open(output_path, 'w') as f: 165 try: 166 logging.info('Getting VM kernel logs.') 167 self._host.run('/usr/bin/vm_pstore_dump', stdout_tee=f) 168 except Exception as e: 169 logging.error('vm_pstore_dump command failed: %s', e) 170 else: 171 logging.info('Wrote VM kernel logs.') 172 173 def _restart(self): 174 """Restart Chrome browser.""" 175 # We clean up /tmp (which is memory backed) from crashes and 176 # other files. A reboot would have cleaned /tmp as well. 177 # TODO(ihf): Remove "start ui" which is a nicety to non-ARC tests (i.e. 178 # now we wait on login screen, but login() above will 'stop ui' again 179 # before launching Chrome with ARC enabled). 180 logging.info('Skipping reboot, restarting browser.') 181 script = 'stop ui' 182 script += '&& find /tmp/ -mindepth 1 -delete ' 183 script += '&& start ui' 184 self._host.run(script, ignore_status=False, verbose=False, timeout=120) 185 186 def _reboot(self): 187 """Reboot the machine.""" 188 if self._hard_reboot_on_failure: 189 logging.info('Powering OFF the DUT: %s', self._host) 190 self._host.servo.get_power_state_controller().power_off() 191 logging.info('Powering ON the DUT: %s', self._host) 192 self._host.servo.get_power_state_controller().power_on() 193 else: 194 logging.info('Rebooting...') 195 self._host.reboot() 196 197 198@contextlib.contextmanager 199def login_chrome(hosts, **kwargs): 200 """Returns Chrome log-in context manager for multiple hosts. """ 201 # TODO(pwang): Chromelogin takes 10+ seconds for it to successfully 202 # enter. Parallelize if this becomes a bottleneck. 203 instances = [ChromeLogin(host, **kwargs) for host in hosts] 204 for instance in instances: 205 instance.enter() 206 yield instances 207 for instance in instances: 208 instance.exit() 209