Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / __init__.py @ 7bbaf88b

History | View | Annotate | Download (9.3 kB)

1 43ca98ee Giorgos Verigakis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 a1c50326 Giorgos Verigakis
#
3 a1c50326 Giorgos Verigakis
# Redistribution and use in source and binary forms, with or
4 a1c50326 Giorgos Verigakis
# without modification, are permitted provided that the following
5 a1c50326 Giorgos Verigakis
# conditions are met:
6 a1c50326 Giorgos Verigakis
#
7 a1c50326 Giorgos Verigakis
#   1. Redistributions of source code must retain the above
8 e742badc Stavros Sachtouris
#      copyright notice, self.list of conditions and the following
9 a1c50326 Giorgos Verigakis
#      disclaimer.
10 a1c50326 Giorgos Verigakis
#
11 a1c50326 Giorgos Verigakis
#   2. Redistributions in binary form must reproduce the above
12 e742badc Stavros Sachtouris
#      copyright notice, self.list of conditions and the following
13 a1c50326 Giorgos Verigakis
#      disclaimer in the documentation and/or other materials
14 a1c50326 Giorgos Verigakis
#      provided with the distribution.
15 a1c50326 Giorgos Verigakis
#
16 a1c50326 Giorgos Verigakis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 a1c50326 Giorgos Verigakis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 a1c50326 Giorgos Verigakis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 a1c50326 Giorgos Verigakis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 a1c50326 Giorgos Verigakis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 a1c50326 Giorgos Verigakis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 a1c50326 Giorgos Verigakis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 a1c50326 Giorgos Verigakis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 a1c50326 Giorgos Verigakis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 a1c50326 Giorgos Verigakis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 a1c50326 Giorgos Verigakis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 a1c50326 Giorgos Verigakis
# POSSIBILITY OF SUCH DAMAGE.
28 a1c50326 Giorgos Verigakis
#
29 a1c50326 Giorgos Verigakis
# The views and conclusions contained in the software and
30 a1c50326 Giorgos Verigakis
# documentation are those of the authors and should not be
31 a1c50326 Giorgos Verigakis
# interpreted as representing official policies, either expressed
32 a1c50326 Giorgos Verigakis
# or implied, of GRNET S.A.
33 a1c50326 Giorgos Verigakis
34 fce31e83 Stavros Sachtouris
from urllib2 import quote
35 2b74ab4a Stavros Sachtouris
from threading import Thread
36 de4f08ef Stavros Sachtouris
from json import dumps, loads
37 cad39033 Stavros Sachtouris
from time import time
38 6a0b1658 Giorgos Verigakis
import logging
39 c270fe96 Stavros Sachtouris
from kamaki.clients.connection.kamakicon import KamakiHTTPConnection
40 b2c5b650 Stavros Sachtouris
from kamaki.clients.connection.errors import KamakiConnectionError
41 b2c5b650 Stavros Sachtouris
from kamaki.clients.connection.errors import KamakiResponseError
42 6a0b1658 Giorgos Verigakis
43 6a0b1658 Giorgos Verigakis
sendlog = logging.getLogger('clients.send')
44 1a3c18fd Stavros Sachtouris
datasendlog = logging.getLogger('data.send')
45 6a0b1658 Giorgos Verigakis
recvlog = logging.getLogger('clients.recv')
46 1a3c18fd Stavros Sachtouris
datarecvlog = logging.getLogger('data.recv')
47 6a0b1658 Giorgos Verigakis
48 3dabe5d2 Stavros Sachtouris
49 a1c50326 Giorgos Verigakis
class ClientError(Exception):
50 4f989909 Stavros Sachtouris
    def __init__(self, message, status=0, details=None):
51 de4f08ef Stavros Sachtouris
        try:
52 1f417830 Stavros Sachtouris
            message += '' if message and message[-1] == '\n' else '\n'
53 062b1d0a Stavros Sachtouris
            serv_stat, sep, new_msg = message.partition('{')
54 062b1d0a Stavros Sachtouris
            new_msg = sep + new_msg
