1# Copyright 2017 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
6import random
7import time
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.common_lib import utils
11from autotest_lib.client.common_lib.cros import kernel_utils
12from autotest_lib.client.common_lib.cros import tpm_utils
13from autotest_lib.server.cros.update_engine import update_engine_test
14
15class autoupdate_ForcedOOBEUpdate(update_engine_test.UpdateEngineTest):
16    """Runs a forced autoupdate during OOBE."""
17    version = 1
18
19
20    def cleanup(self):
21        # Get the last two update_engine logs: before and after reboot.
22        self._save_extra_update_engine_logs(number_of_logs=2)
23
24        self._clear_custom_lsb_release()
25
26        self._set_update_over_cellular_setting(False)
27
28        # Cancel any update still in progress.
29        if not self._is_update_engine_idle():
30            logging.debug('Canceling the in-progress update.')
31            self._restart_update_engine()
32        super(autoupdate_ForcedOOBEUpdate, self).cleanup()
33
34
35    def _wait_for_reboot_after_update(self, timeout_minutes=15):
36        """
37        Waits for the OOBE update to finish and autoreboot.
38
39        The update goes through the following statuses: DOWNLOADING to
40        FINALIZING to NEED_REBOOT. It then automatically reboots back to the
41        same screen of OOBE. Detecting the reboot is done by:
42
43        1) Checking the number of logs in /var/log/update_engine/ increased.
44        2) Checking that the two recent statuses were FINALIZING and IDLE.
45
46        @param timeout_minutes: How long to wait for the update to finish.
47                                See crbug/1073855 for context on this default.
48
49        """
50        timeout = time.time() + 60 * timeout_minutes
51        last_status = None
52        logs_before = len(self._get_update_engine_logs())
53
54        while True:
55            # Use timeout so if called during reboot we fail early and retry.
56            status = self._get_update_engine_status(timeout=10,
57                                                    ignore_timeout=True)
58
59            # Check that the status is not reporting an error.
60            if status is not None:
61                if self._is_checking_for_update(status):
62                    continue
63                if self._is_update_engine_reporting_error(status):
64                    err_str = self._get_last_error_string()
65                    raise error.TestFail('Update status reported error '
66                                         'during OOBE update: %s' % err_str)
67                # if status is IDLE we need to figure out if an error occurred
68                # or the DUT autorebooted.
69                elif self._is_update_engine_idle(status):
70                    if self._is_update_finished_downloading(last_status):
71                        if len(self._get_update_engine_logs()) > logs_before:
72                            return
73                    err_str = self._get_last_error_string()
74                    raise error.TestFail('Update status was IDLE during '
75                                         'update: %s' % err_str)
76                last_status = status
77
78            time.sleep(1)
79            if time.time() > timeout:
80                raise error.TestFail(
81                    'OOBE update did not finish in %d minutes. Last status: %s,'
82                    ' Last Progress: %s' % (timeout_minutes,
83                    status[self._CURRENT_OP], status[self._PROGRESS]))
84
85
86    def _wait_for_oobe_update_to_complete(self):
87        """Wait for the update that started to complete."""
88        self._wait_for_reboot_after_update()
89        def found_post_reboot_event():
90            """
91            Now that the device is rebooted, we have to make sure update_engine
92            is up and running and post reboot update check has been performed.
93
94            """
95            self._get_update_engine_status(timeout=10, ignore_timeout=False)
96            return self._check_update_engine_log_for_entry(
97                  'Omaha request response:')
98        utils.poll_for_condition(found_post_reboot_event, timeout=60,
99                                 desc='post-reboot event to fire after reboot')
100
101
102    def run_once(self, full_payload=True, cellular=False,
103                 interrupt=None, job_repo_url=None, moblab=False):
104        """
105        Runs a forced autoupdate during ChromeOS OOBE.
106
107        @param full_payload: True for a full payload. False for delta.
108        @param cellular: True to do the update over a cellualar connection.
109                         Requires that the DUT have a sim card slot.
110        @param interrupt: Type of interrupt to try. See _SUPPORTED_INTERRUPTS.
111        @param job_repo_url: Used for debugging locally. This is used to figure
112                             out the current build and the devserver to use.
113                             The test will read this from a host argument
114                             when run in the lab.
115        @param moblab: True if we are running on moblab.
116
117        """
118        if interrupt and interrupt not in self._SUPPORTED_INTERRUPTS:
119            raise error.TestFail('Unknown interrupt type: %s' % interrupt)
120        tpm_utils.ClearTPMOwnerRequest(self._host)
121
122        # This test can be used with Nebraska (cellular tests) or a devserver
123        # (non-cellular) tests. Each passes a different value to the client:
124        # An update_url for a devserver or a payload_url for Nebraska.
125        payload_url = None
126        update_url = None
127        if cellular:
128            self._set_update_over_cellular_setting(True)
129            payload_url = self.get_payload_url_on_public_bucket(
130                job_repo_url, full_payload=full_payload)
131        else:
132            update_url = self.get_update_url_for_test(
133                job_repo_url, full_payload=full_payload)
134        before_version = self._host.get_release_version()
135
136        # Clear any previously started updates.
137        self._remove_update_engine_pref(self._UPDATE_CHECK_RESPONSE_HASH)
138        self._restart_update_engine(ignore_status=True)
139
140        progress = None
141        if interrupt is not None:
142            # Choose a random downloaded progress to interrupt the update.
143            # Moblab may have higher download speeds and take longer to return
144            # from the client test, so use a reduced progress range there.
145            progress_limit = 0.3 if moblab else 0.6
146            progress = random.uniform(0.1, progress_limit)
147            logging.info('Progress when we will interrupt: %f', progress)
148
149        active, inactive = kernel_utils.get_kernel_state(self._host)
150        # Call client test to start the forced OOBE update.
151        self._run_client_test_and_check_result(
152            'autoupdate_StartOOBEUpdate', update_url=update_url,
153            payload_url=payload_url, full_payload=full_payload,
154            cellular=cellular, critical_update=True,
155            interrupt_network=interrupt == self._NETWORK_INTERRUPT,
156            interrupt_progress=progress)
157
158        if interrupt in [self._REBOOT_INTERRUPT, self._SUSPEND_INTERRUPT]:
159            logging.info('Waiting to interrupt update.')
160            self._wait_for_progress(progress)
161            logging.info('The update will be interrupted now...')
162            completed = self._get_update_progress()
163
164            self._take_screenshot(self._BEFORE_INTERRUPT_FILENAME)
165            if interrupt == self._REBOOT_INTERRUPT:
166                self._host.reboot()
167            elif interrupt == self._SUSPEND_INTERRUPT:
168                self._suspend_then_resume()
169            self._take_screenshot(self._AFTER_INTERRUPT_FILENAME)
170
171            if self._is_update_engine_idle():
172                raise error.TestFail('The update was IDLE after interrupt.')
173            if not self._update_continued_where_it_left_off(
174                completed, reboot_interrupt=interrupt is 'reboot'):
175                raise error.TestFail('The update did not continue where it '
176                                     'left off after interruption.')
177
178            # Remove screenshots since interrupt test succeeded.
179            self._remove_screenshots()
180
181        # Create lsb-release with no_update=True to get post-reboot event.
182        lsb_url = payload_url if cellular else update_url
183        self._create_custom_lsb_release(lsb_url, no_update=True)
184
185        self._wait_for_oobe_update_to_complete()
186
187        # Verify the update was successful by checking hostlog and kernel.
188        rootfs_hostlog, reboot_hostlog = self._create_hostlog_files()
189        self.verify_update_events(self._CUSTOM_LSB_VERSION, rootfs_hostlog)
190        self.verify_update_events(self._CUSTOM_LSB_VERSION, reboot_hostlog,
191                                  self._CUSTOM_LSB_VERSION)
192        kernel_utils.verify_boot_expectations(inactive, host=self._host)
193        logging.info('Successfully force updated from %s to %s.',
194                     before_version, self._host.get_release_version())
195