blob: d5a4c945d7ff9db6420a2f6755ff320315826e2f [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
David Haddock77b75c32020-05-14 01:56:32 -070013from autotest_lib.client.common_lib.cros import kernel_utils
Richard Barnette5adb6d42018-06-28 15:52:32 -070014from autotest_lib.server.cros import autoupdater
15
16
17class _StubUpdateError(autoupdater._AttributedUpdateError):
18 STUB_MESSAGE = 'Stub message'
19 STUB_PATTERN = 'Stub pattern matched'
20 _SUMMARY = 'Stub summary'
21 _CLASSIFIERS = [
22 (STUB_MESSAGE, STUB_MESSAGE),
23 ('Stub .*', STUB_PATTERN),
24 ]
25
26 def __init__(self, info, msg):
27 super(_StubUpdateError, self).__init__(
28 'Stub %s' % info, msg)
29
30
31class TestErrorClassifications(unittest.TestCase):
32 """Test error message handling in `_AttributedUpdateError`."""
33
34 def test_exception_message(self):
35 """Test that the exception string includes its arguments."""
36 info = 'info marker'
37 msg = 'an error message'
38 stub = _StubUpdateError(info, msg)
39 self.assertIn(info, str(stub))
40 self.assertIn(msg, str(stub))
41
42 def test_classifier_message(self):
43 """Test that the exception classifier can match a simple string."""
44 info = 'info marker'
45 stub = _StubUpdateError(info, _StubUpdateError.STUB_MESSAGE)
46 self.assertNotIn(info, stub.failure_summary)
47 self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
48 self.assertIn(_StubUpdateError.STUB_MESSAGE, stub.failure_summary)
49
50 def test_classifier_pattern(self):
51 """Test that the exception classifier can match a regex."""
52 info = 'info marker'
53 stub = _StubUpdateError(info, 'Stub this is a test')
54 self.assertNotIn(info, stub.failure_summary)
55 self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
56 self.assertIn(_StubUpdateError.STUB_PATTERN, stub.failure_summary)
57
58 def test_classifier_unmatched(self):
59 """Test exception summary when no classifier matches."""
60 info = 'info marker'
61 stub = _StubUpdateError(info, 'This matches no pattern')
62 self.assertNotIn(info, stub.failure_summary)
63 self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
64
65 def test_host_update_error(self):
66 """Sanity test the `HostUpdateError` classifier."""
67 exception = autoupdater.HostUpdateError(
68 'chromeos6-row3-rack3-host19', 'Fake message')
69 self.assertTrue(isinstance(exception.failure_summary, str))
70
71 def test_dev_server_error(self):
72 """Sanity test the `DevServerError` classifier."""
73 exception = autoupdater.DevServerError(
74 'chromeos4-devserver7.cros', 'Fake message')
75 self.assertTrue(isinstance(exception.failure_summary, str))
76
77 def test_image_install_error(self):
78 """Sanity test the `ImageInstallError` classifier."""
79 exception = autoupdater.ImageInstallError(
80 'chromeos6-row3-rack3-host19',
81 'chromeos4-devserver7.cros',
82 'Fake message')
83 self.assertTrue(isinstance(exception.failure_summary, str))
84
85 def test_new_build_update_error(self):
86 """Sanity test the `NewBuildUpdateError` classifier."""
87 exception = autoupdater.NewBuildUpdateError(
88 'R68-10621.0.0', 'Fake message')
89 self.assertTrue(isinstance(exception.failure_summary, str))
90
Scott Zawalskieadbf702013-03-14 09:23:06 -040091
Dan Shib95bb862013-03-22 16:29:28 -070092class TestAutoUpdater(mox.MoxTestBase):
Scott Zawalskieadbf702013-03-14 09:23:06 -040093 """Test autoupdater module."""
94
Scott Zawalskieadbf702013-03-14 09:23:06 -040095 def testParseBuildFromUpdateUrlwithUpdate(self):
96 """Test that we properly parse the build from an update_url."""
97 update_url = ('http://172.22.50.205:8082/update/lumpy-release/'
98 'R27-3837.0.0')
99 expected_value = 'lumpy-release/R27-3837.0.0'
100 self.assertEqual(autoupdater.url_to_image_name(update_url),
101 expected_value)
102
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800103 def _host_run_for_update(self, cmd, exception=None,
104 bad_update_status=False):
105 """Helper function for AU tests.
106
107 @param host: the test host
108 @param cmd: the command to be recorded
109 @param exception: the exception to be recorded, or None
110 """
111 if exception:
112 self.host.run(command=cmd).AndRaise(exception)
113 else:
114 result = self.mox.CreateMockAnything()
115 if bad_update_status:
116 # Pick randomly one unexpected status
117 result.stdout = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
118 else:
119 result.stdout = 'UPDATE_STATUS_IDLE'
120 result.status = 0
121 self.host.run(command=cmd).AndReturn(result)
122
Don Garrettdf8aef72013-12-16 11:12:41 -0800123 def testTriggerUpdate(self):
124 """Tests that we correctly handle updater errors."""
Don Garrettdf8aef72013-12-16 11:12:41 -0800125 update_url = 'http://server/test/url'
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800126 self.host = self.mox.CreateMockAnything()
127 self.mox.StubOutWithMock(self.host, 'run')
Shuqian Zhaod9992722016-02-29 12:26:38 -0800128 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
Richard Barnette3e8b2282018-05-15 20:42:20 +0000129 '_get_last_update_error')
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800130 self.host.hostname = 'test_host'
131 updater_control_bin = '/usr/bin/update_engine_client'
132 test_url = 'http://server/test/url'
133 expected_wait_cmd = ('%s -status | grep CURRENT_OP' %
134 updater_control_bin)
135 expected_cmd = ('%s --check_for_update --omaha_url=%s' %
136 (updater_control_bin, test_url))
137 self.mox.StubOutWithMock(time, "sleep")
138 UPDATE_ENGINE_RETRY_WAIT_TIME=5
Don Garrettdf8aef72013-12-16 11:12:41 -0800139
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800140 # Generic SSH Error.
Don Garrettdf8aef72013-12-16 11:12:41 -0800141 cmd_result_255 = self.mox.CreateMockAnything()
142 cmd_result_255.exit_status = 255
143
Shuqian Zhaod9992722016-02-29 12:26:38 -0800144 # Command Failed Error
145 cmd_result_1 = self.mox.CreateMockAnything()
146 cmd_result_1.exit_status = 1
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800147
148 # Error 37
149 cmd_result_37 = self.mox.CreateMockAnything()
150 cmd_result_37.exit_status = 37
151
152 updater = autoupdater.ChromiumOSUpdater(update_url, host=self.host)
153
154 # (SUCCESS) Expect one wait command and one status command.
155 self._host_run_for_update(expected_wait_cmd)
156 self._host_run_for_update(expected_cmd)
157
158 # (SUCCESS) Test with one retry to wait for update-engine.
159 self._host_run_for_update(expected_wait_cmd, exception=
160 error.AutoservRunError('non-zero status', cmd_result_1))
161 time.sleep(UPDATE_ENGINE_RETRY_WAIT_TIME)
162 self._host_run_for_update(expected_wait_cmd)
163 self._host_run_for_update(expected_cmd)
164
165 # (SUCCESS) One-time SSH timeout, then success on retry.
166 self._host_run_for_update(expected_wait_cmd)
167 self._host_run_for_update(expected_cmd, exception=
168 error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
169 self._host_run_for_update(expected_cmd)
170
171 # (SUCCESS) One-time ERROR 37, then success.
172 self._host_run_for_update(expected_wait_cmd)
173 self._host_run_for_update(expected_cmd, exception=
174 error.AutoservRunError('ERROR_CODE=37', cmd_result_37))
175 self._host_run_for_update(expected_cmd)
176
177 # (FAILURE) Bad status of update engine.
178 self._host_run_for_update(expected_wait_cmd)
179 self._host_run_for_update(expected_cmd, bad_update_status=True,
180 exception=error.InstallError(
181 'host is not in installable state'))
182
183 # (FAILURE) Two-time SSH timeout.
184 self._host_run_for_update(expected_wait_cmd)
185 self._host_run_for_update(expected_cmd, exception=
186 error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
187 self._host_run_for_update(expected_cmd, exception=
188 error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
189
190 # (FAILURE) SSH Permission Error
191 self._host_run_for_update(expected_wait_cmd)
192 self._host_run_for_update(expected_cmd, exception=
193 error.AutoservSshPermissionDeniedError('no permission',
194 cmd_result_255))
195
196 # (FAILURE) Other ssh failure
197 self._host_run_for_update(expected_wait_cmd)
198 self._host_run_for_update(expected_cmd, exception=
199 error.AutoservSshPermissionDeniedError('no permission',
200 cmd_result_255))
201 # (FAILURE) Other error
202 self._host_run_for_update(expected_wait_cmd)
203 self._host_run_for_update(expected_cmd, exception=
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800204 error.AutoservRunError("unknown error", cmd_result_1))
Shuqian Zhaod9992722016-02-29 12:26:38 -0800205
Don Garrettdf8aef72013-12-16 11:12:41 -0800206 self.mox.ReplayAll()
207
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800208 # Expect success
209 updater.trigger_update()
210 updater.trigger_update()
Don Garrettdf8aef72013-12-16 11:12:41 -0800211 updater.trigger_update()
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800212 updater.trigger_update()
Don Garrettdf8aef72013-12-16 11:12:41 -0800213
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800214 # Expect errors as listed above
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 self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800219 self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
Don Garrettdf8aef72013-12-16 11:12:41 -0800220
221 self.mox.VerifyAll()
222
Chris Sosa72312602013-04-16 15:01:56 -0700223 def testUpdateStateful(self):
224 """Tests that we call the stateful update script with the correct args.
225 """
226 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
Chris Sosab9ada9b2013-06-12 12:47:47 -0700227 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700228 '_get_stateful_update_script')
Chris Sosa72312602013-04-16 15:01:56 -0700229 update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
230 'R28-4444.0.0-b2996')
joychen03eaad92013-06-26 09:55:21 -0700231 static_update_url = ('http://172.22.50.205:8082/static/'
Chris Sosa72312602013-04-16 15:01:56 -0700232 'lumpy-chrome-perf/R28-4444.0.0-b2996')
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700233 update_script = '/usr/local/bin/stateful_update'
Chris Sosa72312602013-04-16 15:01:56 -0700234
235 # Test with clobber=False.
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700236 autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
237 update_script)
Chris Sosa72312602013-04-16 15:01:56 -0700238 autoupdater.ChromiumOSUpdater._run(
239 mox.And(
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700240 mox.StrContains(update_script),
Chris Sosa72312602013-04-16 15:01:56 -0700241 mox.StrContains(static_update_url),
242 mox.Not(mox.StrContains('--stateful_change=clean'))),
Dan Shi80aa9102016-01-25 13:45:00 -0800243 timeout=mox.IgnoreArg())
Chris Sosa72312602013-04-16 15:01:56 -0700244
245 self.mox.ReplayAll()
246 updater = autoupdater.ChromiumOSUpdater(update_url)
247 updater.update_stateful(clobber=False)
248 self.mox.VerifyAll()
249
250 # Test with clobber=True.
251 self.mox.ResetAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700252 autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
253 update_script)
Chris Sosa72312602013-04-16 15:01:56 -0700254 autoupdater.ChromiumOSUpdater._run(
255 mox.And(
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700256 mox.StrContains(update_script),
Chris Sosa72312602013-04-16 15:01:56 -0700257 mox.StrContains(static_update_url),
258 mox.StrContains('--stateful_change=clean')),
Dan Shi80aa9102016-01-25 13:45:00 -0800259 timeout=mox.IgnoreArg())
Chris Sosa72312602013-04-16 15:01:56 -0700260 self.mox.ReplayAll()
261 updater = autoupdater.ChromiumOSUpdater(update_url)
262 updater.update_stateful(clobber=True)
263 self.mox.VerifyAll()
264
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700265 def testGetRemoteScript(self):
266 """Test _get_remote_script() behaviors."""
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700267 update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
268 'R28-4444.0.0-b2996')
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700269 script_name = 'fubar'
270 local_script = '/usr/local/bin/%s' % script_name
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700271 host = self.mox.CreateMockAnything()
272 updater = autoupdater.ChromiumOSUpdater(update_url, host=host)
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700273 host.path_exists(local_script).AndReturn(True)
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700274
275 self.mox.ReplayAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700276 # Simple case: file exists on DUT
277 self.assertEqual(updater._get_remote_script(script_name),
278 local_script)
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700279 self.mox.VerifyAll()
280
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700281 self.mox.ResetAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700282 fake_shell = '/bin/ash'
Laurence Goodby06fb42c2020-02-29 17:14:42 -0800283 tmp_script = '/usr/local/tmp/%s' % script_name
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700284 fake_result = self.mox.CreateMockAnything()
Dana Goyette353d1d92019-06-27 10:43:59 -0700285 fake_result.stdout = '#!%s\n' % fake_shell
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700286 host.path_exists(local_script).AndReturn(False)
Laurence Goodby06fb42c2020-02-29 17:14:42 -0800287 host.run(mox.IgnoreArg())
Dana Goyette353d1d92019-06-27 10:43:59 -0700288 host.run(mox.IgnoreArg()).AndReturn(fake_result)
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700289
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700290 self.mox.ReplayAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700291 # Complicated case: script not on DUT, so try to download it.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700292 self.assertEqual(
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700293 updater._get_remote_script(script_name),
294 '%s %s' % (fake_shell, tmp_script))
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700295 self.mox.VerifyAll()
296
Chris Sosac8617522014-06-09 23:22:26 +0000297 def testRollbackRootfs(self):
298 """Tests that we correctly rollback the rootfs when requested."""
299 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
Chris Sosac8617522014-06-09 23:22:26 +0000300 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
301 '_verify_update_completed')
302 host = self.mox.CreateMockAnything()
303 update_url = 'http://server/test/url'
304 host.hostname = 'test_host'
305
306 can_rollback_cmd = ('/usr/bin/update_engine_client --can_rollback')
307 rollback_cmd = ('/usr/bin/update_engine_client --rollback '
308 '--follow')
309
310 updater = autoupdater.ChromiumOSUpdater(update_url, host=host)
311
312 # Return an old build which shouldn't call can_rollback.
Dan Shi549fb822015-03-24 18:01:11 -0700313 updater.host.get_release_version().AndReturn('1234.0.0')
Chris Sosac8617522014-06-09 23:22:26 +0000314 autoupdater.ChromiumOSUpdater._run(rollback_cmd)
315 autoupdater.ChromiumOSUpdater._verify_update_completed()
316
317 self.mox.ReplayAll()
318 updater.rollback_rootfs(powerwash=True)
319 self.mox.VerifyAll()
320
321 self.mox.ResetAll()
322 cmd_result_1 = self.mox.CreateMockAnything()
323 cmd_result_1.exit_status = 1
324
325 # Rollback but can_rollback says we can't -- return an error.
Dan Shi549fb822015-03-24 18:01:11 -0700326 updater.host.get_release_version().AndReturn('5775.0.0')
Chris Sosac8617522014-06-09 23:22:26 +0000327 autoupdater.ChromiumOSUpdater._run(can_rollback_cmd).AndRaise(
328 error.AutoservRunError('can_rollback failed', cmd_result_1))
329 self.mox.ReplayAll()
330 self.assertRaises(autoupdater.RootFSUpdateError,
331 updater.rollback_rootfs, True)
332 self.mox.VerifyAll()
333
334 self.mox.ResetAll()
335 # Rollback >= version blacklisted.
Dan Shi549fb822015-03-24 18:01:11 -0700336 updater.host.get_release_version().AndReturn('5775.0.0')
Chris Sosac8617522014-06-09 23:22:26 +0000337 autoupdater.ChromiumOSUpdater._run(can_rollback_cmd)
338 autoupdater.ChromiumOSUpdater._run(rollback_cmd)
339 autoupdater.ChromiumOSUpdater._verify_update_completed()
340 self.mox.ReplayAll()
341 updater.rollback_rootfs(powerwash=True)
342 self.mox.VerifyAll()
343
344
Congbin Guo20ef0ce2019-05-15 12:08:13 -0700345class TestAutoUpdater2(unittest.TestCase):
346 """Another test for autoupdater module that using mock."""
347
348 def testAlwaysRunQuickProvision(self):
349 """Tests that we call quick provsion for all kinds of builds."""
350 image = 'foo-whatever/R65-1234.5.6'
351 devserver = 'http://mock_devserver'
352 autoupdater.dev_server = mock.MagicMock()
353 autoupdater.metrics = mock.MagicMock()
354 host = mock.MagicMock()
355 update_url = '%s/update/%s' % (devserver, image)
356 updater = autoupdater.ChromiumOSUpdater(update_url, host,
357 use_quick_provision=True)
358 updater.check_update_status = mock.MagicMock()
359 updater.check_update_status.return_value = autoupdater.UPDATER_IDLE
David Haddock77b75c32020-05-14 01:56:32 -0700360 kernel_utils.verify_kernel_state_after_update = mock.MagicMock()
361 kernel_utils.verify_kernel_state_after_update.return_value = 3
362 kernel_utils.verify_boot_expectations = mock.MagicMock()
Congbin Guo20ef0ce2019-05-15 12:08:13 -0700363
364 updater.run_update()
365 host.run.assert_any_call(
Congbin Guo4a2a6642019-08-12 15:03:01 -0700366 '/usr/local/bin/quick-provision --noreboot %s '
367 '%s/download/chromeos-image-archive' % (image, devserver))
Congbin Guo20ef0ce2019-05-15 12:08:13 -0700368
369
Scott Zawalskieadbf702013-03-14 09:23:06 -0400370if __name__ == '__main__':
Congbin Guo20ef0ce2019-05-15 12:08:13 -0700371 unittest.main()