55 062b1d0a Stavros Sachtouris
            json_msg = loads(new_msg)
56 de4f08ef Stavros Sachtouris
            key = json_msg.keys()[0]
57 062b1d0a Stavros Sachtouris
58 de4f08ef Stavros Sachtouris
            json_msg = json_msg[key]
59 062b1d0a Stavros Sachtouris
            message = '%s %s (%s)\n' % (serv_stat, key, json_msg['message'])\
60 062b1d0a Stavros Sachtouris
                if 'message' in json_msg else '%s %s' % (serv_stat, key)
61 de4f08ef Stavros Sachtouris
            if 'code' in json_msg:
62 de4f08ef Stavros Sachtouris
                status = json_msg['code']
63 de4f08ef Stavros Sachtouris
            if 'details' in json_msg:
64 de4f08ef Stavros Sachtouris
                if not details:
65 de4f08ef Stavros Sachtouris
                    details = []
66 de4f08ef Stavros Sachtouris
                elif not isinstance(details, list):
67 de4f08ef Stavros Sachtouris
                    details = [details]
68 de4f08ef Stavros Sachtouris
                if json_msg['details']:
69 de4f08ef Stavros Sachtouris
                    details.append(json_msg['details'])
70 de4f08ef Stavros Sachtouris
        except:
71 de4f08ef Stavros Sachtouris
            pass
72 de4f08ef Stavros Sachtouris
73 7e5415a4 Stavros Sachtouris
        super(ClientError, self).__init__(message)
74 a1c50326 Giorgos Verigakis
        self.status = status
75 4f989909 Stavros Sachtouris
        self.details = details if details else []
76 a1c50326 Giorgos Verigakis
77 3dabe5d2 Stavros Sachtouris
78 2b74ab4a Stavros Sachtouris
class SilentEvent(Thread):
79 b2c5b650 Stavros Sachtouris
    """ Thread-run method(*args, **kwargs)"""
80 2b74ab4a Stavros Sachtouris
    def __init__(self, method, *args, **kwargs):
81 2b74ab4a Stavros Sachtouris
        super(self.__class__, self).__init__()
82 2b74ab4a Stavros Sachtouris
        self.method = method
83 2b74ab4a Stavros Sachtouris
        self.args = args
84 2b74ab4a Stavros Sachtouris
        self.kwargs = kwargs
85 2b74ab4a Stavros Sachtouris
86 2b74ab4a Stavros Sachtouris
    @property
87 2b74ab4a Stavros Sachtouris
    def exception(self):
88 2b74ab4a Stavros Sachtouris
        return getattr(self, '_exception', False)
89 2b74ab4a Stavros Sachtouris
90 2b74ab4a Stavros Sachtouris
    @property
91 2b74ab4a Stavros Sachtouris
    def value(self):
92 2b74ab4a Stavros Sachtouris
        return getattr(self, '_value', None)
93 2b74ab4a Stavros Sachtouris
94 2b74ab4a Stavros Sachtouris
    def run(self):
95 2b74ab4a Stavros Sachtouris
        try:
96 2b74ab4a Stavros Sachtouris
            self._value = self.method(*(self.args), **(self.kwargs))
97 2b74ab4a Stavros Sachtouris
        except Exception as e:
98 7644c38e Stavros Sachtouris
            recvlog.debug('Thread %s got exception %s\n<%s %s' % (
99 7644c38e Stavros Sachtouris
                self,
100 7644c38e Stavros Sachtouris
                type(e),
101 7644c38e Stavros Sachtouris
                e.status if isinstance(e, ClientError) else '',
102 7644c38e Stavros Sachtouris
                e))
103 2b74ab4a Stavros Sachtouris
            self._exception = e
104 2b74ab4a Stavros Sachtouris
105 6069b53b Stavros Sachtouris
106 6a0b1658 Giorgos Verigakis
class Client(object):
107 cccff590 Stavros Sachtouris
    POOL_SIZE = 7
