# 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. """Smaug base exception handling. Includes decorator for re-raising Smaug-type exceptions. SHOULD include dedicated exception logging. """ import sys from oslo_config import cfg from oslo_log import log as logging from oslo_versionedobjects import exception as obj_exc import six import webob.exc from webob.util import status_generic_reasons from webob.util import status_reasons from smaug.i18n import _, _LE LOG = logging.getLogger(__name__) exc_log_opts = [ cfg.BoolOpt('fatal_exception_format_errors', default=False, help='Make exception message format errors fatal.'), ] CONF = cfg.CONF CONF.register_opts(exc_log_opts) class ConvertedException(webob.exc.WSGIHTTPException): def __init__(self, code=500, title="", explanation=""): self.code = code # There is a strict rule about constructing status line for HTTP: # '...Status-Line, consisting of the protocol version followed by a # numeric status code and its associated textual phrase, with each # element separated by SP characters' # (http://www.faqs.org/rfcs/rfc2616.html) # 'code' and 'title' can not be empty because they correspond # to numeric status code and its associated text if title: self.title = title else: try: self.title = status_reasons[self.code] except KeyError: generic_code = self.code // 100 self.title = status_generic_reasons[generic_code] self.explanation = explanation super(ConvertedException, self).__init__() class Error(Exception): pass class SmaugException(Exception): """Base Smaug Exception To correctly use this class, inherit from it and define a 'message' property. That message will get printf'd with the keyword arguments provided to the constructor. """ message = _("An unknown exception occurred.") code = 500 headers = {} safe = False def __init__(self, message=None, **kwargs): """Initiate the instance of SmaugException There are two ways to initiate the instance. 1. Specify the value of 'message' and leave the 'kwargs' None. 2. Leave 'message' None, and specify the keyword arguments matched with the format of SmaugException.message. Especially, can't use the 'message' as the key in the 'kwargs', otherwise, the first argument('message') will be set. Note: This class doesn't support to create instance of SmaugException with another instance. """ self.kwargs = kwargs if 'code' not in self.kwargs: try: self.kwargs['code'] = self.code except AttributeError: pass if not message: try: message = self.message % kwargs except Exception: exc_info = sys.exc_info() # kwargs doesn't match a variable in the message # log the issue and the kwargs LOG.exception(_LE('Exception in string format operation')) for name, value in kwargs.items(): LOG.error(_LE("%(name)s: %(value)s"), {'name': name, 'value': value}) if CONF.fatal_exception_format_errors: six.reraise(*exc_info) # at least get the core message out if something happened message = self.message elif isinstance(message, Exception): message = six.text_type(message) # NOTE(luisg): We put the actual message in 'msg' so that we can access # it, because if we try to access the message via 'message' it will be # overshadowed by the class' message attribute self.msg = message super(SmaugException, self).__init__(message) def __unicode__(self): return six.text_type(self.msg) class NotAuthorized(SmaugException): message = _("Not authorized.") code = 403 class AdminRequired(NotAuthorized): message = _("User does not have admin privileges") class PolicyNotAuthorized(NotAuthorized): message = _("Policy doesn't allow %(action)s to be performed.") class Invalid(SmaugException): message = _("Unacceptable parameters.") code = 400 class InvalidParameterValue(Invalid): message = _("%(err)s") class InvalidInput(Invalid): message = _("Invalid input received: %(reason)s") class NotFound(SmaugException): message = _("Resource could not be found.") code = 404 safe = True class ConfigNotFound(NotFound): message = _("Could not find config at %(path)s") class MalformedRequestBody(SmaugException): message = _("Malformed message body: %(reason)s") class InvalidContentType(Invalid): message = _("Invalid content type %(content_type)s.") class InvalidProtectableInstance(Invalid): message = _("Invalid protectable instance %(protectable_id)s.") class PasteAppNotFound(NotFound): message = _("Could not load paste app '%(name)s' from %(path)s") class ServiceNotFound(NotFound): message = _("Service %(service_id)s could not be found.") class HostBinaryNotFound(NotFound): message = _("Could not find binary %(binary)s on host %(host)s.") class TriggerNotFound(NotFound): message = _("Trigger %(id)s could not be found.") class ScheduledOperationNotFound(NotFound): message = _("Scheduled Operation %(id)s could not be found.") class ScheduledOperationStateNotFound(NotFound): message = _("Scheduled Operation State %(op_id)s could not be found.") class ScheduledOperationLogNotFound(NotFound): message = _("Scheduled Operation Log %(log_id)s could not be found.") class ListProtectableResourceFailed(SmaugException): message = _("List protectable resources of type %(type)s failed: " "%(reason)s") class InvalidOperationObject(Invalid): message = _("The operation %(operation_id)s is invalid") class ClassNotFound(NotFound): message = _("Class %(class_name)s could not be found: %(exception)s") class InvalidOperationDefinition(Invalid): message = _("Invalid operation definition, reason:%(reason)s") OrphanedObjectError = obj_exc.OrphanedObjectError ObjectActionError = obj_exc.ObjectActionError class PlanNotFound(NotFound): message = _("Plan %(plan_id)s could not be found.") class RestoreNotFound(NotFound): message = _("Restore %(restore_id)s could not be found.") class InvalidPlan(Invalid): message = _("Invalid plan: %(reason)s") class ProtectableTypeNotFound(NotFound): message = _("ProtectableType %(protectable_type)s could" " not be found.") class ProviderNotFound(NotFound): message = _("Provider %(provider_id)s could" " not be found.") class CheckpointNotFound(NotFound): message = _("Checkpoint %(checkpoint_id)s could" " not be found.") class BankCreateObjectFailed(SmaugException): message = _("Create Object in Bank Failed: %(reason)s") class BankUpdateObjectFailed(SmaugException): message = _("Update Object %(key)s in Bank Failed: %(reason)s") class BankDeleteObjectFailed(SmaugException): message = _("Delete Object %(key)s in Bank Failed: %(reason)s") class BankGetObjectFailed(SmaugException): message = _("Get Object %(key)s in Bank Failed: %(reason)s") class BankListObjectsFailed(SmaugException): message = _("Get Object in Bank Failed: %(reason)s") class AcquireLeaseFailed(SmaugException): message = _("Acquire Lease in Failed: %(reason)s") class CreateContainerFailed(SmaugException): message = _("Create Container in Bank Failed: %(reason)s")