blob: cf82a29609bbbbddb148a23478d194b47afcc22f [file] [log] [blame]
Scott Zawalskieadbf702013-03-14 09:23:06 -04001#!/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
Dan Shib95bb862013-03-22 16:29:28 -07006import mox
Richard Barnette5adb6d42018-06-28 15:52:32 -07007import time
Scott Zawalskieadbf702013-03-14 09:23:06 -04008import unittest
9
10import common
Don Garrettdf8aef72013-12-16 11:12:41 -080011from autotest_lib.client.common_lib import error
Richard Barnette5adb6d42018-06-28 15:52:32 -070012from 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
Scott Zawalskieadbf702013-03-14 09:23:06 -040089
Dan Shib95bb862013-03-22 16:29:28 -070090class TestAutoUpdater(mox.MoxTestBase):
Scott Zawalskieadbf702013-03-14 09:23:06 -040091 """Test autoupdater module."""
92
Scott Zawalskieadbf702013-03-14 09:23:06 -040093 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
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800101 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
Don Garrettdf8aef72013-12-16 11:12:41 -0800121 def testTriggerUpdate(self):
122 """Tests that we correctly handle updater errors."""
Don Garrettdf8aef72013-12-16 11:12:41 -0800123 update_url = 'http://server/test/url'
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800124 self.host = self.mox.CreateMockAnything()
125 self.mox.StubOutWithMock(self.host, 'run')
Shuqian Zhaod9992722016-02-29 12:26:38 -0800126 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
Richard Barnette3e8b2282018-05-15 20:42:20 +0000127 '_get_last_update_error')
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800128 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
Don Garrettdf8aef72013-12-16 11:12:41 -0800137
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800138 # Generic SSH Error.
Don Garrettdf8aef72013-12-16 11:12:41 -0800139 cmd_result_255 = self.mox.CreateMockAnything()
140 cmd_result_255.exit_status = 255
141
Shuqian Zhaod9992722016-02-29 12:26:38 -0800142 # Command Failed Error
143 cmd_result_1 = self.mox.CreateMockAnything()
144 cmd_result_1.exit_status = 1
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800145
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=
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800202 error.AutoservRunError("unknown error", cmd_result_1))
Shuqian Zhaod9992722016-02-29 12:26:38 -0800203
Don Garrettdf8aef72013-12-16 11:12:41 -0800204 self.mox.ReplayAll()
205
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800206 # Expect success
207 updater.trigger_update()
208 updater.trigger_update()
Don Garrettdf8aef72013-12-16 11:12:41 -0800209 updater.trigger_update()
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800210 updater.trigger_update()
Don Garrettdf8aef72013-12-16 11:12:41 -0800211
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800212 # 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)
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800217 self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
Don Garrettdf8aef72013-12-16 11:12:41 -0800218
219 self.mox.VerifyAll()
220
Chris Sosa72312602013-04-16 15:01:56 -0700221 def testUpdateStateful(self):
222 """Tests that we call the stateful update script with the correct args.
223 """
224 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
Chris Sosab9ada9b2013-06-12 12:47:47 -0700225 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700226 '_get_stateful_update_script')
Chris Sosa72312602013-04-16 15:01:56 -0700227 update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
228 'R28-4444.0.0-b2996')
joychen03eaad92013-06-26 09:55:21 -0700229 static_update_url = ('http://172.22.50.205:8082/static/'
Chris Sosa72312602013-04-16 15:01:56 -0700230 'lumpy-chrome-perf/R28-4444.0.0-b2996')
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700231 update_script = '/usr/local/bin/stateful_update'
Chris Sosa72312602013-04-16 15:01:56 -0700232
233 # Test with clobber=False.
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700234 autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
235 update_script)
Chris Sosa72312602013-04-16 15:01:56 -0700236 autoupdater.ChromiumOSUpdater._run(
237 mox.And(
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700238 mox.StrContains(update_script),
Chris Sosa72312602013-04-16 15:01:56 -0700239 mox.StrContains(static_update_url),
240 mox.Not(mox.StrContains('--stateful_change=clean'))),
Dan Shi80aa9102016-01-25 13:45:00 -0800241 timeout=mox.IgnoreArg())
Chris Sosa72312602013-04-16 15:01:56 -0700242
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()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700250 autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
251 update_script)
Chris Sosa72312602013-04-16 15:01:56 -0700252 autoupdater.ChromiumOSUpdater._run(
253 mox.And(
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700254 mox.StrContains(update_script),
Chris Sosa72312602013-04-16 15:01:56 -0700255 mox.StrContains(static_update_url),
256 mox.StrContains('--stateful_change=clean')),
Dan Shi80aa9102016-01-25 13:45:00 -0800257 timeout=mox.IgnoreArg())
Chris Sosa72312602013-04-16 15:01:56 -0700258 self.mox.ReplayAll()
259 updater = autoupdater.ChromiumOSUpdater(update_url)
260 updater.update_stateful(clobber=True)
261 self.mox.VerifyAll()
262
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700263 def testGetRemoteScript(self):
264 """Test _get_remote_script() behaviors."""
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700265 update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
266 'R28-4444.0.0-b2996')
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700267 script_name = 'fubar'
268 local_script = '/usr/local/bin/%s' % script_name
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700269 host = self.mox.CreateMockAnything()
270 updater = autoupdater.ChromiumOSUpdater(update_url, host=host)
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700271 host.path_exists(local_script).AndReturn(True)
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700272
273 self.mox.ReplayAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700274 # Simple case: file exists on DUT
275 self.assertEqual(updater._get_remote_script(script_name),
276 local_script)
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700277 self.mox.VerifyAll()
278
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700279 self.mox.ResetAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700280 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
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700288 self.mox.ReplayAll()
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700289 # Complicated case: script not on DUT, so try to download it.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700290 self.assertEqual(
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700291 updater._get_remote_script(script_name),
292 '%s %s' % (fake_shell, tmp_script))
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700293 self.mox.VerifyAll()
294
Chris Sosac8617522014-06-09 23:22:26 +0000295 def testRollbackRootfs(self):
296 """Tests that we correctly rollback the rootfs when requested."""
297 self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
Chris Sosac8617522014-06-09 23:22:26 +0000298 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.
Dan Shi549fb822015-03-24 18:01:11 -0700311 updater.host.get_release_version().AndReturn('1234.0.0')
Chris Sosac8617522014-06-09 23:22:26 +0000312 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.
Dan Shi549fb822015-03-24 18:01:11 -0700324 updater.host.get_release_version().AndReturn('5775.0.0')
Chris Sosac8617522014-06-09 23:22:26 +0000325 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.
Dan Shi549fb822015-03-24 18:01:11 -0700334 updater.host.get_release_version().AndReturn('5775.0.0')
Chris Sosac8617522014-06-09 23:22:26 +0000335 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
Scott Zawalskieadbf702013-03-14 09:23:06 -0400343if __name__ == '__main__':
344 unittest.main()