108 cad39033 Stavros Sachtouris
109 5b263ba2 Stavros Sachtouris
    def __init__(self, base_url, token, http_client=KamakiHTTPConnection()):
110 6a0b1658 Giorgos Verigakis
        self.base_url = base_url
111 6c62a96d Giorgos Verigakis
        self.token = token
112 e742badc Stavros Sachtouris
        self.headers = {}
113 de73876b Stavros Sachtouris
        self.DATE_FORMATS = [
114 de73876b Stavros Sachtouris
            '%a %b %d %H:%M:%S %Y',
115 de73876b Stavros Sachtouris
            '%A, %d-%b-%y %H:%M:%S GMT',
116 de73876b Stavros Sachtouris
            '%a, %d %b %Y %H:%M:%S GMT']
117 a2e8e549 Stavros Sachtouris
        self.http_client = http_client
118 6c62a96d Giorgos Verigakis
119 cad39033 Stavros Sachtouris
    def _init_thread_limit(self, limit=1):
120 cad39033 Stavros Sachtouris
        self._thread_limit = limit
121 cad39033 Stavros Sachtouris
        self._elapsed_old = 0.0
122 cad39033 Stavros Sachtouris
        self._elapsed_new = 0.0
123 cad39033 Stavros Sachtouris
124 cad39033 Stavros Sachtouris
    def _watch_thread_limit(self, threadlist):
125 16c895db Stavros Sachtouris
        recvlog.debug('# running threads: %s' % len(threadlist))
126 24ff0a35 Stavros Sachtouris
        if (self._elapsed_old > self._elapsed_new) and (
127 2005b18e Stavros Sachtouris
                self._thread_limit < self.POOL_SIZE):
128 2005b18e Stavros Sachtouris
            self._thread_limit += 1
129 cad39033 Stavros Sachtouris
        elif self._elapsed_old < self._elapsed_new and self._thread_limit > 1:
130 cad39033 Stavros Sachtouris
            self._thread_limit -= 1
131 cad39033 Stavros Sachtouris
132 cad39033 Stavros Sachtouris
        self._elapsed_old = self._elapsed_new
133 cad39033 Stavros Sachtouris
        if len(threadlist) >= self._thread_limit:
134 cad39033 Stavros Sachtouris
            self._elapsed_new = 0.0
135 cad39033 Stavros Sachtouris
            for thread in threadlist:
136 cad39033 Stavros Sachtouris
                begin_time = time()
137 cad39033 Stavros Sachtouris
                thread.join()
138 cad39033 Stavros Sachtouris
                self._elapsed_new += time() - begin_time
139 cad39033 Stavros Sachtouris
            self._elapsed_new = self._elapsed_new / len(threadlist)
140 cad39033 Stavros Sachtouris
            return []
141 cad39033 Stavros Sachtouris
        return threadlist
142 cad39033 Stavros Sachtouris
143 6ad245d5 Stavros Sachtouris
    def _raise_for_status(self, r):
144 7966ffb8 Stavros Sachtouris
        status_msg = getattr(r, 'status', '')
145 d804de82 Stavros Sachtouris
        try:
146 7966ffb8 Stavros Sachtouris
            message = '%s %s\n' % (status_msg, r.text)
147 d804de82 Stavros Sachtouris
        except:
148 7966ffb8 Stavros Sachtouris
            message = '%s %s\n' % (status_msg, r)
149 7966ffb8 Stavros Sachtouris
        status = getattr(r, 'status_code', getattr(r, 'status', 0))
150 7966ffb8 Stavros Sachtouris
        raise ClientError(message, status=status)
151 6a0b1658 Giorgos Verigakis
152 4adfa919 Stavros Sachtouris
    def set_header(self, name, value, iff=True):
153 3dabe5d2 Stavros Sachtouris
        """Set a header 'name':'value'"""
154 4adfa919 Stavros Sachtouris
        if value is not None and iff:
155 2f749e6e Stavros Sachtouris
            self.http_client.set_header(name, value)
