summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/services/exception.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/services/exception.py')
-rw-r--r--src/pybind/mgr/dashboard/services/exception.py122
1 files changed, 122 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/services/exception.py b/src/pybind/mgr/dashboard/services/exception.py
new file mode 100644
index 00000000..b5c0bd58
--- /dev/null
+++ b/src/pybind/mgr/dashboard/services/exception.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import json
+import sys
+from contextlib import contextmanager
+
+import cherrypy
+
+import rbd
+import rados
+
+from .. import logger
+from ..services.ceph_service import SendCommandError
+from ..exceptions import ViewCacheNoDataException, DashboardException
+from ..tools import wraps
+
+if sys.version_info < (3, 0):
+ # Monkey-patch a __call__ method into @contextmanager to make
+ # it compatible to Python 3
+
+ from contextlib import GeneratorContextManager # pylint: disable=no-name-in-module
+
+ def init(self, *args):
+ if len(args) == 1:
+ self.gen = args[0]
+ elif len(args) == 3:
+ self.func, self.args, self.kwargs = args
+ else:
+ raise TypeError()
+
+ def enter(self):
+ if hasattr(self, 'func'):
+ self.gen = self.func(*self.args, **self.kwargs)
+ try:
+ return self.gen.next()
+ except StopIteration:
+ raise RuntimeError("generator didn't yield")
+
+ def call(self, f):
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ with self:
+ return f(*args, **kwargs)
+
+ return wrapper
+
+ GeneratorContextManager.__init__ = init
+ GeneratorContextManager.__enter__ = enter
+ GeneratorContextManager.__call__ = call
+
+ # pylint: disable=function-redefined
+ def contextmanager(func):
+
+ @wraps(func)
+ def helper(*args, **kwds):
+ return GeneratorContextManager(func, args, kwds)
+
+ return helper
+
+
+def serialize_dashboard_exception(e, include_http_status=False, task=None):
+ """
+ :type e: Exception
+ :param include_http_status: Used for Tasks, where the HTTP status code is not available.
+ """
+ from ..tools import ViewCache
+ if isinstance(e, ViewCacheNoDataException):
+ return {'status': ViewCache.VALUE_NONE, 'value': None}
+
+ out = dict(detail=str(e))
+ try:
+ out['code'] = e.code
+ except AttributeError:
+ pass
+ component = getattr(e, 'component', None)
+ out['component'] = component if component else None
+ if include_http_status:
+ out['status'] = getattr(e, 'status', 500)
+ if task:
+ out['task'] = dict(name=task.name, metadata=task.metadata)
+ return out
+
+
+def dashboard_exception_handler(handler, *args, **kwargs):
+ try:
+ with handle_rados_error(component=None): # make the None controller the fallback.
+ return handler(*args, **kwargs)
+ # Don't catch cherrypy.* Exceptions.
+ except (ViewCacheNoDataException, DashboardException) as e:
+ logger.exception('dashboard_exception_handler')
+ cherrypy.response.headers['Content-Type'] = 'application/json'
+ cherrypy.response.status = getattr(e, 'status', 400)
+ return json.dumps(serialize_dashboard_exception(e)).encode('utf-8')
+
+
+@contextmanager
+def handle_rbd_error():
+ try:
+ yield
+ except rbd.OSError as e:
+ raise DashboardException(e, component='rbd')
+ except rbd.Error as e:
+ raise DashboardException(e, component='rbd', code=e.__class__.__name__)
+
+
+@contextmanager
+def handle_rados_error(component):
+ try:
+ yield
+ except rados.OSError as e:
+ raise DashboardException(e, component=component)
+ except rados.Error as e:
+ raise DashboardException(e, component=component, code=e.__class__.__name__)
+
+
+@contextmanager
+def handle_send_command_error(component):
+ try:
+ yield
+ except SendCommandError as e:
+ raise DashboardException(e, component=component)