1# Copyright 2016 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# arc_util.py is supposed to be called from chrome.py for ARC specific logic. 6# It should not import arc.py since it will create a import loop. 7 8import collections 9import logging 10import os 11import select 12import tempfile 13import time 14 15from autotest_lib.client.bin import utils 16from autotest_lib.client.common_lib import error 17from autotest_lib.client.common_lib import file_utils 18from autotest_lib.client.common_lib.cros import arc_common 19from telemetry.core import exceptions 20from telemetry.internal.browser import extension_page 21 22_ARC_SUPPORT_HOST_URL = 'chrome-extension://cnbgggchhmkkdmeppjobngjoejnihlei/' 23_ARC_SUPPORT_HOST_PAGENAME = '_generated_background_page.html' 24_DUMPSTATE_DEFAULT_TIMEOUT = 20 25_DUMPSTATE_PATH = '/var/log/arc-dumpstate.log' 26_DUMPSTATE_PIPE_PATH = '/run/arc/bugreport/pipe' 27_USERNAME = 'crosarcplusplustest@gmail.com' 28_ARCP_URL = 'https://sites.google.com/a/chromium.org/dev/chromium-os' \ 29 '/testing/arcplusplus-testing/arcp' 30_OPT_IN_BEGIN = 'Initializing ARC opt-in flow.' 31_OPT_IN_SKIP = 'Skip waiting provisioning completed.' 32_OPT_IN_FINISH = 'ARC opt-in flow complete.' 33 34_SIGN_IN_TIMEOUT = 120 35_SIGN_IN_CHECK_INTERVAL = 1 36 37 38# Describes ARC session state. 39# - provisioned is set to True once ARC is provisioned. 40# - tos_needed is set to True in case ARC Terms of Service need to be shown. 41# This depends on ARC Terms of Service acceptance and policy for 42# ARC preferences, such as backup and restore and use of location 43# services. 44ArcSessionState = collections.namedtuple( 45 'ArcSessionState', ['provisioned', 'tos_needed']) 46 47 48def get_arc_session_state(autotest_ext): 49 """Returns the runtime state of ARC session. 50 51 @param autotest_ext private autotest API. 52 53 @raises error.TestFail in case autotest API failure or if ARC is not 54 allowed for the device. 55 56 @return ArcSessionState that describes the state of current ARC session. 57 58 """ 59 get_arc_state = ''' 60 new Promise((resolve, reject) => { 61 chrome.autotestPrivate.getArcState((state) => { 62 if (chrome.runtime.lastError) { 63 reject(new Error(chrome.runtime.lastError.message)); 64 return; 65 } 66 resolve(state); 67 }); 68 }) 69 ''' 70 try: 71 state = autotest_ext.EvaluateJavaScript(get_arc_state, promise=True) 72 return ArcSessionState(state['provisioned'], state['tosNeeded']) 73 except exceptions.EvaluateException as e: 74 raise error.TestFail('Could not get ARC session state "%s".' % e) 75 76 77def _wait_arc_provisioned(autotest_ext): 78 """Waits until ARC provisioning is completed. 79 80 @param autotest_ext private autotest API. 81 82 @raises: error.TimeoutException if provisioning is not complete. 83 84 """ 85 utils.poll_for_condition( 86 condition=lambda: get_arc_session_state(autotest_ext).provisioned, 87 desc='Wait for ARC is provisioned', 88 timeout=_SIGN_IN_TIMEOUT, 89 sleep_interval=_SIGN_IN_CHECK_INTERVAL) 90 91 92def should_start_arc(arc_mode): 93 """ 94 Determines whether ARC should be started. 95 96 @param arc_mode: mode as defined in arc_common. 97 98 @returns: True or False. 99 100 """ 101 logging.debug('ARC is enabled in mode %s', arc_mode) 102 assert arc_mode is None or arc_mode in arc_common.ARC_MODES 103 return arc_mode in [arc_common.ARC_MODE_ENABLED, 104 arc_common.ARC_MODE_ENABLED_ASYNC] 105 106 107def post_processing_after_browser(chrome, timeout): 108 """ 109 Called when a new browser instance has been initialized. 110 111 Note that this hook function is called regardless of arc_mode. 112 113 @param chrome: Chrome object. 114 115 """ 116 # Remove any stale dumpstate files. 117 if os.path.isfile(_DUMPSTATE_PATH): 118 os.unlink(_DUMPSTATE_PATH) 119 120 # Wait for Android container ready if ARC is enabled. 121 if chrome.arc_mode == arc_common.ARC_MODE_ENABLED: 122 try: 123 arc_common.wait_for_android_boot(timeout) 124 except Exception: 125 # Save dumpstate so that we can figure out why boot does not 126 # complete. 127 _save_android_dumpstate() 128 raise 129 130 131def pre_processing_before_close(chrome): 132 """ 133 Called when the browser instance is being closed. 134 135 Note that this hook function is called regardless of arc_mode. 136 137 @param chrome: Chrome object. 138 139 """ 140 if not should_start_arc(chrome.arc_mode): 141 return 142 # TODO(b/29341443): Implement stopping of adb logcat when we start adb 143 # logcat for all tests 144 145 # Save dumpstate just before logout. 146 _save_android_dumpstate() 147 148 149def _save_android_dumpstate(timeout=_DUMPSTATE_DEFAULT_TIMEOUT): 150 """ 151 Triggers a dumpstate and saves its contents to to /var/log/arc-dumpstate.log 152 with logging. 153 154 Exception thrown while doing dumpstate will be ignored. 155 156 @param timeout: The timeout in seconds. 157 """ 158 159 try: 160 logging.info('Saving Android dumpstate.') 161 with open(_DUMPSTATE_PATH, 'w') as out: 162 # _DUMPSTATE_PIPE_PATH is a named pipe, so it permanently blocks if 163 # opened normally if the other end has not been opened. In order to 164 # avoid that, open the file with O_NONBLOCK and use a select loop to 165 # read from the file with a timeout. 166 fd = os.open(_DUMPSTATE_PIPE_PATH, os.O_RDONLY | os.O_NONBLOCK) 167 with os.fdopen(fd, 'r') as pipe: 168 end_time = time.time() + timeout 169 while True: 170 remaining_time = end_time - time.time() 171 if remaining_time <= 0: 172 break 173 rlist, _, _ = select.select([pipe], [], [], remaining_time) 174 if pipe not in rlist: 175 break 176 buf = os.read(pipe.fileno(), 1024) 177 if len(buf) == 0: 178 break 179 out.write(buf) 180 logging.info('Android dumpstate successfully saved.') 181 except Exception: 182 # Dumpstate is nice-to-have stuff. Do not make it as a fatal error. 183 logging.exception('Failed to save Android dumpstate.') 184 185 186def get_test_account_info(): 187 """Retrieve test account information.""" 188 with tempfile.NamedTemporaryFile() as pltp: 189 file_utils.download_file(_ARCP_URL, pltp.name) 190 password = pltp.read().rstrip() 191 return (_USERNAME, password) 192 193 194def set_browser_options_for_opt_in(b_options): 195 """ 196 Setup Chrome for gaia login and opt_in. 197 198 @param b_options: browser options object used by chrome.Chrome. 199 200 """ 201 b_options.username, b_options.password = get_test_account_info() 202 b_options.disable_default_apps = False 203 b_options.disable_component_extensions_with_background_pages = False 204 b_options.gaia_login = True 205 206 207def enable_play_store(autotest_ext, enabled, enable_managed_policy=True): 208 """ 209 Enable ARC++ Play Store 210 211 Do nothing if the account is managed and enable_managed_policy is set to 212 True. 213 214 @param autotest_ext: autotest extension object. 215 216 @param enabled: if True then perform opt-in, otherwise opt-out. 217 218 @param enable_managed_policy: If False then policy check is ignored for 219 managed user case and ARC++ is forced enabled or disabled. 220 221 @returns: True if the opt-in should continue; else False. 222 223 """ 224 225 if autotest_ext is None: 226 raise error.TestFail( 227 'Could not change the Play Store enabled state because ' 228 'autotest API does not exist') 229 230 # Skip enabling for managed users, since value is policy enforced. 231 # Return early if a managed user has ArcEnabled set to false. 232 get_play_store_state = ''' 233 new Promise((resolve, reject) => { 234 chrome.autotestPrivate.getPlayStoreState((state) => { 235 if (chrome.runtime.lastError) { 236 reject(new Error(chrome.runtime.lastError.message)); 237 return; 238 } 239 resolve(state); 240 }); 241 }) 242 ''' 243 try: 244 state = autotest_ext.EvaluateJavaScript(get_play_store_state, 245 promise=True) 246 if state['managed']: 247 # Handle managed case. 248 logging.info('Determined that ARC is managed by user policy.') 249 if enable_managed_policy: 250 if enabled == state['enabled']: 251 return True 252 logging.info('Returning early since ARC is policy-enforced.') 253 return False 254 logging.info('Forcing ARC %s, ignore managed state.', 255 ('enabled' if enabled else 'disabled')) 256 257 autotest_ext.ExecuteJavaScript(''' 258 chrome.autotestPrivate.setPlayStoreEnabled( 259 %s, function(enabled) {}); 260 ''' % ('true' if enabled else 'false')) 261 except exceptions.EvaluateException as e: 262 raise error.TestFail('Could not change the Play Store enabled state ' 263 ' via autotest API. "%s".' % e) 264 265 return True 266 267 268def find_opt_in_extension_page(browser): 269 """ 270 Find and verify the opt-in extension extension page. 271 272 @param browser: chrome.Chrome broswer object. 273 274 @returns: the extension page. 275 276 @raises: error.TestFail if extension is not found or is mal-formed. 277 278 """ 279 opt_in_extension_id = extension_page.UrlToExtensionId(_ARC_SUPPORT_HOST_URL) 280 try: 281 extension_pages = browser.extensions.GetByExtensionId( 282 opt_in_extension_id) 283 except Exception, e: 284 raise error.TestFail('Could not locate extension for arc opt-in. ' 285 'Make sure disable_default_apps is False. ' 286 '"%s".' % e) 287 288 extension_main_page = None 289 for page in extension_pages: 290 url = page.EvaluateJavaScript('location.href;') 291 if url.endswith(_ARC_SUPPORT_HOST_PAGENAME): 292 extension_main_page = page 293 break 294 if not extension_main_page: 295 raise error.TestError('Found opt-in extension but not correct page!') 296 extension_main_page.WaitForDocumentReadyStateToBeComplete() 297 298 js_code_did_start_conditions = ['termsPage != null', 299 '(termsPage.isManaged_ || termsPage.state_ == LoadState.LOADED)'] 300 try: 301 for condition in js_code_did_start_conditions: 302 extension_main_page.WaitForJavaScriptCondition(condition, 303 timeout=60) 304 except Exception, e: 305 raise error.TestError('Error waiting for "%s": "%s".' % (condition, e)) 306 307 return extension_main_page 308 309 310def opt_in_and_wait_for_completion(extension_main_page): 311 """ 312 Step through the user input of the opt-in extension and wait for completion. 313 314 @param extension_main_page: opt-in extension object. 315 316 @raises error.TestFail if opt-in doesn't complete after timeout. 317 318 """ 319 extension_main_page.ExecuteJavaScript('termsPage.onAgree()') 320 321 try: 322 extension_main_page.WaitForJavaScriptCondition('!appWindow', 323 timeout=_SIGN_IN_TIMEOUT) 324 except Exception, e: 325 js_read_error_message = """ 326 err = appWindow.contentWindow.document.getElementById( 327 "error-message"); 328 if (err) { 329 err.innerText; 330 } 331 """ 332 err_msg = extension_main_page.EvaluateJavaScript(js_read_error_message) 333 err_msg = err_msg.strip() 334 logging.error('Error: %r', err_msg) 335 if err_msg: 336 raise error.TestFail('Opt-in app error: %r' % err_msg) 337 else: 338 raise error.TestFail('Opt-in app did not finish running after %s ' 339 'seconds!' % _SIGN_IN_TIMEOUT) 340 # Reset termsPage to be able to reuse OptIn page and wait condition for ToS 341 # are loaded. 342 extension_main_page.ExecuteJavaScript('termsPage = null') 343 344 345def opt_in(browser, 346 autotest_ext, 347 enable_managed_policy=True, 348 wait_for_provisioning=True): 349 """ 350 Step through opt in and wait for it to complete. 351 352 Return early if the arc_setting cannot be set True. 353 354 @param browser: chrome.Chrome browser object. 355 @param autotest_ext: autotest extension object. 356 @param enable_managed_policy: If False then policy check is ignored for 357 managed user case and ARC++ is forced enabled. 358 @param wait_for_provisioning: True in case we have to wait for provisioning 359 to complete. 360 361 @raises: error.TestFail if opt in fails. 362 363 """ 364 365 logging.info(_OPT_IN_BEGIN) 366 367 # TODO(b/124391451): Enterprise tests have race, when ARC policy appears 368 # after profile prefs sync completed. That means they may appear as 369 # non-managed first, treated like this but finally turns as managed. This 370 # leads to test crash. Solution is to wait until prefs are synced or, if 371 # possible tune test accounts to wait sync is completed before starting the 372 # Chrome session. 373 374 # Enabling Play Store may affect this flag, capture it before. 375 tos_needed = get_arc_session_state(autotest_ext).tos_needed 376 377 if not enable_play_store(autotest_ext, True, enable_managed_policy): 378 return 379 380 if not wait_for_provisioning: 381 logging.info(_OPT_IN_SKIP) 382 return 383 384 if tos_needed: 385 extension_main_page = find_opt_in_extension_page(browser) 386 opt_in_and_wait_for_completion(extension_main_page) 387 else: 388 _wait_arc_provisioned(autotest_ext) 389 390 logging.info(_OPT_IN_FINISH) 391