156 2f749e6e Stavros Sachtouris
157 2f749e6e Stavros Sachtouris
    def set_param(self, name, value=None, iff=True):
158 2f749e6e Stavros Sachtouris
        if iff:
159 2f749e6e Stavros Sachtouris
            self.http_client.set_param(name, value)
160 2f749e6e Stavros Sachtouris
161 2f749e6e Stavros Sachtouris
    def set_default_header(self, name, value):
162 2f749e6e Stavros Sachtouris
        self.http_client.headers.setdefault(name, value)
163 e742badc Stavros Sachtouris
164 de73876b Stavros Sachtouris
    def request(
165 24ff0a35 Stavros Sachtouris
            self,
166 24ff0a35 Stavros Sachtouris
            method,
167 24ff0a35 Stavros Sachtouris
            path,
168 24ff0a35 Stavros Sachtouris
            async_headers={},
169 24ff0a35 Stavros Sachtouris
            async_params={},
170 24ff0a35 Stavros Sachtouris
            **kwargs):
171 9a7efb0d Stavros Sachtouris
        """In threaded/asynchronous requests, headers and params are not safe
172 3dabe5d2 Stavros Sachtouris
        Therefore, the standard self.set_header/param system can be used only
173 3dabe5d2 Stavros Sachtouris
        for headers and params that are common for all requests. All other
174 3dabe5d2 Stavros Sachtouris
        params and headers should passes as
175 9a7efb0d Stavros Sachtouris
        @param async_headers
176 9a7efb0d Stavros Sachtouris
        @async_params
177 3dabe5d2 Stavros Sachtouris
        E.g. in most queries the 'X-Auth-Token' header might be the same for
178 3dabe5d2 Stavros Sachtouris
        all, but the 'Range' header might be different from request to request.
179 9a7efb0d Stavros Sachtouris
        """
180 d726b3d0 Stavros Sachtouris
        try:
181 2f749e6e Stavros Sachtouris
            success = kwargs.pop('success', 200)
182 2f749e6e Stavros Sachtouris
183 2f749e6e Stavros Sachtouris
            data = kwargs.pop('data', None)
184 2f749e6e Stavros Sachtouris
            self.set_default_header('X-Auth-Token', self.token)
185 2f749e6e Stavros Sachtouris
186 2f749e6e Stavros Sachtouris
            if 'json' in kwargs:
187 de4f08ef Stavros Sachtouris
                data = dumps(kwargs.pop('json'))
188 2f749e6e Stavros Sachtouris
                self.set_default_header('Content-Type', 'application/json')
189 2f749e6e Stavros Sachtouris
            if data:
190 a517ff50 Stavros Sachtouris
                self.set_default_header('Content-Length', '%s' % len(data))
191 2f749e6e Stavros Sachtouris
192 a40e152f Stavros Sachtouris
            sendlog.info('perform a %s @ %s', method, self.base_url)
193 a40e152f Stavros Sachtouris
194 c1004a00 Stavros Sachtouris
            self.http_client.url = self.base_url
195 4de960c7 Stavros Sachtouris
            self.http_client.path = quote(path.encode('utf8'))
196 de73876b Stavros Sachtouris
            r = self.http_client.perform_request(
197 de73876b Stavros Sachtouris
                method,
198 3dabe5d2 Stavros Sachtouris
                data,
199 3dabe5d2 Stavros Sachtouris
                async_headers,
200 3dabe5d2 Stavros Sachtouris
                async_params)
201 2f749e6e Stavros Sachtouris
202 2f749e6e Stavros Sachtouris
            req = self.http_client
203 5b263ba2 Stavros Sachtouris
            sendlog.info('%s %s', method, req.url)
204 1785ad41 Stavros Sachtouris
            headers = dict(req.headers)
205 1785ad41 Stavros Sachtouris
            headers.update(async_headers)
206 1785ad41 Stavros Sachtouris
207 1785ad41 Stavros Sachtouris
            for key, val in headers.items():
208 1785ad41 Stavros Sachtouris
                sendlog.info('\t%s: %s', key, val)
