blob: 6df44f2316f5b77ee525553f2f374321c3cd7b29 [file] [log] [blame]
Mike Frysingerd03e6b52019-08-03 12:49:01 -04001#!/usr/bin/python2
Scott Zawalskieadbf702013-03-14 09:23:06 -04002# 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
Congbin Guo20ef0ce2019-05-15 12:08:13 -07006import mock
Dan Shib95bb862013-03-22 16:29:28 -07007import mox
Richard Barnette5adb6d42018-06-28 15:52:32 -07008import time
Scott Zawalskieadbf702013-03-14 09:23:06 -04009import unittest
10
11import common
Don Garrettdf8aef72013-12-16 11:12:41 -080012from autotest_lib.client.common_lib import error
Richard Barnette5adb6d42018-06-28 15:52:32 -070013from autotest_lib.server.cros import autoupdater
14
15
16class _StubUpdateError(autoupdater._AttributedUpdateError):
17 STUB_MESSAGE = 'Stub message'
18 STUB_PATTERN = 'Stub pattern matched'
19 _SUMMARY = 'Stub summary'
20 _CLASSIFIERS = [
21 (STUB_MESSAGE, STUB_MESSAGE),
22 ('Stub .*', STUB_PATTERN),
23 ]
24
25 def __init__(self, info, msg):
26 super(_StubUpdateError, self).__init__(
27 'Stub %s' % info, msg)
28
29
30class TestErrorClassifications(unittest.TestCase):
31 """Test error message handling in `_AttributedUpdateError`."""
32
33 def test_exception_message(self):
34 """Test that the exception string includes its arguments."""
35 info = 'info marker'
36 msg = 'an error message'
37 stub = _StubUpdateError(info, msg)
38 self.assertIn(info, str(stub))
39 self.assertIn(msg, str(stub))
40
41 def test_classifier_message(self):
42 """Test that the exception classifier can match a simple string."""
43 info = 'info marker'
44 stub = _StubUpdateError(info, _StubUpdateError.STUB_MESSAGE)
45 self.assertNotIn(info, stub.failure_summary)
46 self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
47 self.assertIn(_StubUpdateError.STUB_MESSAGE, stub.failure_summary)
48
49 def test_classifier_pattern(self):
50 """Test that the exception classifier can match a regex."""
51 info = 'info marker'
52 stub = _StubUpdateError(info, 'Stub this is a test')
53 self.assertNotIn(info, stub.failure_summary)
54 self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
55 self.assertIn(_StubUpdateError.STUB_PATTERN, stub.failure_summary)
56
57 def test_classifier_unmatched(self):
58 """Test exception summary when no classifier matches."""
59 info = 'info marker'
60 stub = _StubUpdateError(info, 'This matches no pattern')
61 self.assertNotIn(info, stub.failure_summary)
62 self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
63
64 def test_host_update_error(self):
65 """Sanity test the `HostUpdateError` classifier."""
66 exception = autoupdater.HostUpdateError(
67 'chromeos6-row3-rack3-host19', 'Fake message')
68 self.assertTrue(isinstance(exception.failure_summary, str))
69
70 def test_dev_server_error(self):
71 """Sanity test the `DevServerError` classifier."""
72 exception = autoupdater.DevServerError(
73 'chromeos4-devserver7.cros', 'Fake message')
74 self.assertTrue(isinstance(exception.failure_summary, str))
75
76 def test_image_install_error(self):
77 """Sanity test the `ImageInstallError` classifier."""
78 exception = autoupdater.ImageInstallError(
79 'chromeos6-row3-rack3-host19',
80 'chromeos4-devserver7.cros',
81 'Fake message')
82 self.assertTrue(isinstance(exception.failure_summary, str))
83
84 def test_new_build_update_error(self):
85 """Sanity test the `NewBuildUpdateError` classifier."""
86 exception = autoupdater.NewBuildUpdateError(
87 'R68-10621.0.0', 'Fake message')
88 self.assertTrue(isinstance(exception.failure_summary, str))
89
Scott Zawalskieadbf702013-03-14 09:23:06 -040090
Dan Shib95bb862013-03-22 16:29:28 -070091class TestAutoUpdater(mox.MoxTestBase):
Scott Zawalskieadbf702013-03-14 09:23:06 -040092 """Test autoupdater module."""
93
Scott Zawalskieadbf702013-03-14 09:23:06 -040094 def testParseBuildFromUpdateUrlwithUpdate(self):
95 """Test that we properly parse the build from an update_url."""
96 update_url = ('http://172.22.50.205:8082/update/lumpy-release/'
97 'R27-3837.0.0')
98 expected_value = 'lumpy-release/R27-3837.0.0'
99 self.assertEqual(autoupdater.url_to_image_name(update_url),
100 expected_value)
101
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800102 def _host_run_for_update(self, cmd, exception=None,
103 bad_update_status=False):
104 """Helper function for AU tests.
105
106 @param host: the test host
107 @param cmd: the command to be recorded
108 @param exception: the exception to be recorded, or None
109 """
110 if exception:
111 self.host.run(command=cmd).AndRaise(exception)
112 else:
113 result = self.mox.CreateMockAnything()
114 if bad_update_status:
115 # Pick randomly one unexpected status
116 result.stdout = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
117 else:
118 result.stdout = 'UPDATE_STATUS_IDLE'
119 result.status = 0
120 self.host.run(command=cmd).AndReturn(result)
121
Don Garrettdf8aef72013-12-16 11:12:41 -0800122 def testTriggerUpdate(self):
123 """Tests that we correctly handle updater errors."""
Don Garrettdf8aef72013-12-16 11:12:41 -0800124 update_url = 'http://server/test/url'
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800125 self.host = self.mox.CreateMockAnything()
126 self.mox.StubOutWithMock(self.host, 'run')
Shuqian Zhaod9992722016-02-29 12:26:38 -0800127 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
Richard Barnette3e8b2282018-05-15 20:42:20 +0000128 '_get_last_update_error')
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800129 self.host.hostname = 'test_host'
130 updater_control_bin = '/usr/bin/update_engine_client'
131 test_url = 'http://server/test/url'
132 expected_wait_cmd = ('%s -status | grep CURRENT_OP' %
133 updater_control_bin)
134 expected_cmd = ('%s --check_for_update --omaha_url=%s' %
135 (updater_control_bin, test_url))
136 self.mox.StubOutWithMock(time, "sleep")
137 UPDATE_ENGINE_RETRY_WAIT_TIME=5
Don Garrettdf8aef72013-12-16 11:12:41 -0800138
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800139 # Generic SSH Error.
Don Garrettdf8aef72013-12-16 11:12:41 -0800140 cmd_result_255 = self.mox.CreateMockAnything()
141 cmd_result_255.exit_status = 255
142
Shuqian Zhaod9992722016-02-29 12:26:38 -0800143 # Command Failed Error
144 cmd_result_1 = self.mox.CreateMockAnything()
145 cmd_result_1.exit_status = 1
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800146
147 # Error 37
148 cmd_result_37 = self.mox.CreateMockAnything()
149 cmd_result_37.exit_status = 37
150
151 updater = autoupdater.ChromiumOSUpdater(update_url, host=self.host)
152
153 # (SUCCESS) Expect one wait command and one status command.
154 self._host_run_for_update(expected_wait_cmd)
155 self._host_run_for_update(expected_cmd)
156
157 # (SUCCESS) Test with one retry to wait for update-engine.
158 self._host_run_for_update(expected_wait_cmd, exception=
159 error.AutoservRunError('non-zero status', cmd_result_1))
160 time.sleep(UPDATE_ENGINE_RETRY_WAIT_TIME)
161 self._host_run_for_update(expected_wait_cmd)
162 self._host_run_for_update(expected_cmd)
163
164 # (SUCCESS) One-time SSH timeout, then success on retry.
165 self._host_run_for_update(expected_wait_cmd)
166 self._host_run_for_update(expected_cmd, exception=
167 error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
168 self._host_run_for_update(expected_cmd)
169
170 # (SUCCESS) One-time ERROR 37, then success.
171 self._host_run_for_update(expected_wait_cmd)
172 self._host_run_for_update(expected_cmd, exception=
173 error.AutoservRunError('ERROR_CODE=37', cmd_result_37))
174 self._host_run_for_update(expected_cmd)
175
176 # (FAILURE) Bad status of update engine.
177 self._host_run_for_update(expected_wait_cmd)
178 self._host_run_for_update(expected_cmd, bad_update_status=True,
179 exception=error.InstallError(
180 'host is not in installable state'))
181
182 # (FAILURE) Two-time SSH timeout.
183 self._host_run_for_update(expected_wait_cmd)
184 self._host_run_for_update(expected_cmd, exception=
185 error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
186 self._host_run_for_update(expected_cmd, exception=
187 error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
188
189 # (FAILURE) SSH Permission Error
190 self._host_run_for_update(expected_wait_cmd)
191 self._host_run_for_update(expected_cmd, exception=
192 error.AutoservSshPermissionDeniedError('no permission',
193 cmd_result_255))
194
195 # (FAILURE) Other ssh failure
196 self._host_run_for_update(expected_wait_cmd)
197 self._host_run_for_update(expected_cmd, exception=
198 error.AutoservSshPermissionDeniedError('no permission',
199 cmd_result_255))
200 # (FAILURE) Other error
201 self._host_run_for_update(expected_wait_cmd)
202 self._host_run_for_update(expected_cmd, exception=
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800203 error.AutoservRunError("unknown error", cmd_result_1))
Shuqian Zhaod9992722016-02-29 12:26:38 -0800204
Don Garrettdf8aef72013-12-16 11:12:41 -0800205 self.mox.ReplayAll()
206
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800207 # Expect success
208 updater.trigger_update()
209 updater.trigger_update()
Don Garrettdf8aef72013-12-16 11:12:41 -0800210 updater.trigger_update()
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800211 updater.trigger_update()
Don Garrettdf8aef72013-12-16 11:12:41 -0800212
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800213 # Expect errors as listed above
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)
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800218 self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
Don Garrettdf8aef72013-12-16 11:12:41 -0800219
220 self.mox.VerifyAll()
221
Chris Sosa72312602013-04-16 15:01:56 -0700222 def testUpdateStateful(self):
223 """Tests that we call the stateful update script with the correct args.
224 """
225 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
Chris Sosab9ada9b2013-06-12 12:47:47 -0700226 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700227 '_get_stateful_update_script')
Chris Sosa72312602013-04-16 15:01:56 -0700228 update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
229 'R28-4444.0.0-b2996')
joychen03eaad92013-06-26 09:55:21 -0700230 static_update_url = ('http://172.22.50.205:8082/static/'
Chris Sosa72312602013-04-16 15:01:56 -0700231 'lumpy-chrome-perf/R28-4444.0.0-b2996')
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700232 update_script = '/usr/local/bin/stateful_update'
Chris Sosa72312602013-04-16 15:01:56 -0700233
234 # Test with clobber=False.
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700235 autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
236 update_script)
Chris Sosa72312602013-04-16 15:01:56 -0700237 autoupdater.ChromiumOSUpdater._run(
238 mox.And(
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700239 mox.StrContains(update_script),
Chris Sosa72312602013-04-16 15:01:56 -0700240 mox.StrContains(static_update_url),
241 mox.Not(mox.StrContains('--stateful_change=clean'))),
Dan Shi80aa9102016-01-25 13:45:00 -0800242 timeout=mox.IgnoreArg())
Chris Sosa72312602013-04-16 15:01:56 -0700243
244 self.mox.ReplayAll()
245 updater = autoupdater.ChromiumOSUpdater(update_url)
246 updater.update_stateful(clobber=False)
247 self.mox.VerifyAll()
248
249 # Test with clobber=True.
250 self.mox.ResetAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700251 autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
252 update_script)
Chris Sosa72312602013-04-16 15:01:56 -0700253 autoupdater.ChromiumOSUpdater._run(
254 mox.And(
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700255 mox.StrContains(update_script),
Chris Sosa72312602013-04-16 15:01:56 -0700256 mox.StrContains(static_update_url),
257 mox.StrContains('--stateful_change=clean')),
Dan Shi80aa9102016-01-25 13:45:00 -0800258 timeout=mox.IgnoreArg())
Chris Sosa72312602013-04-16 15:01:56 -0700259 self.mox.ReplayAll()
260 updater = autoupdater.ChromiumOSUpdater(update_url)
261 updater.update_stateful(clobber=True)
262 self.mox.VerifyAll()
263
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700264 def testGetRemoteScript(self):
265 """Test _get_remote_script() behaviors."""
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700266 update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
267 'R28-4444.0.0-b2996')
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700268 script_name = 'fubar'
269 local_script = '/usr/local/bin/%s' % script_name
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700270 host = self.mox.CreateMockAnything()
271 updater = autoupdater.ChromiumOSUpdater(update_url, host=host)
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700272 host.path_exists(local_script).AndReturn(True)
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700273
274 self.mox.ReplayAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700275 # Simple case: file exists on DUT
276 self.assertEqual(updater._get_remote_script(script_name),
277 local_script)
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700278 self.mox.VerifyAll()
279
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700280 self.mox.ResetAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700281 fake_shell = '/bin/ash'
Laurence Goodby06fb42c2020-02-29 17:14:42 -0800282 tmp_script = '/usr/local/tmp/%s' % script_name
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700283 fake_result = self.mox.CreateMockAnything()
Dana Goyette353d1d92019-06-27 10:43:59 -0700284 fake_result.stdout = '#!%s\n' % fake_shell
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700285 host.path_exists(local_script).AndReturn(False)
Laurence Goodby06fb42c2020-02-29 17:14:42 -0800286 host.run(mox.IgnoreArg())
Dana Goyette353d1d92019-06-27 10:43:59 -0700287 host.run(mox.IgnoreArg()).AndReturn(fake_result)
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700288
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700289 self.mox.ReplayAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700290 # Complicated case: script not on DUT, so try to download it.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700291 self.assertEqual(
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700292 updater._get_remote_script(script_name),
293 '%s %s' % (fake_shell, tmp_script))
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700294 self.mox.VerifyAll()
295
Chris Sosac8617522014-06-09 23:22:26 +0000296 def testRollbackRootfs(self):
297 """Tests that we correctly rollback the rootfs when requested."""
298 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
Chris Sosac8617522014-06-09 23:22:26 +0000299 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
300 '_verify_update_completed')
301 host = self.mox.CreateMockAnything()
302 update_url = 'http://server/test/url'
303 host.hostname = 'test_host'
304
305 can_rollback_cmd = ('/usr/bin/update_engine_client --can_rollback')
306 rollback_cmd = ('/usr/bin/update_engine_client --rollback '
307 '--follow')
308
309 updater = autoupdater.ChromiumOSUpdater(update_url, host=host)
310
311 # Return an old build which shouldn't call can_rollback.
Dan Shi549fb822015-03-24 18:01:11 -0700312 updater.host.get_release_version().AndReturn('1234.0.0')
Chris Sosac8617522014-06-09 23:22:26 +0000313 autoupdater.ChromiumOSUpdater._run(rollback_cmd)
314 autoupdater.ChromiumOSUpdater._verify_update_completed()
315
316 self.mox.ReplayAll()
317 updater.rollback_rootfs(powerwash=True)
318 self.mox.VerifyAll()
319
320 self.mox.ResetAll()
321 cmd_result_1 = self.mox.CreateMockAnything()
322 cmd_result_1.exit_status = 1
323
324 # Rollback but can_rollback says we can't -- return an error.
Dan Shi549fb822015-03-24 18:01:11 -0700325 updater.host.get_release_version().AndReturn('5775.0.0')
Chris Sosac8617522014-06-09 23:22:26 +0000326 autoupdater.ChromiumOSUpdater._run(can_rollback_cmd).AndRaise(
327 error.AutoservRunError('can_rollback failed', cmd_result_1))
328 self.mox.ReplayAll()
329 self.assertRaises(autoupdater.RootFSUpdateError,
330 updater.rollback_rootfs, True)
331 self.mox.VerifyAll()
332
333 self.mox.ResetAll()
334 # Rollback >= version blacklisted.
Dan Shi549fb822015-03-24 18:01:11 -0700335 updater.host.get_release_version().AndReturn('5775.0.0')
Chris Sosac8617522014-06-09 23:22:26 +0000336 autoupdater.ChromiumOSUpdater._run(can_rollback_cmd)
337 autoupdater.ChromiumOSUpdater._run(rollback_cmd)
338 autoupdater.ChromiumOSUpdater._verify_update_completed()
339 self.mox.ReplayAll()
340 updater.rollback_rootfs(powerwash=True)
341 self.mox.VerifyAll()
342
343
Congbin Guo20ef0ce2019-05-15 12:08:13 -0700344class TestAutoUpdater2(unittest.TestCase):
345 """Another test for autoupdater module that using mock."""
346
347 def testAlwaysRunQuickProvision(self):
348 """Tests that we call quick provsion for all kinds of builds."""
349 image = 'foo-whatever/R65-1234.5.6'
350 devserver = 'http://mock_devserver'
351 autoupdater.dev_server = mock.MagicMock()
352 autoupdater.metrics = mock.MagicMock()
353 host = mock.MagicMock()
354 update_url = '%s/update/%s' % (devserver, image)
355 updater = autoupdater.ChromiumOSUpdater(update_url, host,
356 use_quick_provision=True)
357 updater.check_update_status = mock.MagicMock()
358 updater.check_update_status.return_value = autoupdater.UPDATER_IDLE
359 updater._verify_kernel_state = mock.MagicMock()
360 updater._verify_kernel_state.return_value = 3
361 updater.verify_boot_expectations = mock.MagicMock()
362
363 updater.run_update()
364 host.run.assert_any_call(
Congbin Guo4a2a6642019-08-12 15:03:01 -0700365 '/usr/local/bin/quick-provision --noreboot %s '
366 '%s/download/chromeos-image-archive' % (image, devserver))
Congbin Guo20ef0ce2019-05-15 12:08:13 -0700367
368
Scott Zawalskieadbf702013-03-14 09:23:06 -0400369if __name__ == '__main__':
Congbin Guo20ef0ce2019-05-15 12:08:13 -0700370 unittest.main()