1# (c) Copyright 2013 Hewlett-Packard Development Company, L.P. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14 15"""Exceptions for the Brick library.""" 16 17import traceback 18 19from oslo_concurrency import processutils as putils 20from oslo_log import log as logging 21 22from os_brick.i18n import _ 23 24 25LOG = logging.getLogger(__name__) 26 27 28class BrickException(Exception): 29 """Base Brick Exception 30 31 To correctly use this class, inherit from it and define 32 a 'message' property. That message will get printf'd 33 with the keyword arguments provided to the constructor. 34 """ 35 message = _("An unknown exception occurred.") 36 code = 500 37 headers = {} 38 safe = False 39 40 def __init__(self, message=None, **kwargs): 41 self.kwargs = kwargs 42 43 if 'code' not in self.kwargs: 44 try: 45 self.kwargs['code'] = self.code 46 except AttributeError: 47 pass 48 49 if not message: 50 try: 51 message = self.message % kwargs 52 53 except Exception: 54 # kwargs doesn't match a variable in the message 55 # log the issue and the kwargs 56 LOG.exception("Exception in string format operation. " 57 "msg='%s'", self.message) 58 for name, value in kwargs.items(): 59 LOG.error("%(name)s: %(value)s", {'name': name, 60 'value': value}) 61 62 # at least get the core message out if something happened 63 message = self.message 64 65 # Put the message in 'msg' so that we can access it. If we have it in 66 # message it will be overshadowed by the class' message attribute 67 self.msg = message 68 super(BrickException, self).__init__(message) 69 70 71class NotFound(BrickException): 72 message = _("Resource could not be found.") 73 code = 404 74 safe = True 75 76 77class Invalid(BrickException): 78 message = _("Unacceptable parameters.") 79 code = 400 80 81 82# Cannot be templated as the error syntax varies. 83# msg needs to be constructed when raised. 84class InvalidParameterValue(Invalid): 85 message = _("%(err)s") 86 87 88class NoFibreChannelHostsFound(BrickException): 89 message = _("We are unable to locate any Fibre Channel devices.") 90 91 92class NoFibreChannelVolumeDeviceFound(BrickException): 93 message = _("Unable to find a Fibre Channel volume device.") 94 95 96class VolumeNotDeactivated(BrickException): 97 message = _('Volume %(name)s was not deactivated in time.') 98 99 100class VolumeDeviceNotFound(BrickException): 101 message = _("Volume device not found at %(device)s.") 102 103 104class VolumePathsNotFound(BrickException): 105 message = _("Could not find any paths for the volume.") 106 107 108class VolumePathNotRemoved(BrickException): 109 message = _("Volume path %(volume_path)s was not removed in time.") 110 111 112class ProtocolNotSupported(BrickException): 113 message = _("Connect to volume via protocol %(protocol)s not supported.") 114 115 116class TargetPortalNotFound(BrickException): 117 message = _("Unable to find target portal %(target_portal)s.") 118 119 120class TargetPortalsNotFound(TargetPortalNotFound): 121 message = _("Unable to find target portal in %(target_portals)s.") 122 123 124class FailedISCSITargetPortalLogin(BrickException): 125 message = _("Unable to login to iSCSI Target Portal") 126 127 128class BlockDeviceReadOnly(BrickException): 129 message = _("Block device %(device)s is Read-Only.") 130 131 132class VolumeGroupNotFound(BrickException): 133 message = _("Unable to find Volume Group: %(vg_name)s") 134 135 136class VolumeGroupCreationFailed(BrickException): 137 message = _("Failed to create Volume Group: %(vg_name)s") 138 139 140class CommandExecutionFailed(BrickException): 141 message = _("Failed to execute command %(cmd)s") 142 143 144class VolumeDriverException(BrickException): 145 message = _('An error occurred while IO to volume %(name)s.') 146 147 148class InvalidIOHandleObject(BrickException): 149 message = _('IO handle of %(protocol)s has wrong object ' 150 'type %(actual_type)s.') 151 152 153class VolumeEncryptionNotSupported(Invalid): 154 message = _("Volume encryption is not supported for %(volume_type)s " 155 "volume %(volume_id)s.") 156 157 158class VolumeLocalCacheNotSupported(Invalid): 159 message = _("Volume local cache is not supported for %(volume_type)s " 160 "volume %(volume_id)s.") 161 162 163# NOTE(mriedem): This extends ValueError to maintain backward compatibility. 164class InvalidConnectorProtocol(ValueError): 165 pass 166 167 168class ExceptionChainer(BrickException): 169 """A Exception that can contain a group of exceptions. 170 171 This exception serves as a container for exceptions, useful when we want to 172 store all exceptions that happened during a series of steps and then raise 173 them all together as one. 174 175 The representation of the exception will include all exceptions and their 176 tracebacks. 177 178 This class also includes a context manager for convenience, one that will 179 support both swallowing the exception as if nothing had happened and 180 raising the exception. In both cases the exception will be stored. 181 182 If a message is provided to the context manager it will be formatted and 183 logged with warning level. 184 """ 185 def __init__(self, *args, **kwargs): 186 self._exceptions = [] 187 self._repr = None 188 super(ExceptionChainer, self).__init__(*args, **kwargs) 189 190 def __repr__(self): 191 # Since generating the representation can be slow we cache it 192 if not self._repr: 193 tracebacks = ( 194 ''.join(traceback.format_exception(*e)).replace('\n', '\n\t') 195 for e in self._exceptions) 196 self._repr = '\n'.join('\nChained Exception #%s\n\t%s' % (i + 1, t) 197 for i, t in enumerate(tracebacks)) 198 return self._repr 199 200 __str__ = __repr__ 201 202 def __nonzero__(self): 203 # We want to be able to do boolean checks on the exception 204 return bool(self._exceptions) 205 206 __bool__ = __nonzero__ # For Python 3 207 208 def add_exception(self, exc_type, exc_val, exc_tb): 209 # Clear the representation cache 210 self._repr = None 211 self._exceptions.append((exc_type, exc_val, exc_tb)) 212 213 def context(self, catch_exception, msg='', *msg_args): 214 self._catch_exception = catch_exception 215 self._exc_msg = msg 216 self._exc_msg_args = msg_args 217 return self 218 219 def __enter__(self): 220 return self 221 222 def __exit__(self, exc_type, exc_val, exc_tb): 223 if exc_type: 224 self.add_exception(exc_type, exc_val, exc_tb) 225 if self._exc_msg: 226 LOG.warning(self._exc_msg, *self._exc_msg_args) 227 if self._catch_exception: 228 return True 229 230 231class ExecutionTimeout(putils.ProcessExecutionError): 232 pass 233