1# Copyright (c) 2012 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 common, logging, os, time
6
7from autotest_lib.client.bin import utils
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.cros import constants
10
11# Log messages used to signal when we're restarting UI. Used to detect
12# crashes by cros_ui_test.UITest.
13UI_RESTART_ATTEMPT_MSG = 'cros_ui.py: Attempting StopSession...'
14UI_RESTART_COMPLETE_MSG = 'cros_ui.py: StopSession complete.'
15RESTART_UI_TIMEOUT = 90  # longer because we may be crash dumping now.
16
17
18def get_chrome_session_ident(host=None):
19    """Return an identifier that changes whenever Chrome restarts.
20
21    This function returns a value that is unique to the most
22    recently started Chrome process; the returned value changes
23    each time Chrome restarts and displays the login screen.  The
24    change in the value can be used to detect a successful Chrome
25    restart.
26
27    Note that uniqueness is only guaranteed until the host reboots.
28
29    Args:
30        host:  If not None, a host object on which to test Chrome
31            state, rather than running commands on the local host.
32
33    """
34    if host:
35        return host.run(constants.LOGIN_PROMPT_STATUS_COMMAND).stdout
36    return utils.run(constants.LOGIN_PROMPT_STATUS_COMMAND).stdout
37
38
39def wait_for_chrome_ready(old_session, host=None,
40                          timeout=RESTART_UI_TIMEOUT):
41    """Wait until a new Chrome login prompt is on screen and ready.
42
43    The standard formula to check whether the prompt has appeared yet
44    is with a pattern like the following:
45
46       session = get_chrome_session_ident()
47       logout()
48       wait_for_chrome_ready(session)
49
50    Args:
51        old_session:  identifier for the login prompt prior to
52            restarting Chrome.
53        host:  If not None, a host object on which to test Chrome
54            state, rather than running commands on the local host.
55        timeout: float number of seconds to wait
56
57    Raises:
58        TimeoutError: Login prompt didn't get up before timeout
59
60    """
61    utils.poll_for_condition(
62        condition=lambda: old_session != get_chrome_session_ident(host),
63        exception=utils.TimeoutError('Timed out waiting for login prompt'),
64        timeout=timeout, sleep_interval=1.0)
65
66
67def stop_and_wait_for_chrome_to_exit(timeout_secs=40):
68    """Stops the UI and waits for chrome to exit.
69
70    Stops the UI and waits for all chrome processes to exit or until
71    timeout_secs is reached.
72
73    Args:
74        timeout_secs: float number of seconds to wait.
75
76    Returns:
77        True upon successfully stopping the UI and all chrome processes exiting.
78        False otherwise.
79    """
80    status = stop(allow_fail=True)
81    if status:
82        logging.error('stop ui returned non-zero status: %s', status)
83        return False
84    start_time = time.time()
85    while time.time() - start_time < timeout_secs:
86        status = utils.system('pgrep chrome', ignore_status=True)
87        if status == 1: return True
88        time.sleep(1)
89    logging.error('stop ui failed to stop chrome within %s seconds',
90                  timeout_secs)
91    return False
92
93
94def stop(allow_fail=False):
95    return utils.system("stop ui", ignore_status=allow_fail)
96
97
98def start(allow_fail=False, wait_for_login_prompt=True):
99    """Start the login manager and wait for the prompt to show up."""
100    session = get_chrome_session_ident()
101    result = utils.system("start ui", ignore_status=allow_fail)
102    # If allow_fail is set, the caller might be calling us when the UI job
103    # is already running. In that case, the above command fails.
104    if result == 0 and wait_for_login_prompt:
105        wait_for_chrome_ready(session)
106    return result
107
108
109def restart(report_stop_failure=False):
110    """Restart the session manager.
111
112    - If the user is logged in, the session will be terminated.
113    - If the UI is currently down, just go ahead and bring it up unless the
114      caller has requested that a failure to stop be reported.
115    - To ensure all processes are up and ready, this function will wait
116      for the login prompt to show up and be marked as visible.
117
118    @param report_stop_failure: False by default, set to True if you care about
119                                the UI being up at the time of call and
120                                successfully torn down by this call.
121    """
122    session = get_chrome_session_ident()
123
124    # Log what we're about to do to /var/log/messages. Used to log crashes later
125    # in cleanup by cros_ui_test.UITest.
126    utils.system('logger "%s"' % UI_RESTART_ATTEMPT_MSG)
127
128    try:
129        if stop(allow_fail=not report_stop_failure) != 0:
130            raise error.TestError('Could not stop session')
131        start(wait_for_login_prompt=False)
132        # Wait for login prompt to appear to indicate that all processes are
133        # up and running again.
134        wait_for_chrome_ready(session)
135    finally:
136        utils.system('logger "%s"' % UI_RESTART_COMPLETE_MSG)
137
138
139def nuke():
140    """Nuke the login manager, waiting for it to restart."""
141    restart(lambda: utils.nuke_process_by_name(constants.SESSION_MANAGER))
142
143
144def is_up():
145    """Return True if the UI is up, False if not."""
146    return 'start/running' in utils.system_output('initctl status ui')
147
148
149def clear_respawn_state():
150    """Removes bookkeeping related to respawning crashed UI."""
151    for filename in [constants.UI_RESPAWN_TIMESTAMPS_FILE,
152                     constants.UI_TOO_CRASHY_TIMESTAMPS_FILE]:
153        try:
154            os.unlink(filename)
155        except OSError:
156            pass  # It's already gone.
157