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 6 import mox 7 import time 8 import unittest 9 10 import common 11 from autotest_lib.client.common_lib import error 12 from autotest_lib.server.cros import autoupdater 13 14 15 class _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 29 class 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 90 class 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 343 if __name__ == '__main__': 344 unittest.main() 345