← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rvb/maas/long-lasting-warnings into lp:maas

 

Raphaël Badin has proposed merging lp:~rvb/maas/long-lasting-warnings into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~rvb/maas/long-lasting-warnings/+merge/101126

This branch adds the basic machinery that we will use to report, discard and display component errors.  The errors string are really just a first draft at this stage.

This branch is the first in 3 branches:
- this branch creates the backend utilities to report, discard and display component errors.
- a ui branch will use the backend utilities to display the errors.
- a third branch will connect the dots and apply the decorator introduced in this branch where appropriate.  This branch will also polish the error messages.
-- 
https://code.launchpad.net/~rvb/maas/long-lasting-warnings/+merge/101126
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/long-lasting-warnings into lp:maas.
=== added file 'src/maasserver/components.py'
--- src/maasserver/components.py	1970-01-01 00:00:00 +0000
+++ src/maasserver/components.py	2012-04-06 16:06:23 +0000
@@ -0,0 +1,81 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""MAAS components management."""
+
+from __future__ import (
+    print_function,
+    unicode_literals,
+    )
+
+__metaclass__ = type
+__all__ = [
+    "discard_persistent_error",
+    "get_persistent_errors",
+    "PERSISTENT_COMPONENTS_ERRORS",
+    "persistent_error_sensor",
+    "register_persistent_error",
+    ]
+
+from collections import Sequence
+import threading
+
+
+PERSISTENT_COMPONENTS_ERRORS = {
+    'provisioning_server_error': """
+        The provisioning server is failing.
+        """,
+    'cobbler_server_error': """
+        Cobbler is failing.
+        """,
+    'maas-import-isos_error': """
+        The maas-import-isos script appears not to have been run.
+        """,
+}
+
+# Persistent errors are global to a MAAS instance.
+_PERSISTENT_ERRORS = set()
+
+
+_PERSISTENT_ERRORS_LOCK = threading.Lock()
+
+
+def register_persistent_error(error_code):
+    with _PERSISTENT_ERRORS_LOCK:
+        global _PERSISTENT_ERRORS
+        _PERSISTENT_ERRORS.add(error_code)
+
+
+def discard_persistent_error(error_code):
+    with _PERSISTENT_ERRORS_LOCK:
+        global _PERSISTENT_ERRORS
+        _PERSISTENT_ERRORS.discard(error_code)
+
+
+def get_persistent_errors():
+    for error_code in _PERSISTENT_ERRORS:
+        yield PERSISTENT_COMPONENTS_ERRORS[error_code]
+
+
+def persistent_error_sensor(exceptions, error_code):
+    """A method decorator used to report if the decorated method ran
+    successfully or raised an exception.  In case of success,
+    the permanent error corresponding to error_code will be discarded if it
+    was previously registered; if one of the exceptions in 'exceptions' is
+    raised, the permanent error corresponding to error_code will be
+    registered.
+    """
+    if not isinstance(exceptions, Sequence):
+        exceptions = (exceptions, )
+
+    def wrapper(func):
+        def _wrapper(*args, **kwargs):
+            try:
+                res = func(*args, **kwargs)
+                discard_persistent_error(error_code)
+                return res
+            except exceptions:
+                register_persistent_error(error_code)
+                raise
+        return _wrapper
+    return wrapper

=== added file 'src/maasserver/tests/test_components.py'
--- src/maasserver/tests/test_components.py	1970-01-01 00:00:00 +0000
+++ src/maasserver/tests/test_components.py	2012-04-06 16:06:23 +0000
@@ -0,0 +1,113 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test maasserver components module."""
+
+from __future__ import (
+    print_function,
+    unicode_literals,
+    )
+
+__metaclass__ = type
+__all__ = []
+
+
+import random
+
+from maasserver import components
+from maasserver.components import (
+    discard_persistent_error,
+    get_persistent_errors,
+    PERSISTENT_COMPONENTS_ERRORS,
+    persistent_error_sensor,
+    register_persistent_error,
+    )
+from maasserver.testing.testcase import TestCase
+
+
+def get_random_error():
+    return random.choice(PERSISTENT_COMPONENTS_ERRORS.keys())
+
+
+class PersistentErrorsUtilitiesTest(TestCase):
+
+    def setUp(self):
+        super(PersistentErrorsUtilitiesTest, self).setUp()
+        self._PERSISTENT_ERRORS = set()
+        self.patch(components, '_PERSISTENT_ERRORS', self._PERSISTENT_ERRORS)
+
+    def test_register_persistent_error_registers_error(self):
+        error = get_random_error()
+        register_persistent_error(error)
+        self.assertItemsEqual([error], self._PERSISTENT_ERRORS)
+
+    def test_register_persistent_error_does_not_register_error_twice(self):
+        error = get_random_error()
+        register_persistent_error(error)
+        register_persistent_error(error)
+        self.assertItemsEqual([error], self._PERSISTENT_ERRORS)
+
+    def test_discard_persistent_error_discards_error(self):
+        error = get_random_error()
+        register_persistent_error(error)
+        discard_persistent_error(error)
+        self.assertItemsEqual([], self._PERSISTENT_ERRORS)
+
+    def test_discard_persistent_error_can_be_called_many_times(self):
+        error = get_random_error()
+        register_persistent_error(error)
+        discard_persistent_error(error)
+        discard_persistent_error(error)
+        self.assertItemsEqual([], self._PERSISTENT_ERRORS)
+
+    def get_persistent_errors_returns_text_for_error_codes(self):
+        errors = PERSISTENT_COMPONENTS_ERRORS.keys()
+        for error in errors:
+            register_persistent_error(error)
+        error_messages = get_persistent_errors()
+        self.assertEqual(len(errors), len(error_messages))
+        self.assertItemsEqual(
+            [unicode] * len(errors),
+            [type(error_message) for error_message in error_messages])
+
+    def test_error_sensor_registers_error_if_exception_raised(self):
+        error = get_random_error()
+
+        @persistent_error_sensor(NotImplementedError, error)
+        def test_method():
+            raise NotImplementedError
+
+        self.assertRaises(NotImplementedError, test_method)
+        self.assertItemsEqual([error], self._PERSISTENT_ERRORS)
+
+    def test_error_sensor_registers_does_not_register_unknown_error(self):
+        error = get_random_error()
+
+        @persistent_error_sensor(NotImplementedError, error)
+        def test_method():
+            raise ValueError
+
+        self.assertRaises(ValueError, test_method)
+        self.assertItemsEqual([], self._PERSISTENT_ERRORS)
+
+    def test_error_sensor_discards_error_if_method_runs_successfully(self):
+        error = get_random_error()
+        register_persistent_error(error)
+
+        @persistent_error_sensor(NotImplementedError, error)
+        def test_method():
+            pass
+
+        test_method()
+        self.assertItemsEqual([], self._PERSISTENT_ERRORS)
+
+    def test_error_sensor_does_not_discard_error_if_unknown_exception(self):
+        error = get_random_error()
+        register_persistent_error(error)
+
+        @persistent_error_sensor(ValueError, error)
+        def test_method():
+            raise NotImplementedError
+
+        self.assertRaises(NotImplementedError, test_method)
+        self.assertItemsEqual([error], self._PERSISTENT_ERRORS)


Follow ups