File: exceptions.py

package info (click to toggle)
python-sushy 5.5.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,620 kB
  • sloc: python: 14,026; makefile: 24; sh: 2
file content (188 lines) | stat: -rw-r--r-- 6,196 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# Copyright 2017 Red Hat, Inc.
# 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.

from http import client as http_client
import logging


LOG = logging.getLogger(__name__)


class SushyError(Exception):
    """Basic exception for errors raised by Sushy"""

    message = None

    def __init__(self, message=None, **kwargs):
        if message is not None:
            self.message = message
        if self.message and kwargs:
            self.message = self.message % kwargs

        super().__init__(self.message)


class ConnectionError(SushyError):
    message = 'Unable to connect to %(url)s. Error: %(error)s'


class MissingAttributeError(SushyError):
    message = ('The attribute %(attribute)s is missing from the '
               'resource %(resource)s')


class MalformedAttributeError(SushyError):
    message = ('The attribute %(attribute)s is malformed in the '
               'resource %(resource)s: %(error)s')


class MissingActionError(SushyError):
    message = ('The action %(action)s is missing from the '
               'resource %(resource)s')


class InvalidParameterValueError(SushyError):
    message = ('The parameter "%(parameter)s" value "%(value)s" is invalid. '
               'Valid values are: %(valid_values)s')


class ArchiveParsingError(SushyError):
    message = 'Failed parsing archive "%(path)s": %(error)s'


class UnknownDefaultError(SushyError):
    message = 'Failed at determining default for "%(entity)s": %(error)s'


class ExtensionError(SushyError):
    message = ('Sushy Extension Error: %(error)s')


class OEMExtensionNotFoundError(SushyError):
    message = 'No %(resource)s OEM extension found by name "%(name)s".'


class MissingHeaderError(SushyError):
    message = 'Response to %(target_uri)s did not contain a %(header)s header'


class HTTPError(SushyError):
    """Basic exception for HTTP errors"""

    status_code = None
    """HTTP status code."""

    body = None
    """Error JSON body, if present."""

    code = 'Base.1.0.GeneralError'
    """Error code defined in the Redfish specification, if present."""

    detail = None
    """Error message defined in the Redfish specification, if present."""

    message = ('HTTP %(method)s %(url)s returned code %(code)s. %(error)s '
               'Extended information: %(ext_info)s')

    extended_info = None
    """Extended information provided in the response."""

    def __init__(self, method, url, response):
        self.status_code = response.status_code
        try:
            body = response.json()
        except ValueError:
            LOG.warning('Error response from %(method)s %(url)s '
                        'with status code %(code)s has no JSON body',
                        {'method': method, 'url': url, 'code':
                         self.status_code})
            error = 'unknown error'
        else:
            self.body = body.get('error', {})
            self.code = self.body.get('code', 'Base.1.0.GeneralError')
            self.detail = self.body.get('message')
            self.extended_info = self.body.get('@Message.ExtendedInfo')
            message = self._get_most_severe_msg(self.extended_info or [{}])
            self.detail = message or self.detail
            error = '{}: {}'.format(self.code, self.detail or 'unknown error.')
        kwargs = {'method': method, 'url': url, 'code': self.status_code,
                  'error': error, 'ext_info': self.extended_info}
        LOG.debug('HTTP response for %(method)s %(url)s: '
                  'status code: %(code)s, error: %(error)s, '
                  'extended: %(ext_info)s', kwargs)
        super().__init__(**kwargs)

    @staticmethod
    def _get_most_severe_msg(extended_info):
        if not isinstance(extended_info, list):
            return extended_info.get('Message', None)
        if len(extended_info) > 0:
            for sev in ['Critical', 'Warning']:
                for i, m in enumerate(extended_info):
                    if m.get('Severity') == sev:
                        return m.get('Message')

    @property
    def related_properties(self):
        """List of properties related to the error."""
        try:
            return self.extended_info[0]['RelatedProperties']
        except (IndexError, KeyError, TypeError):
            return []


class BadRequestError(HTTPError):
    pass


class ResourceNotFoundError(HTTPError):
    # Overwrite the complex generic message with a simpler one.
    message = 'Resource %(url)s not found'


class ServerSideError(HTTPError):
    pass


class AccessError(HTTPError):
    pass


class NotAcceptableError(HTTPError):
    pass


class MissingXAuthToken(HTTPError):
    message = ('No X-Auth-Token returned from remote host when '
               'attempting to establish a session. Error: %(error)s')


def raise_for_response(method, url, response):
    """Raise a correct error class, if needed."""
    if response.status_code < http_client.BAD_REQUEST:
        return
    elif response.status_code == http_client.NOT_FOUND:
        raise ResourceNotFoundError(method, url, response)
    elif response.status_code == http_client.BAD_REQUEST:
        raise BadRequestError(method, url, response)
    elif response.status_code in (http_client.UNAUTHORIZED,
                                  http_client.FORBIDDEN):
        raise AccessError(method, url, response)
    elif response.status_code == http_client.NOT_ACCEPTABLE:
        raise NotAcceptableError(method, url, response)
    elif response.status_code >= http_client.INTERNAL_SERVER_ERROR:
        raise ServerSideError(method, url, response)
    else:
        raise HTTPError(method, url, response)