diff --git a/mistral/api/controllers/v1/execution.py b/mistral/api/controllers/v1/execution.py index 2f09f9fc2..d6f998297 100644 --- a/mistral/api/controllers/v1/execution.py +++ b/mistral/api/controllers/v1/execution.py @@ -17,6 +17,7 @@ from pecan import abort from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from mistral import exceptions as ex from mistral.api.controllers.v1 import task from mistral.openstack.common import log as logging from mistral.api.controllers import resource @@ -73,9 +74,12 @@ class ExecutionsController(rest.RestController): def post(self, workbook_name, execution): LOG.debug("Create listener [workbook_name=%s, execution=%s]" % (workbook_name, execution)) - - values = engine.start_workflow_execution(execution.workbook_name, - execution.target_task) + try: + values = engine.start_workflow_execution(execution.workbook_name, + execution.target_task) + except ex.MistralException as e: + #TODO(nmakhotkin) we should use thing such a decorator here + abort(400, e.message) return Execution.from_dict(values) diff --git a/mistral/api/controllers/v1/workbook.py b/mistral/api/controllers/v1/workbook.py index 5ecfdc8c1..c7ac6f87e 100644 --- a/mistral/api/controllers/v1/workbook.py +++ b/mistral/api/controllers/v1/workbook.py @@ -19,6 +19,7 @@ from pecan import abort from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from mistral import exceptions as ex from mistral.api.controllers.v1 import workbook_definition from mistral.api.controllers.v1 import listener from mistral.api.controllers.v1 import execution @@ -71,9 +72,12 @@ class WorkbooksController(rest.RestController): @wsme_pecan.wsexpose(Workbook, body=Workbook, status_code=201) def post(self, workbook): LOG.debug("Create workbook [workbook=%s]" % workbook) - - wb = workbooks.create_workbook(workbook.to_dict()) - return Workbook.from_dict(wb) + try: + wb = workbooks.create_workbook(workbook.to_dict()) + return Workbook.from_dict(wb) + except ex.MistralException as e: + #TODO(nmakhotkin) we should use thing such a decorator here + abort(400, e.message) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204) def delete(self, name): diff --git a/mistral/db/sqlalchemy/api.py b/mistral/db/sqlalchemy/api.py index edfd720e1..e8e1fdc4f 100644 --- a/mistral/db/sqlalchemy/api.py +++ b/mistral/db/sqlalchemy/api.py @@ -202,8 +202,8 @@ def event_create(values, session=None): event.save(session) except db_exc.DBDuplicateEntry as e: LOG.exception("Database registration exception: %s", e) - ##TODO(akuznetsov) create special exception for this case - raise Exception + raise exc.DBDuplicateEntry("Duplicate entry for Event: %s" + % e.columns) return event @@ -263,8 +263,8 @@ def workbook_create(values, session=None): workbook.save(session=session) except db_exc.DBDuplicateEntry as e: LOG.exception("Database registration exception: %s", e) - ##TODO(akuznetsov) create special exception for this case - raise Exception + raise exc.DBDuplicateEntry("Duplicate entry for Workbook: %s" + % e.columns) return workbook @@ -326,8 +326,8 @@ def execution_create(workbook_name, values, session=None): execution.save(session=session) except db_exc.DBDuplicateEntry as e: LOG.exception("Database registration exception: %s", e) - ##TODO(akuznetsov) create special exception for this case - raise Exception + raise exc.DBDuplicateEntry("Duplicate entry for Execution: %s" + % e.columns) return execution @@ -394,8 +394,8 @@ def task_create(workbook_name, execution_id, values, session=None): task.save(session=session) except db_exc.DBDuplicateEntry as e: LOG.exception("Database registration exception: %s", e) - ##TODO(akuznetsov) create special exception for this case - raise Exception + raise exc.DBDuplicateEntry("Duplicate entry for Task: %s" + % e.columns) return task diff --git a/mistral/exceptions.py b/mistral/exceptions.py index f43a51495..0ae06adcb 100644 --- a/mistral/exceptions.py +++ b/mistral/exceptions.py @@ -14,12 +14,42 @@ # See the License for the specific language governing permissions and # limitations under the License. +import mistral.openstack.common.exception as ex -class DataAccessException(Exception): + +class MistralException(ex.Error): + """Base Exception for the project + + To correctly use this class, inherit from it and define + a 'message' and 'code' properties. + """ + message = "An unknown exception occurred" + code = "UNKNOWN_EXCEPTION" + + def __str__(self): + return self.message + + def __init__(self, message): + super(MistralException, self).__init__( + '%s: %s' % (self.code, self.message)) + + +class DataAccessException(MistralException): def __init__(self, message=None): - super(Exception, self).__init__(message) + if message: + self.message = message -class InvalidActionException(Exception): +class InvalidActionException(MistralException): def __init__(self, message=None): - super(Exception, self).__init__(message) + if message: + self.message = message + + +class DBDuplicateEntry(MistralException): + message = "Database object already exists" + code = "DB_DUPLICATE_ENTRY" + + def __init__(self, message=None): + if message: + self.message = message diff --git a/mistral/openstack/common/exception.py b/mistral/openstack/common/exception.py new file mode 100644 index 000000000..7c773f9e6 --- /dev/null +++ b/mistral/openstack/common/exception.py @@ -0,0 +1,139 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Exceptions common to OpenStack projects +""" + +import logging + +from mistral.openstack.common.gettextutils import _ # noqa + +_FATAL_EXCEPTION_FORMAT_ERRORS = False + + +class Error(Exception): + def __init__(self, message=None): + super(Error, self).__init__(message) + + +class ApiError(Error): + def __init__(self, message='Unknown', code='Unknown'): + self.api_message = message + self.code = code + super(ApiError, self).__init__('%s: %s' % (code, message)) + + +class NotFound(Error): + pass + + +class UnknownScheme(Error): + + msg_fmt = "Unknown scheme '%s' found in URI" + + def __init__(self, scheme): + msg = self.msg_fmt % scheme + super(UnknownScheme, self).__init__(msg) + + +class BadStoreUri(Error): + + msg_fmt = "The Store URI %s was malformed. Reason: %s" + + def __init__(self, uri, reason): + msg = self.msg_fmt % (uri, reason) + super(BadStoreUri, self).__init__(msg) + + +class Duplicate(Error): + pass + + +class NotAuthorized(Error): + pass + + +class NotEmpty(Error): + pass + + +class Invalid(Error): + pass + + +class BadInputError(Exception): + """Error resulting from a client sending bad input to a server""" + pass + + +class MissingArgumentError(Error): + pass + + +class DatabaseMigrationError(Error): + pass + + +class ClientConnectionError(Exception): + """Error resulting from a client connecting to a server""" + pass + + +def wrap_exception(f): + def _wrap(*args, **kw): + try: + return f(*args, **kw) + except Exception as e: + if not isinstance(e, Error): + logging.exception(_('Uncaught exception')) + raise Error(str(e)) + raise + _wrap.func_name = f.func_name + return _wrap + + +class OpenstackException(Exception): + """Base Exception class. + + To correctly use this class, inherit from it and define + a 'msg_fmt' property. That message will get printf'd + with the keyword arguments provided to the constructor. + """ + msg_fmt = "An unknown exception occurred" + + def __init__(self, **kwargs): + try: + self._error_string = self.msg_fmt % kwargs + + except Exception: + if _FATAL_EXCEPTION_FORMAT_ERRORS: + raise + else: + # at least get the core message out if something happened + self._error_string = self.msg_fmt + + def __str__(self): + return self._error_string + + +class MalformedRequestBody(OpenstackException): + msg_fmt = "Malformed message body: %(reason)s" + + +class InvalidContentType(OpenstackException): + msg_fmt = "Invalid content type %(content_type)s"