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