Home | History | Annotate | Download | only in cros
      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