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