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
6import shutil
7import time
8import urlparse
9
10from autotest_lib.client.bin import test, utils
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.cros.update_engine import dlc_util
13from autotest_lib.client.cros.update_engine import update_engine_util
14
15class UpdateEngineTest(test.test, update_engine_util.UpdateEngineUtil):
16    """Base class for update engine client tests."""
17
18    _NETWORK_INTERFACES = ['eth0', 'eth1', 'eth2']
19
20
21    def initialize(self):
22        """Initialize for this test."""
23        self._set_util_functions(utils.run, shutil.copy)
24        self._internet_was_disabled = False
25
26        # Utilities for DLC management
27        self._dlc_util = dlc_util.DLCUtil(self._run)
28
29
30    def cleanup(self):
31        """Cleanup for this test."""
32        # Make sure to grab the update engine log for every test run.
33        shutil.copy(self._UPDATE_ENGINE_LOG, self.resultsdir)
34
35        # Ensure ethernet adapters are back on
36        self._enable_internet()
37
38
39    def _enable_internet(self, ping_server='google.com'):
40        """
41        Re-enables the internet connection.
42
43        @param ping_server: The server to ping to check we are online.
44
45        """
46        if not self._internet_was_disabled:
47            return
48
49        self._internet_was_disabled = False
50        logging.debug('Before reconnect: %s', utils.run(['ifconfig']))
51        for eth in self._NETWORK_INTERFACES:
52            utils.run(['ifconfig', eth, 'up'], ignore_status=True)
53        utils.start_service('recover_duts', ignore_status=True)
54
55        # Print ifconfig to help debug DUTs that stay offline.
56        logging.debug('After reconnect: %s', utils.run(['ifconfig']))
57
58        # We can't return right after reconnecting the network or the server
59        # test may not receive the message. So we wait a bit longer for the
60        # DUT to be reconnected.
61        utils.poll_for_condition(lambda: utils.ping(ping_server,
62                                                    tries=3, timeout=10) == 0,
63                                 timeout=120,
64                                 sleep_interval=1,
65                                 exception=error.TestFail(
66                                     'Ping failed after reconnecting network'))
67
68
69    def _disable_internet(self, ping_server='google.com'):
70        """Disable the internet connection"""
71        self._internet_was_disabled = True
72        try:
73            logging.debug('Before disconnect: %s', utils.run(['ifconfig']))
74            # DUTs in the lab have a service called recover_duts that is used to
75            # check that the DUT is online and if it is not it will bring it
76            # back online. We will need to stop this service for the length
77            # of this test.
78            utils.stop_service('recover_duts', ignore_status=True)
79            for eth in self._NETWORK_INTERFACES:
80                result = utils.run(['ifconfig', eth, 'down'],
81                                   ignore_status=True)
82                logging.debug(result)
83
84            # Print ifconfig to help debug DUTs that stay online.
85            logging.debug('After disconnect: %s', utils.run('ifconfig'))
86
87            # Make sure we are offline
88            utils.poll_for_condition(lambda: utils.ping(ping_server,
89                                                        deadline=5,
90                                                        timeout=5) != 0,
91                                     timeout=60,
92                                     sleep_interval=1,
93                                     desc='Ping failure while offline.')
94        except (error.CmdError, utils.TimeoutError):
95            logging.exception('Failed to disconnect one or more interfaces.')
96            logging.debug(utils.run(['ifconfig'], ignore_status=True))
97            raise error.TestFail('Disabling the internet connection failed.')
98
99
100    def _disconnect_reconnect_network_test(self, update_url,
101                                          time_without_network=120,
102                                          accepted_movement=0.015):
103        """
104        Disconnects the network for a period of time, verifies that the update
105        pauses, reconnects the network, and ensures that the update picks up
106        from where it left off. This will be used as a part of
107        autoupdate_ForcedOOBEUpdate.interrupt and autoupdate_Interruptions.
108
109        @param update_url: The update url used by the test. We will ping it to
110                           check whether we are online/offline.
111        @param time_without_network: Duration of the network disconnection in
112                                     seconds.
113        @param accepted_movement: Acceptable movement of update_engine
114                                  progress after the network is disabled.
115                                  Sometimes when network is disabled
116                                  update_engine progress will move a little,
117                                  which can cause false positives.
118
119        """
120        logging.info('Starting network interruption check.')
121        if self._is_update_finished_downloading():
122            raise error.TestFail('The update has already finished before we '
123                                 'can disconnect network.')
124        self._update_server = urlparse.urlparse(update_url).hostname
125        self._disable_internet()
126
127        # Check that we are offline.
128        result = utils.ping(self._update_server, deadline=5, timeout=5)
129        if result != 2:
130            raise error.TestFail('Ping succeeded even though we were offline.')
131
132        # We are seeing update_engine progress move a very tiny amount
133        # after disconnecting network so wait for it to stop moving.
134        utils.poll_for_condition(lambda: self._has_progress_stopped,
135                                 desc='Waiting for update progress to stop.')
136
137        # Get the update progress as the network is down
138        progress_before = float(self._get_update_engine_status()[
139            self._PROGRESS])
140
141        seconds = 1
142        while seconds < time_without_network:
143            logging.info(self._get_update_engine_status())
144            time.sleep(1)
145            seconds += 1
146
147        progress_after = float(self._get_update_engine_status()[
148            self._PROGRESS])
149
150        if progress_before != progress_after:
151            if progress_before < progress_after:
152                if progress_after - progress_before > accepted_movement:
153                    raise error.TestFail('The update continued while the '
154                                         'network was supposedly disabled. '
155                                         'Before: %f, After: %f' % (
156                                         progress_before, progress_after))
157                else:
158                    logging.warning('The update progress moved slightly while '
159                                    'network was off.')
160            elif self._is_update_finished_downloading():
161                raise error.TestFail('The update finished while the network '
162                                     'was disabled. Before: %f, After: %f' %
163                                     (progress_before, progress_after))
164            else:
165                raise error.TestFail('The update appears to have restarted. '
166                                     'Before: %f, After: %f' % (progress_before,
167                                                                progress_after))
168
169        self._enable_internet()
170