Completed
Push — develop ( 0f74fa...fdc3c1 )
by
unknown
01:40
created

SlumberResource._process_response()   A

Complexity

Conditions 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 2
1
# -*- coding: utf-8 -*-
2
#
3
# Copyright (c) 2013-2015 Online SAS and Contributors. All Rights Reserved.
4
#                         Julien Castets <[email protected]>
5
#                         Kevin Deldycke <[email protected]>
6
#
7
# Licensed under the BSD 2-Clause License (the "License"); you may not use this
8
# file except in compliance with the License. You may obtain a copy of the
9
# License at http://opensource.org/licenses/BSD-2-Clause
10
11
from __future__ import print_function
12
13
import logging
14
import platform
15
import sys
16
import time
17
18
import requests
19
import slumber
20
21
from .. import __version__
22
23
24
logger = logging.getLogger(__name__)
25
26
27
class _CustomHTTPAdapter(requests.adapters.HTTPAdapter):
28
    """ In order to support SNI in Python 2.x, the packages pyOpenSSL, pyasn1
29
    and ndg-httpsclient need to be installed. pyOpenSSL needs the system
30
    packages gcc, python-dev, libffi-dev and libssl-dev to be installed.
31
32
    Because Python packaging sucks, you will succeed to install pyOpenSSL even
33
    if the system requirements aren't installed ; but SNI requests will fail.
34
35
    _CustomHTTPAdapter is a simple wrapper around a requests HTTPAdapter that
36
    logs an explicit message if a SSLError occurs, as there are good chances
37
    the problems comes from a bad installation.
38
    """
39
    def send(self, *args, **kwargs):  # pragma: no cover
40
        try:
41
            return super(_CustomHTTPAdapter, self).send(*args, **kwargs)
42
        except requests.exceptions.SSLError as exc:
43
            print("SSL error is raised by python-requests. This is probably "
44
                  "because the required modules to handle SNI aren't "
45
                  "installed correctly. You should probably uninstall them "
46
                  "(pip uninstall pyopenssl pyasn1 ndg-httpsclient), install "
47
                  "the system dependencies required for their installation "
48
                  "(on Ubuntu, apt-get install python-dev libffi-dev "
49
                  "libssl-dev) and resintall them (pip install pyopenssl "
50
                  "pyasn1 ndg-httpsclient).", file=sys.stderr)
51
            raise
52
53
54
class SlumberResource(slumber.Resource):
55
56
    # Maximum number of times we try to make a request against an API in
57
    # maintenance before aborting.
58
    MAX_RETRIES = 3
59
60
    def retry_in(self, retry):
61
        """ If the API returns a maintenance HTTP status code, sleep a while
62
        before retrying.
63
        """
64
        return min(2 ** retry, 30)
65
66
    def _request(self, *args, **kwargs):
67
        """ Makes a request to the Scaleway API, and wait patiently if there is
68
        an ongoing maintenance.
69
        """
70
        retry = 0
71
72
        while True:
73
            try:
74
                return super(SlumberResource, self)._request(*args, **kwargs)
75
            except slumber.exceptions.HttpServerError as exc:
76
                # Not a maintenance exception
77
                if exc.response.status_code not in (502, 503, 504):
78
                    raise
79
80
                retry += 1
81
                retry_in = self.retry_in(retry)
82
83
                if retry >= self.MAX_RETRIES:
84
                    logger.error(
85
                        'API endpoint still in maintenance after %s attempts. '
86
                        'Stop trying.' % (self.MAX_RETRIES,)
87
                    )
88
                    raise
89
90
                logger.info(
91
                    'API endpoint is currently in maintenance. Try again in '
92
                    '%s seconds... (retry %s on %s)' % (
93
                        retry_in, retry, self.MAX_RETRIES
94
                    )
95
                )
96
                time.sleep(retry_in)
97
98
    def _process_response(self, resp):
99
        if self._store.get('serialize', True) is False:
100
            return resp
101
        return super(SlumberResource, self)._process_response(resp)
102
103
104
class SlumberAPI(slumber.API):
105
106
    resource_class = SlumberResource
107
108
109
class API(object):
110
111
    base_url = None
112
    user_agent = 'scw-sdk/%s Python/%s %s' % (
113
        __version__, ' '.join(sys.version.split()), platform.platform()
114
    )
115
116
    def __init__(self, auth_token=None, base_url=None, verify_ssl=True,
117
                 user_agent=None):
118
119
        self.auth_token = auth_token
120
121
        if user_agent is not None:
122
            self.user_agent = user_agent
123
124
        if base_url:
125
            self.base_url = base_url
126
127
        self.verify_ssl = verify_ssl
128
129
    def make_requests_session(self):
130
        """ Attaches headers needed to query Scaleway APIs.
131
        """
132
        session = requests.Session()
133
134
        session.headers.update({'User-Agent': self.user_agent})
135
136
        if self.auth_token:
137
            # HTTP headers must always be ISO-8859-1 encoded
138
            session.headers.update({
139
                'X-Auth-Token': self.auth_token.encode('latin1')
140
            })
141
142
        if not self.verify_ssl:
143
            session.verify = False
144
145
        session.mount('https://', _CustomHTTPAdapter())
146
147
        return session
148
149
    def get_api_url(self):
150
        return self.base_url
151
152
    def query(self, serialize=True, **kwargs):
153
        """ Gets a configured slumber.API object.
154
        """
155
        api = SlumberAPI(
156
            self.get_api_url(),
157
            session=self.make_requests_session(),
158
            **kwargs
159
        )
160
        api._store['serialize'] = serialize
161
        return api
162
163
164
from .api_account import AccountAPI
165
from .api_compute import ComputeAPI
166
from .api_metadata import MetadataAPI
167