209 2f749e6e Stavros Sachtouris
            sendlog.info('')
210 2f749e6e Stavros Sachtouris
            if data:
211 1a3c18fd Stavros Sachtouris
                datasendlog.info(data)
212 2f749e6e Stavros Sachtouris
213 2f749e6e Stavros Sachtouris
            recvlog.info('%d %s', r.status_code, r.status)
214 2f749e6e Stavros Sachtouris
            for key, val in r.headers.items():
215 2f749e6e Stavros Sachtouris
                recvlog.info('%s: %s', key, val)
216 bcb51856 Stavros Sachtouris
            if r.content:
217 1a3c18fd Stavros Sachtouris
                datarecvlog.info(r.content)
218 2f749e6e Stavros Sachtouris
219 b2c5b650 Stavros Sachtouris
        except (KamakiResponseError, KamakiConnectionError) as err:
220 1f417830 Stavros Sachtouris
            from traceback import format_stack
221 1f417830 Stavros Sachtouris
            recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
222 2f749e6e Stavros Sachtouris
            self.http_client.reset_headers()
223 2f749e6e Stavros Sachtouris
            self.http_client.reset_params()
224 1f417830 Stavros Sachtouris
            errstr = '%s' % err
225 1f417830 Stavros Sachtouris
            if not errstr:
226 1f417830 Stavros Sachtouris
                errstr = ('%s' % type(err))[7:-2]
227 de73876b Stavros Sachtouris
            status = getattr(err, 'status', getattr(err, 'errno', 0))
228 de73876b Stavros Sachtouris
            raise ClientError('%s\n' % errstr, status=status)
229 a52d2256 Stavros Sachtouris
230 a52d2256 Stavros Sachtouris
        self.http_client.reset_headers()
231 a52d2256 Stavros Sachtouris
        self.http_client.reset_params()
232 0238c167 Stavros Sachtouris
233 0238c167 Stavros Sachtouris
        if success is not None:
234 a037fd61 Stavros Sachtouris
            # Success can either be an int or a collection
235 0238c167 Stavros Sachtouris
            success = (success,) if isinstance(success, int) else success
236 0238c167 Stavros Sachtouris
            if r.status_code not in success:
237 0238c167 Stavros Sachtouris
                r.release()
238 0238c167 Stavros Sachtouris
                self._raise_for_status(r)
239 d726b3d0 Stavros Sachtouris
        return r
240 a1c50326 Giorgos Verigakis
241 6a0b1658 Giorgos Verigakis
    def delete(self, path, **kwargs):
242 6a0b1658 Giorgos Verigakis
        return self.request('delete', path, **kwargs)
243 6a0b1658 Giorgos Verigakis
244 6a0b1658 Giorgos Verigakis
    def get(self, path, **kwargs):
245 6a0b1658 Giorgos Verigakis
        return self.request('get', path, **kwargs)
246 6a0b1658 Giorgos Verigakis
247 6a0b1658 Giorgos Verigakis
    def head(self, path, **kwargs):
248 6a0b1658 Giorgos Verigakis
        return self.request('head', path, **kwargs)
249 6a0b1658 Giorgos Verigakis
250 6a0b1658 Giorgos Verigakis
    def post(self, path, **kwargs):
251 6a0b1658 Giorgos Verigakis
        return self.request('post', path, **kwargs)
252 6a0b1658 Giorgos Verigakis
253 6a0b1658 Giorgos Verigakis
    def put(self, path, **kwargs):
254 6a0b1658 Giorgos Verigakis
        return self.request('put', path, **kwargs)
255 6a0b1658 Giorgos Verigakis
256 4adfa919 Stavros Sachtouris
    def copy(self, path, **kwargs):
257 4adfa919 Stavros Sachtouris
        return self.request('copy', path, **kwargs)
258 4adfa919 Stavros Sachtouris
259 4adfa919 Stavros Sachtouris
    def move(self, path, **kwargs):
260 4adfa919 Stavros Sachtouris
        return self.request('move', path, **kwargs)