diff options
Diffstat (limited to 'src/pybind/mgr/dashboard/services/exception.py')
-rw-r--r-- | src/pybind/mgr/dashboard/services/exception.py | 122 |
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) |