File: http.py

package info (click to toggle)
python-osc-placement 4.6.0-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 640 kB
  • sloc: python: 4,039; makefile: 25; sh: 2
file content (86 lines) | stat: -rw-r--r-- 3,127 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
# 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.

import contextlib
import json
import logging

import keystoneauth1.exceptions.http as ks_exceptions
import osc_lib.exceptions as exceptions

from osc_placement import version


_http_error_to_exc = {
    cls.http_status: cls
    for cls in exceptions.ClientException.__subclasses__()
}


LOG = logging.getLogger(__name__)


@contextlib.contextmanager
def _wrap_http_exceptions():
    """Reraise osc-lib exceptions with detailed messages."""

    try:
        yield
    except ks_exceptions.HttpError as exc:
        if 400 <= exc.http_status < 500:
            detail = json.loads(exc.response.content)['errors'][0]['detail']
            msg = detail.split('\n')[-1].strip()
            exc_class = _http_error_to_exc.get(exc.http_status,
                                               exceptions.CommandError)
            raise exc_class(exc.http_status, msg) from exc
        else:
            raise


class SessionClient(object):
    def __init__(self, session, ks_filter, api_version='1.0'):
        self.session = session
        self.ks_filter = ks_filter
        self.negotiate_api_version(api_version)

    def request(self, method, url, **kwargs):
        version = kwargs.pop('version', None)
        api_version = (self.ks_filter['service_type'] + ' '
                       + (version or self.api_version))
        headers = kwargs.pop('headers', {})
        headers.setdefault('OpenStack-API-Version', api_version)
        headers.setdefault('Accept', 'application/json')

        with _wrap_http_exceptions():
            return self.session.request(url, method,
                                        headers=headers,
                                        endpoint_filter=self.ks_filter,
                                        **kwargs)

    def negotiate_api_version(self, api_version):
        """Set api_version to self.

        If negotiate version (only majorversion) is given, talk to server to
        pick up max microversion supported both by client and by server.
        """
        if api_version not in version.NEGOTIATE_VERSIONS:
            self.api_version = api_version
            return
        client_ver = version.MAX_VERSION_NO_GAP
        self.api_version = client_ver
        resp = self.request('GET', '/', raise_exc=False)
        if resp.status_code == 406:
            server_ver = resp.json()['errors'][0]['max_version']
            self.api_version = server_ver
            LOG.debug('Microversion %s not supported in server. '
                      'Falling back to microversion %s',
                      client_ver, server_ver)