• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import mox
7import time
8import unittest
9
10import common
11from autotest_lib.client.common_lib import error
12from autotest_lib.server.cros import autoupdater
13
14
15class _StubUpdateError(autoupdater._AttributedUpdateError):
16    STUB_MESSAGE = 'Stub message'
17    STUB_PATTERN = 'Stub pattern matched'
18    _SUMMARY = 'Stub summary'
19    _CLASSIFIERS = [
20        (STUB_MESSAGE, STUB_MESSAGE),
21        ('Stub .*', STUB_PATTERN),
22    ]
23
24    def __init__(self, info, msg):
25        super(_StubUpdateError, self).__init__(
26            'Stub %s' % info, msg)
27
28
29class TestErrorClassifications(unittest.TestCase):
30    """Test error message handling in `_AttributedUpdateError`."""
31
32    def test_exception_message(self):
33        """Test that the exception string includes its arguments."""
34        info = 'info marker'
35        msg = 'an error message'
36        stub = _StubUpdateError(info, msg)
37        self.assertIn(info, str(stub))
38        self.assertIn(msg, str(stub))
39
40    def test_classifier_message(self):
41        """Test that the exception classifier can match a simple string."""
42        info = 'info marker'
43        stub = _StubUpdateError(info, _StubUpdateError.STUB_MESSAGE)
44        self.assertNotIn(info, stub.failure_summary)
45        self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
46        self.assertIn(_StubUpdateError.STUB_MESSAGE, stub.failure_summary)
47
48    def test_classifier_pattern(self):
49        """Test that the exception classifier can match a regex."""
50        info = 'info marker'
51        stub = _StubUpdateError(info, 'Stub this is a test')
52        self.assertNotIn(info, stub.failure_summary)
53        self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
54        self.assertIn(_StubUpdateError.STUB_PATTERN, stub.failure_summary)
55
56    def test_classifier_unmatched(self):
57        """Test exception summary when no classifier matches."""
58        info = 'info marker'
59        stub = _StubUpdateError(info, 'This matches no pattern')
60        self.assertNotIn(info, stub.failure_summary)
61        self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
62
63    def test_host_update_error(self):
64        """Sanity test the `HostUpdateError` classifier."""
65        exception = autoupdater.HostUpdateError(
66                'chromeos6-row3-rack3-host19', 'Fake message')
67        self.assertTrue(isinstance(exception.failure_summary, str))
68
69    def test_dev_server_error(self):
70        """Sanity test the `DevServerError` classifier."""
71        exception = autoupdater.DevServerError(
72                'chromeos4-devserver7.cros', 'Fake message')
73        self.assertTrue(isinstance(exception.failure_summary, str))
74
75    def test_image_install_error(self):
76        """Sanity test the `ImageInstallError` classifier."""
77        exception = autoupdater.ImageInstallError(
78                'chromeos6-row3-rack3-host19',
79                'chromeos4-devserver7.cros',
80                'Fake message')
81        self.assertTrue(isinstance(exception.failure_summary, str))
82
83    def test_new_build_update_error(self):
84        """Sanity test the `NewBuildUpdateError` classifier."""
85        exception = autoupdater.NewBuildUpdateError(
86                'R68-10621.0.0', 'Fake message')
87        self.assertTrue(isinstance(exception.failure_summary, str))
88
89
90class TestAutoUpdater(mox.MoxTestBase):
91    """Test autoupdater module."""
92
93    def testParseBuildFromUpdateUrlwithUpdate(self):
94        """Test that we properly parse the build from an update_url."""
95        update_url = ('http://172.22.50.205:8082/update/lumpy-release/'
96                      'R27-3837.0.0')
97        expected_value = 'lumpy-release/R27-3837.0.0'
98        self.assertEqual(autoupdater.url_to_image_name(update_url),
99                         expected_value)
100
101    def _host_run_for_update(self, cmd, exception=None,
102                             bad_update_status=False):
103        """Helper function for AU tests.
104
105        @param host: the test host
106        @param cmd: the command to be recorded
107        @param exception: the exception to be recorded, or None
108        """
109        if exception:
110            self.host.run(command=cmd).AndRaise(exception)
111        else:
112            result = self.mox.CreateMockAnything()
113            if bad_update_status:
114                # Pick randomly one unexpected status
115                result.stdout = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
116            else:
117                result.stdout = 'UPDATE_STATUS_IDLE'
118            result.status = 0
119            self.host.run(command=cmd).AndReturn(result)
120
121    def testTriggerUpdate(self):
122        """Tests that we correctly handle updater errors."""
123        update_url = 'http://server/test/url'
124        self.host = self.mox.CreateMockAnything()
125        self.mox.StubOutWithMock(self.host, 'run')
126        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
127                                 '_get_last_update_error')
128        self.host.hostname = 'test_host'
129        updater_control_bin = '/usr/bin/update_engine_client'
130        test_url = 'http://server/test/url'
131        expected_wait_cmd = ('%s -status | grep CURRENT_OP' %
132                             updater_control_bin)
133        expected_cmd = ('%s --check_for_update --omaha_url=%s' %
134                        (updater_control_bin, test_url))
135        self.mox.StubOutWithMock(time, "sleep")
136        UPDATE_ENGINE_RETRY_WAIT_TIME=5
137
138        # Generic SSH Error.
139        cmd_result_255 = self.mox.CreateMockAnything()
140        cmd_result_255.exit_status = 255
141
142        # Command Failed Error
143        cmd_result_1 = self.mox.CreateMockAnything()
144        cmd_result_1.exit_status = 1
145
146        # Error 37
147        cmd_result_37 = self.mox.CreateMockAnything()
148        cmd_result_37.exit_status = 37
149
150        updater = autoupdater.ChromiumOSUpdater(update_url, host=self.host)
151
152        # (SUCCESS) Expect one wait command and one status command.
153        self._host_run_for_update(expected_wait_cmd)
154        self._host_run_for_update(expected_cmd)
155
156        # (SUCCESS) Test with one retry to wait for update-engine.
157        self._host_run_for_update(expected_wait_cmd, exception=
158                error.AutoservRunError('non-zero status', cmd_result_1))
159        time.sleep(UPDATE_ENGINE_RETRY_WAIT_TIME)
160        self._host_run_for_update(expected_wait_cmd)
161        self._host_run_for_update(expected_cmd)
162
163        # (SUCCESS) One-time SSH timeout, then success on retry.
164        self._host_run_for_update(expected_wait_cmd)
165        self._host_run_for_update(expected_cmd, exception=
166                error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
167        self._host_run_for_update(expected_cmd)
168
169        # (SUCCESS) One-time ERROR 37, then success.
170        self._host_run_for_update(expected_wait_cmd)
171        self._host_run_for_update(expected_cmd, exception=
172                error.AutoservRunError('ERROR_CODE=37', cmd_result_37))
173        self._host_run_for_update(expected_cmd)
174
175        # (FAILURE) Bad status of update engine.
176        self._host_run_for_update(expected_wait_cmd)
177        self._host_run_for_update(expected_cmd, bad_update_status=True,
178                                  exception=error.InstallError(
179                                      'host is not in installable state'))
180
181        # (FAILURE) Two-time SSH timeout.
182        self._host_run_for_update(expected_wait_cmd)
183        self._host_run_for_update(expected_cmd, exception=
184                error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
185        self._host_run_for_update(expected_cmd, exception=
186                error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
187
188        # (FAILURE) SSH Permission Error
189        self._host_run_for_update(expected_wait_cmd)
190        self._host_run_for_update(expected_cmd, exception=
191                error.AutoservSshPermissionDeniedError('no permission',
192                                                       cmd_result_255))
193
194        # (FAILURE) Other ssh failure
195        self._host_run_for_update(expected_wait_cmd)
196        self._host_run_for_update(expected_cmd, exception=
197                error.AutoservSshPermissionDeniedError('no permission',
198                                                       cmd_result_255))
199        # (FAILURE) Other error
200        self._host_run_for_update(expected_wait_cmd)
201        self._host_run_for_update(expected_cmd, exception=
202                error.AutoservRunError("unknown error", cmd_result_1))
203
204        self.mox.ReplayAll()
205
206        # Expect success
207        updater.trigger_update()
208        updater.trigger_update()
209        updater.trigger_update()
210        updater.trigger_update()
211
212        # Expect errors as listed above
213        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
214        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
215        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
216        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
217        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
218
219        self.mox.VerifyAll()
220
221    def testUpdateStateful(self):
222        """Tests that we call the stateful update script with the correct args.
223        """
224        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
225        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
226                                 '_get_stateful_update_script')
227        update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
228                      'R28-4444.0.0-b2996')
229        static_update_url = ('http://172.22.50.205:8082/static/'
230                             'lumpy-chrome-perf/R28-4444.0.0-b2996')
231        update_script = '/usr/local/bin/stateful_update'
232
233        # Test with clobber=False.
234        autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
235                update_script)
236        autoupdater.ChromiumOSUpdater._run(
237                mox.And(
238                        mox.StrContains(update_script),
239                        mox.StrContains(static_update_url),
240                        mox.Not(mox.StrContains('--stateful_change=clean'))),
241                timeout=mox.IgnoreArg())
242
243        self.mox.ReplayAll()
244        updater = autoupdater.ChromiumOSUpdater(update_url)
245        updater.update_stateful(clobber=False)
246        self.mox.VerifyAll()
247
248        # Test with clobber=True.
249        self.mox.ResetAll()
250        autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
251                update_script)
252        autoupdater.ChromiumOSUpdater._run(
253                mox.And(
254                        mox.StrContains(update_script),
255                        mox.StrContains(static_update_url),
256                        mox.StrContains('--stateful_change=clean')),
257                timeout=mox.IgnoreArg())
258        self.mox.ReplayAll()
259        updater = autoupdater.ChromiumOSUpdater(update_url)
260        updater.update_stateful(clobber=True)
261        self.mox.VerifyAll()
262
263    def testGetRemoteScript(self):
264        """Test _get_remote_script() behaviors."""
265        update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
266                      'R28-4444.0.0-b2996')
267        script_name = 'fubar'
268        local_script = '/usr/local/bin/%s' % script_name
269        host = self.mox.CreateMockAnything()
270        updater = autoupdater.ChromiumOSUpdater(update_url, host=host)
271        host.path_exists(local_script).AndReturn(True)
272
273        self.mox.ReplayAll()
274        # Simple case:  file exists on DUT
275        self.assertEqual(updater._get_remote_script(script_name),
276                         local_script)
277        self.mox.VerifyAll()
278
279        self.mox.ResetAll()
280        fake_shell = '/bin/ash'
281        tmp_script = '/tmp/%s' % script_name
282        fake_result = self.mox.CreateMockAnything()
283        fake_result.stdout = ' %s\n' % fake_shell
284        host.path_exists(local_script).AndReturn(False)
285        host.run(mox.IgnoreArg(),
286                 ignore_status=True).AndReturn(fake_result)
287
288        self.mox.ReplayAll()
289        # Complicated case:  script not on DUT, so try to download it.
290        self.assertEqual(
291                updater._get_remote_script(script_name),
292                '%s %s' % (fake_shell, tmp_script))
293        self.mox.VerifyAll()
294
295    def testRollbackRootfs(self):
296        """Tests that we correctly rollback the rootfs when requested."""
297        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
298        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
299                                 '_verify_update_completed')
300        host = self.mox.CreateMockAnything()
301        update_url = 'http://server/test/url'
302        host.hostname = 'test_host'
303
304        can_rollback_cmd = ('/usr/bin/update_engine_client --can_rollback')
305        rollback_cmd = ('/usr/bin/update_engine_client --rollback '
306                        '--follow')
307
308        updater = autoupdater.ChromiumOSUpdater(update_url, host=host)
309
310        # Return an old build which shouldn't call can_rollback.
311        updater.host.get_release_version().AndReturn('1234.0.0')
312        autoupdater.ChromiumOSUpdater._run(rollback_cmd)
313        autoupdater.ChromiumOSUpdater._verify_update_completed()
314
315        self.mox.ReplayAll()
316        updater.rollback_rootfs(powerwash=True)
317        self.mox.VerifyAll()
318
319        self.mox.ResetAll()
320        cmd_result_1 = self.mox.CreateMockAnything()
321        cmd_result_1.exit_status = 1
322
323        # Rollback but can_rollback says we can't -- return an error.
324        updater.host.get_release_version().AndReturn('5775.0.0')
325        autoupdater.ChromiumOSUpdater._run(can_rollback_cmd).AndRaise(
326                error.AutoservRunError('can_rollback failed', cmd_result_1))
327        self.mox.ReplayAll()
328        self.assertRaises(autoupdater.RootFSUpdateError,
329                          updater.rollback_rootfs, True)
330        self.mox.VerifyAll()
331
332        self.mox.ResetAll()
333        # Rollback >= version blacklisted.
334        updater.host.get_release_version().AndReturn('5775.0.0')
335        autoupdater.ChromiumOSUpdater._run(can_rollback_cmd)
336        autoupdater.ChromiumOSUpdater._run(rollback_cmd)
337        autoupdater.ChromiumOSUpdater._verify_update_completed()
338        self.mox.ReplayAll()
339        updater.rollback_rootfs(powerwash=True)
340        self.mox.VerifyAll()
341
342
343if __name__ == '__main__':
344  unittest.main()
345