• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 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 logging
6
7from autotest_lib.server import autotest
8from autotest_lib.server.cros import tradefed_constants as constants
9
10
11class ChromeLogin(object):
12    """Context manager to handle Chrome login state."""
13
14    def need_reboot(self):
15        """Marks state as "dirty" - reboot needed during/after test."""
16        logging.info('Will reboot DUT when Chrome stops.')
17        self._need_reboot = True
18
19    def need_restart(self):
20        """Marks state as "dirty" - restart needed after test."""
21        self._need_restart = True
22
23    def __init__(self, host, kwargs):
24        """Initializes the _ChromeLogin object.
25
26        @param reboot: indicate if a reboot before destruction is required.
27        @param restart: indicate if a restart before destruction is required.
28        @param board: optional parameter to extend timeout for login for slow
29                      DUTs. Used in particular for virtual machines.
30        """
31        self._host = host
32        self._cts_helper_kwargs = kwargs
33        # We will override reboot/restart options to some degree. Keep track
34        # of them in a local copy.
35        self._need_reboot = False
36        if kwargs.get('reboot'):
37            self.need_reboot()
38        self._need_restart = False
39        if kwargs.get('restart'):
40            self.need_restart()
41        self._timeout = constants.LOGIN_DEFAULT_TIMEOUT
42        board = kwargs.get('board')
43        if board in constants.LOGIN_BOARD_TIMEOUT:
44            self._timeout = constants.LOGIN_BOARD_TIMEOUT[board]
45        # DUT power off -> on cycle will still adhere DUT's reboot preference.
46        self._hard_reboot_on_failure = False
47        if kwargs.get('hard_reboot_on_failure') and self._need_reboot:
48            self._hard_reboot_on_failure = True
49
50    def _cmd_builder(self, verbose=False):
51        """Gets remote command to start browser with ARC enabled."""
52        # If autotest is not installed on the host, as with moblab at times,
53        # getting the autodir will raise an exception.
54        cmd = autotest.Autotest.get_installed_autodir(self._host)
55        cmd += '/bin/autologin.py --arc'
56        if self._cts_helper_kwargs.get('dont_override_profile'):
57            logging.info('Using --dont_override_profile to start Chrome.')
58            cmd += ' --dont_override_profile'
59        else:
60            logging.info('Not using --dont_override_profile to start Chrome.')
61        if not verbose:
62            cmd += ' > /dev/null 2>&1'
63        return cmd
64
65    def login(self, timeout=None, raise_exception=False, verbose=False):
66        """Logs into Chrome."""
67        if not timeout:
68            timeout = self._timeout
69        try:
70            # We used to call cheets_StartAndroid, but it is a little faster to
71            # call a script on the DUT. This also saves CPU time on the server.
72            self._host.run(
73                self._cmd_builder(),
74                ignore_status=False,
75                verbose=verbose,
76                timeout=timeout)
77            return True
78        except autotest.AutodirNotFoundError:
79            # Autotest is not installed (can happen on moblab after image
80            # install). Run dummy_Pass to foce autotest install, before trying
81            # to login again.
82            logging.warning(
83                'Autotest not installed, forcing install using dummy_Pass...')
84            try:
85                autotest.Autotest(self._host).run_timed_test(
86                    'dummy_Pass',
87                    timeout=2 * timeout,
88                    check_client_result=True,
89                    **self._cts_helper_kwargs)
90                self._host.run(
91                    self._cmd_builder(),
92                    ignore_status=False,
93                    verbose=verbose,
94                    timeout=timeout)
95                return True
96            except:
97                # We were unable to start the browser/Android. Maybe we can
98                # salvage the DUT by rebooting. This can hide some failures.
99                self.reboot()
100                if raise_exception:
101                    raise
102        except:
103            # We were unable to start the browser/Android. Maybe we can
104            # salvage the DUT by rebooting. This can hide some failures.
105            self.reboot()
106            if raise_exception:
107                raise
108        return False
109
110    def enter(self):
111        """Logs into Chrome with retry."""
112        timeout = self._timeout
113        logging.info('Ensure Android is running (timeout=%d)...', timeout)
114        if not self.login(timeout=timeout):
115            timeout *= 2
116            # The DUT reboots after unsuccessful login, try with more time.
117            logging.info('Retrying failed login (timeout=%d)...', timeout)
118            self.login(timeout=timeout, raise_exception=True, verbose=True)
119        return self
120
121    def __enter__(self):
122        """Logs into Chrome with retry."""
123        return self.enter()
124
125    def exit(self, exc_type=None, exc_value=None, traceback=None):
126        """On exit restart the browser or reboot the machine.
127
128        @param exc_type: Exception type if an exception is raised from the
129                         with-block.
130        @param exc_value: Exception instance if an exception is raised from
131                          the with-block.
132        @param traceback: Stack trace info if an exception is raised from
133                          the with-block.
134        @return None, indicating not to ignore an exception from the with-block
135                if raised.
136        """
137        if not self._need_reboot:
138            logging.info('Skipping reboot, restarting browser.')
139            try:
140                self.restart()
141            except:
142                logging.error('Restarting browser has failed.')
143                self.need_reboot()
144        if self._need_reboot:
145            self.reboot(exc_type, exc_value, traceback)
146
147    def __exit__(self, exc_type, exc_value, traceback):
148        """On exit restart the browser or reboot the machine.
149
150        @param exc_type: Exception type if an exception is raised from the
151                         with-block.
152        @param exc_value: Exception instance if an exception is raised from
153                          the with-block.
154        @param traceback: Stack trace info if an exception is raised from
155                          the with-block.
156        @return None, indicating not to ignore an exception from the with-block
157                if raised.
158        """
159        self.exit(exc_type, exc_value, traceback)
160
161    def restart(self):
162        """Restart Chrome browser."""
163        # We clean up /tmp (which is memory backed) from crashes and
164        # other files. A reboot would have cleaned /tmp as well.
165        # TODO(ihf): Remove "start ui" which is a nicety to non-ARC tests (i.e.
166        # now we wait on login screen, but login() above will 'stop ui' again
167        # before launching Chrome with ARC enabled).
168        script = 'stop ui'
169        script += '&& find /tmp/ -mindepth 1 -delete '
170        script += '&& start ui'
171        self._host.run(script, ignore_status=False, verbose=False, timeout=120)
172
173    def reboot(self, exc_type=None, exc_value=None, traceback=None):
174        """Reboot the machine.
175
176        @param exc_type: Exception type if an exception is raised from the
177                         with-block.
178        @param exc_value: Exception instance if an exception is raised from
179                          the with-block.
180        @param traceback: Stack trace info if an exception is raised from
181                          the with-block.
182        @return None, indicating not to ignore an exception from the with-block
183                if raised.
184        """
185        logging.info('Rebooting...')
186        try:
187            if self._hard_reboot_on_failure and self._host.servo:
188                logging.info('Powering OFF the DUT: %s', self._host)
189                self._host.servo.get_power_state_controller().power_off()
190                logging.info('Powering ON the DUT: %s', self._host)
191                self._host.servo.get_power_state_controller().power_on()
192                self._hard_reboot_on_failure = False
193            else:
194                self._host.reboot()
195                self._need_reboot = False
196        except Exception:
197            if exc_type is None:
198                raise
199            # If an exception is raise from the with-block, just record the
200            # exception for the rebooting to avoid ignoring the original
201            # exception.
202            logging.exception('Rebooting failed.')
203