普通文本  |  345行  |  14.17 KB

#!/usr/bin/python
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import mox
import time
import unittest

import common
from autotest_lib.client.common_lib import error
from autotest_lib.server.cros import autoupdater


class _StubUpdateError(autoupdater._AttributedUpdateError):
    STUB_MESSAGE = 'Stub message'
    STUB_PATTERN = 'Stub pattern matched'
    _SUMMARY = 'Stub summary'
    _CLASSIFIERS = [
        (STUB_MESSAGE, STUB_MESSAGE),
        ('Stub .*', STUB_PATTERN),
    ]

    def __init__(self, info, msg):
        super(_StubUpdateError, self).__init__(
            'Stub %s' % info, msg)


class TestErrorClassifications(unittest.TestCase):
    """Test error message handling in `_AttributedUpdateError`."""

    def test_exception_message(self):
        """Test that the exception string includes its arguments."""
        info = 'info marker'
        msg = 'an error message'
        stub = _StubUpdateError(info, msg)
        self.assertIn(info, str(stub))
        self.assertIn(msg, str(stub))

    def test_classifier_message(self):
        """Test that the exception classifier can match a simple string."""
        info = 'info marker'
        stub = _StubUpdateError(info, _StubUpdateError.STUB_MESSAGE)
        self.assertNotIn(info, stub.failure_summary)
        self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
        self.assertIn(_StubUpdateError.STUB_MESSAGE, stub.failure_summary)

    def test_classifier_pattern(self):
        """Test that the exception classifier can match a regex."""
        info = 'info marker'
        stub = _StubUpdateError(info, 'Stub this is a test')
        self.assertNotIn(info, stub.failure_summary)
        self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)
        self.assertIn(_StubUpdateError.STUB_PATTERN, stub.failure_summary)

    def test_classifier_unmatched(self):
        """Test exception summary when no classifier matches."""
        info = 'info marker'
        stub = _StubUpdateError(info, 'This matches no pattern')
        self.assertNotIn(info, stub.failure_summary)
        self.assertIn(_StubUpdateError._SUMMARY, stub.failure_summary)

    def test_host_update_error(self):
        """Sanity test the `HostUpdateError` classifier."""
        exception = autoupdater.HostUpdateError(
                'chromeos6-row3-rack3-host19', 'Fake message')
        self.assertTrue(isinstance(exception.failure_summary, str))

    def test_dev_server_error(self):
        """Sanity test the `DevServerError` classifier."""
        exception = autoupdater.DevServerError(
                'chromeos4-devserver7.cros', 'Fake message')
        self.assertTrue(isinstance(exception.failure_summary, str))

    def test_image_install_error(self):
        """Sanity test the `ImageInstallError` classifier."""
        exception = autoupdater.ImageInstallError(
                'chromeos6-row3-rack3-host19',
                'chromeos4-devserver7.cros',
                'Fake message')
        self.assertTrue(isinstance(exception.failure_summary, str))

    def test_new_build_update_error(self):
        """Sanity test the `NewBuildUpdateError` classifier."""
        exception = autoupdater.NewBuildUpdateError(
                'R68-10621.0.0', 'Fake message')
        self.assertTrue(isinstance(exception.failure_summary, str))


