• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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