class TestAutoUpdater(mox.MoxTestBase):
    """Test autoupdater module."""

    def testParseBuildFromUpdateUrlwithUpdate(self):
        """Test that we properly parse the build from an update_url."""
        update_url = ('http://172.22.50.205:8082/update/lumpy-release/'
                      'R27-3837.0.0')
        expected_value = 'lumpy-release/R27-3837.0.0'
        self.assertEqual(autoupdater.url_to_image_name(update_url),
                         expected_value)

    def _host_run_for_update(self, cmd, exception=None,
                             bad_update_status=False):
        """Helper function for AU tests.

        @param host: the test host
        @param cmd: the command to be recorded
        @param exception: the exception to be recorded, or None
        """
        if exception:
            self.host.run(command=cmd).AndRaise(exception)
        else:
            result = self.mox.CreateMockAnything()
            if bad_update_status:
                # Pick randomly one unexpected status
                result.stdout = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
            else:
                result.stdout = 'UPDATE_STATUS_IDLE'
            result.status = 0
            self.host.run(command=cmd).AndReturn(result)

    def testTriggerUpdate(self):
        """Tests that we correctly handle updater errors."""
        update_url = 'http://server/test/url'
        self.host = self.mox.CreateMockAnything()
        self.mox.StubOutWithMock(self.host, 'run')
        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
                                 '_get_last_update_error')
        self.host.hostname = 'test_host'
        updater_control_bin = '/usr/bin/update_engine_client'
        test_url = 'http://server/test/url'
        expected_wait_cmd = ('%s -status | grep CURRENT_OP' %
                             updater_control_bin)
        expected_cmd = ('%s --check_for_update --omaha_url=%s' %
                        (updater_control_bin, test_url))
        self.mox.StubOutWithMock(time, "sleep")
        UPDATE_ENGINE_RETRY_WAIT_TIME=5

        # Generic SSH Error.
        cmd_result_255 = self.mox.CreateMockAnything()
        cmd_result_255.exit_status = 255

        # Command Failed Error
        cmd_result_1 = self.mox.CreateMockAnything()
        cmd_result_1.exit_status = 1

        # Error 37
        cmd_result_37 = self.mox.CreateMockAnything()
        cmd_result_37.exit_status = 37

        updater = autoupdater.ChromiumOSUpdater(update_url, host=self.host)

        # (SUCCESS) Expect one wait command and one status command.
        self._host_run_for_update(expected_wait_cmd)
        self._host_run_for_update(expected_cmd)

        # (SUCCESS) Test with one retry to wait for update-engine.
        self._host_run_for_update(expected_wait_cmd, exception=
                error.AutoservRunError('non-zero status', cmd_result_1))
        time.sleep(UPDATE_ENGINE_RETRY_WAIT_TIME)
        self._host_run_for_update(expected_wait_cmd)
        self._host_run_for_update(expected_cmd)

        # (SUCCESS) One-time SSH timeout, then success on retry.
        self._host_run_for_update(expected_wait_cmd)
        self._host_run_for_update(expected_cmd, exception=
                error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
        self._host_run_for_update(expected_cmd)

        # (SUCCESS) One-time ERROR 37, then success.
        self._host_run_for_update(expected_wait_cmd)
        self._host_run_for_update(expected_cmd, exception=
                error.AutoservRunError('ERROR_CODE=37', cmd_result_37))
        self._host_run_for_update(expected_cmd)

        # (FAILURE) Bad status of update engine.
        self._host_run_for_update(expected_wait_cmd)
        self._host_run_for_update(expected_cmd, bad_update_status=True,
                                  exception=error.InstallError(
                                      'host is not in installable state'))

        # (FAILURE) Two-time SSH timeout.
        self._host_run_for_update(expected_wait_cmd)
        self._host_run_for_update(expected_cmd, exception=
                error.AutoservSSHTimeout('ssh timed out', cmd_result_255))
        self._host_run_for_update(expected_cmd, exception=
                error.AutoservSSHTimeout('ssh timed out', cmd_result_255))

        # (FAILURE) SSH Permission Error
        self._host_run_for_update(expected_wait_cmd)
        self._host_run_for_update(expected_cmd, exception=
                error.AutoservSshPermissionDeniedError('no permission',
                                                       cmd_result_255))

        # (FAILURE) Other ssh failure
        self._host_run_for_update(expected_wait_cmd)
        self._host_run_for_update(expected_cmd, exception=
                error.AutoservSshPermissionDeniedError('no permission',
                                                       cmd_result_255))
        # (FAILURE) Other error
        self._host_run_for_update(expected_wait_cmd)
        self._host_run_for_update(expected_cmd, exception=
                error.AutoservRunError("unknown error", cmd_result_1))

        self.mox.ReplayAll()

        # Expect success
        updater.trigger_update()
        updater.trigger_update()
        updater.trigger_update()
        updater.trigger_update()

        # Expect errors as listed above
        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)
        self.assertRaises(autoupdater.RootFSUpdateError, updater.trigger_update)

        self.mox.VerifyAll()

    def testUpdateStateful(self):
        """Tests that we call the stateful update script with the correct args.
        """
        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
                                 '_get_stateful_update_script')
        update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
                      'R28-4444.0.0-b2996')
        static_update_url = ('http://172.22.50.205:8082/static/'
                             'lumpy-chrome-perf/R28-4444.0.0-b2996')
        update_script = '/usr/local/bin/stateful_update'

        # Test with clobber=False.
        autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
                update_script)
        autoupdater.ChromiumOSUpdater._run(
                mox.And(
                        mox.StrContains(update_script),
                        mox.StrContains(static_update_url),
                        mox.Not(mox.StrContains('--stateful_change=clean'))),
                timeout=mox.IgnoreArg())

        self.mox.ReplayAll()
        updater = autoupdater.ChromiumOSUpdater(update_url)
        updater.update_stateful(clobber=False)
        self.mox.VerifyAll()

        # Test with clobber=True.
        self.mox.ResetAll()
        autoupdater.ChromiumOSUpdater._get_stateful_update_script().AndReturn(
                update_script)
        autoupdater.ChromiumOSUpdater._run(
                mox.And(
                        mox.StrContains(update_script),
                        mox.StrContains(static_update_url),
                        mox.StrContains('--stateful_change=clean')),
                timeout=mox.IgnoreArg())
        self.mox.ReplayAll()
        updater = autoupdater.ChromiumOSUpdater(update_url)
        updater.update_stateful(clobber=True)
        self.mox.VerifyAll()

    def testGetRemoteScript(self):
        """Test _get_remote_script() behaviors."""
        update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
                      'R28-4444.0.0-b2996')
        script_name = 'fubar'
        local_script = '/usr/local/bin/%s' % script_name
        host = self.mox.CreateMockAnything()
        updater = autoupdater.ChromiumOSUpdater(update_url, host=host)
        host.path_exists(local_script).AndReturn(True)

        self.mox.ReplayAll()
        # Simple case:  file exists on DUT
        self.assertEqual(updater._get_remote_script(script_name),
                         local_script)
        self.mox.VerifyAll()

        self.mox.ResetAll()
        fake_shell = '/bin/ash'
        tmp_script = '/tmp/%s' % script_name
        fake_result = self.mox.CreateMockAnything()
        fake_result.stdout = ' %s\n' % fake_shell
        host.path_exists(local_script).AndReturn(False)
        host.run(mox.IgnoreArg(),
                 ignore_status=True).AndReturn(fake_result)

        self.mox.ReplayAll()
        # Complicated case:  script not on DUT, so try to download it.
        self.assertEqual(
                updater._get_remote_script(script_name),
                '%s %s' % (fake_shell, tmp_script))
        self.mox.VerifyAll()

    def testRollbackRootfs(self):
        """Tests that we correctly rollback the rootfs when requested."""
        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')
        self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater,
                                 '_verify_update_completed')
        host = self.mox.CreateMockAnything()
        update_url = 'http://server/test/url'
        host.hostname = 'test_host'

        can_rollback_cmd = ('/usr/bin/update_engine_client --can_rollback')
        rollback_cmd = ('/usr/bin/update_engine_client --rollback '
                        '--follow')

        updater = autoupdater.ChromiumOSUpdater(update_url, host=host)

        # Return an old build which shouldn't call can_rollback.
        updater.host.get_release_version().AndReturn('1234.0.0')
        autoupdater.ChromiumOSUpdater._run(rollback_cmd)
        autoupdater.ChromiumOSUpdater._verify_update_completed()

        self.mox.ReplayAll()
        updater.rollback_rootfs(powerwash=True)
        self.mox.VerifyAll()

        self.mox.ResetAll()
        cmd_result_1 = self.mox.CreateMockAnything()
        cmd_result_1.exit_status = 1

        # Rollback but can_rollback says we can't -- return an error.
        updater.host.get_release_version().AndReturn('5775.0.0')
        autoupdater.ChromiumOSUpdater._run(can_rollback_cmd).AndRaise(
                error.AutoservRunError('can_rollback failed', cmd_result_1))
        self.mox.ReplayAll()
        self.assertRaises(autoupdater.RootFSUpdateError,
                          updater.rollback_rootfs, True)
        self.mox.VerifyAll()

        self.mox.ResetAll()
        # Rollback >= version blacklisted.
        updater.host.get_release_version().AndReturn('5775.0.0')
        autoupdater.ChromiumOSUpdater._run(can_rollback_cmd)
        autoupdater.ChromiumOSUpdater._run(rollback_cmd)
        autoupdater.ChromiumOSUpdater._verify_update_completed()
        self.mox.ReplayAll()
        updater.rollback_rootfs(powerwash=True)
        self.mox.VerifyAll()


if __name__ == '__main__':
  unittest.main()