Revision c2b5da2f

b/kamaki/clients/__init__.py
32 32
# or implied, of GRNET S.A.
33 33

  
34 34
from urllib2 import quote
35
from urlparse import urlparse
35 36
from threading import Thread
36 37
from json import dumps, loads
37 38
from time import time
39
from httplib import ResponseNotReady
40
from time import sleep
41
from random import random
42

  
43
from objpool.http import PooledHTTPConnection
38 44

  
39 45
from kamaki.clients.utils import logger
40
from kamaki.clients.connection.kamakicon import KamakiHTTPConnection
41
from kamaki.clients.connection.errors import KamakiConnectionError
42
from kamaki.clients.connection.errors import KamakiResponseError
43 46

  
44 47
LOG_TOKEN = False
45 48
DEBUG_LOG = logger.get_log_filename()
......
60 63
logger.add_file_logger('ClientError', __name__, filename=DEBUG_LOG)
61 64
clienterrorlog = logger.get_logger('ClientError')
62 65

  
66
HTTP_METHODS = ['GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'COPY', 'MOVE']
67

  
68

  
69
def _encode(v):
70
    if v and isinstance(v, unicode):
71
        return quote(v.encode('utf-8'))
72
    return v
73

  
63 74

  
64 75
class ClientError(Exception):
65 76
    def __init__(self, message, status=0, details=None):
......
97 108
            self.details = details if details else []
98 109

  
99 110

  
111
class RequestManager(object):
112
    """Handle http request information"""
113

  
114
    def _connection_info(self, url, path, params={}):
115
        """ Set self.url to scheme://netloc/?params
116
        :param url: (str or unicode) The service url
117

  
118
        :param path: (str or unicode) The service path (url/path)
119

  
120
        :param params: (dict) Parameters to add to final url
121

  
122
        :returns: (scheme, netloc)
123
        """
124
        url = _encode(url) if url else 'http://127.0.0.1/'
125
        url += '' if url.endswith('/') else '/'
126
        if path:
127
            url += _encode(path[1:] if path.startswith('/') else path)
128
        for i, (key, val) in enumerate(params.items()):
129
            val = _encode(val)
130
            url += '%s%s' % ('&' if i else '?', key)
131
            if val:
132
                url += '=%s' % val
133
        parsed = urlparse(url)
134
        self.url = url
135
        self.path = parsed.path or '/'
136
        if parsed.query:
137
            self.path += '?%s' % parsed.query
138
        return (parsed.scheme, parsed.netloc)
139

  
140
    def __init__(
141
            self, method, url, path,
142
            data=None, headers={}, params={}):
143
        method = method.upper()
144
        assert method in HTTP_METHODS, 'Invalid http method %s' % method
145
        if headers:
146
            assert isinstance(headers, dict)
147
            self.headers = dict(headers)
148
        self.method, self.data = method, data
149
        self.scheme, self.netloc = self._connection_info(url, path, params)
150

  
151
    def perform(self, conn):
152
        """
153
        :param conn: (httplib connection object)
154

  
155
        :returns: (HTTPResponse)
156
        """
157
        #  sendlog.debug(
158
        #    'RequestManager.perform mthd(%s), url(%s), headrs(%s), bdlen(%s)',
159
        #    self.method, self.url, self.headers, self.data)
160
        conn.request(
161
            method=str(self.method.upper()),
162
            url=str(self.path),
163
            headers=self.headers,
164
            body=self.data)
165
        while True:
166
            try:
167
                return conn.getresponse()
168
            except ResponseNotReady:
169
                sleep(0.03 * random())
170

  
171

  
172
class ResponseManager(object):
173
    """Manage the http request and handle the response data, headers, etc."""
174

  
175
    def __init__(self, request, poolsize=None):
176
        """
177
        :param request: (RequestManager)
178
        """
179
        self.request = request
180
        self._request_performed = False
181
        self.poolsize = poolsize
182

  
183
    def _get_response(self):
184
        if self._request_performed:
185
            return
186

  
187
        pool_kw = dict(size=self.poolsize) if self.poolsize else dict()
188
        try:
189
            with PooledHTTPConnection(
190
                    self.request.netloc, self.request.scheme,
191
                    **pool_kw) as connection:
192
                r = self.request.perform(connection)
193
                #  recvlog.debug('ResponseManager(%s):' % r)
194
                self._request_performed = True
195
                self._headers = dict()
196
                for k, v in r.getheaders():
197
                    self.headers[k] = v
198
                    #  recvlog.debug('\t%s: %s\t(%s)' % (k, v, r))
199
                self._content = r.read()
200
                self._status_code = r.status
201
                self._status = r.reason
202
        except Exception as err:
203
            from kamaki.clients import recvlog
204
            from traceback import format_stack
205
            recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
206
            raise ClientError(
207
                'Failed while http-connecting to %s (%s)' % (
208
                    self.request.url,
209
                    err),
210
                1000)
211

  
212
    @property
213
    def status_code(self):
214
        self._get_response()
215
        return self._status_code
216

  
217
    @property
218
    def status(self):
219
        self._get_response()
220
        return self._status
221

  
222
    @property
223
    def headers(self):
224
        self._get_response()
225
        return self._headers
226

  
227
    @property
228
    def content(self):
229
        self._get_response()
230
        return self._content
231

  
232
    @property
233
    def text(self):
234
        """
235
        :returns: (str) content
236
        """
237
        self._get_response()
238
        return '%s' % self._content
239

  
240
    @property
241
    def json(self):
242
        """
243
        :returns: (dict) squeezed from json-formated content
244
        """
245
        self._get_response()
246
        try:
247
            return loads(self._content)
248
        except ValueError as err:
249
            ClientError('Response not formated in JSON - %s' % err)
250

  
251

  
100 252
class SilentEvent(Thread):
101 253
    """ Thread-run method(*args, **kwargs)"""
102 254
    def __init__(self, method, *args, **kwargs):
......
127 279

  
128 280
class Client(object):
129 281

  
130
    def __init__(self, base_url, token, http_client=KamakiHTTPConnection()):
282
    def __init__(self, base_url, token):
131 283
        self.base_url = base_url
132 284
        self.token = token
133
        self.headers = {}
285
        self.headers, self.params = dict(), dict()
134 286
        self.DATE_FORMATS = [
135 287
            '%a %b %d %H:%M:%S %Y',
136 288
            '%A, %d-%b-%y %H:%M:%S GMT',
137 289
            '%a, %d %b %Y %H:%M:%S GMT']
138
        self.http_client = http_client
139 290
        self.MAX_THREADS = 7
140 291

  
141 292
    def _init_thread_limit(self, limit=1):
......
179 330
    def set_header(self, name, value, iff=True):
180 331
        """Set a header 'name':'value'"""
181 332
        if value is not None and iff:
182
            self.http_client.set_header(name, value)
333
            self.headers[name] = value
183 334

  
184 335
    def set_param(self, name, value=None, iff=True):
185 336
        if iff:
186
            self.http_client.set_param(name, value)
337
            self.params[name] = value
187 338

  
188 339
    def request(
189
            self,
190
            method,
191
            path,
192
            async_headers={},
193
            async_params={},
340
            self, method, path,
341
            async_headers=dict(), async_params=dict(),
194 342
            **kwargs):
195 343
        """In threaded/asynchronous requests, headers and params are not safe
196 344
        Therefore, the standard self.set_header/param system can be used only
......
205 353
        assert method
206 354
        assert isinstance(path, str) or isinstance(path, unicode)
207 355
        try:
356
            headers = dict(self.headers)
357
            headers.update(async_headers)
358
            params = dict(self.params)
359
            params.update(async_params)
208 360
            success = kwargs.pop('success', 200)
209 361
            data = kwargs.pop('data', None)
210
            self.http_client.headers.setdefault('X-Auth-Token', self.token)
211

  
362
            headers.setdefault('X-Auth-Token', self.token)
212 363
            if 'json' in kwargs:
213 364
                data = dumps(kwargs.pop('json'))
214
                self.http_client.headers.setdefault(
215
                    'Content-Type',
216
                    'application/json')
365
                headers.setdefault('Content-Type', 'application/json')
217 366
            if data:
218
                self.http_client.headers.setdefault(
219
                    'Content-Length',
220
                    '%s' % len(data))
221

  
222
            sendlog.info('perform a %s @ %s', method, self.base_url)
223

  
224
            self.http_client.url = self.base_url
225
            self.http_client.path = quote(path.encode('utf8'))
226
            r = self.http_client.perform_request(
227
                method,
228
                data,
229
                async_headers,
230
                async_params)
231

  
232
            req = self.http_client
233
            sendlog.info('%s %s', method, req.url)
234
            headers = dict(req.headers)
235
            headers.update(async_headers)
236

  
237
            for key, val in headers.items():
367
                headers.setdefault('Content-Length', '%s' % len(data))
368

  
369
            req = RequestManager(
370
                method, self.base_url, path,
371
                data=data, headers=headers, params=params)
372
            sendlog.info('commit a %s @ %s\t[%s]', method, self.base_url, self)
373
            sendlog.info('\tpath: %s\t[%s]', req.path, self)
374
            for key, val in req.headers.items():
238 375
                if (not LOG_TOKEN) and key.lower() == 'x-auth-token':
239 376
                    continue
240
                sendlog.info('\t%s: %s', key, val)
241
            sendlog.info('')
377
                sendlog.info('\t%s: %s [%s]', key, val, self)
242 378
            if data:
243 379
                datasendlog.info(data)
380
            sendlog.info('END HTTP request commit\t[%s]', self)
244 381

  
382
            r = ResponseManager(req)
245 383
            recvlog.info('%d %s', r.status_code, r.status)
246 384
            for key, val in r.headers.items():
247 385
                if (not LOG_TOKEN) and key.lower() == 'x-auth-token':
......
249 387
                recvlog.info('%s: %s', key, val)
250 388
            if r.content:
251 389
                datarecvlog.info(r.content)
252

  
253
        except (KamakiResponseError, KamakiConnectionError) as err:
254
            from traceback import format_stack
255
            recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
256
            self.http_client.reset_headers()
257
            self.http_client.reset_params()
258
            errstr = '%s' % err
259
            if not errstr:
260
                errstr = ('%s' % type(err))[7:-2]
261
            status = getattr(err, 'status', getattr(err, 'errno', 0))
262
            raise ClientError('%s\n' % errstr, status=status)
263 390
        finally:
264
            self.http_client.reset_headers()
265
            self.http_client.reset_params()
391
            self.headers = dict()
392
            self.params = dict()
266 393

  
267 394
        if success is not None:
268 395
            # Success can either be an int or a collection
269 396
            success = (success,) if isinstance(success, int) else success
270 397
            if r.status_code not in success:
271
                r.release()
272 398
                self._raise_for_status(r)
273 399
        return r
274 400

  
b/kamaki/clients/astakos/test.py
63 63
    status = None
64 64
    status_code = 200
65 65

  
66
    def release(self):
67
        pass
68

  
69 66
astakos_pkg = 'kamaki.clients.astakos.AstakosClient'
70 67

  
71 68

  
b/kamaki/clients/compute/__init__.py
118 118
        :param new_name: (str)
119 119
        """
120 120
        req = {'server': {'name': new_name}}
121
        r = self.servers_put(server_id, json_data=req)
122
        r.release()
121
        self.servers_put(server_id, json_data=req)
123 122

  
124 123
    def delete_server(self, server_id):
125 124
        """Submit a deletion request for a server specified by id
126 125

  
127 126
        :param server_id: integer (str or int)
128 127
        """
129
        r = self.servers_delete(server_id)
130
        r.release()
128
        self.servers_delete(server_id)
131 129

  
132 130
    def reboot_server(self, server_id, hard=False):
133 131
        """
......
137 135
        """
138 136
        boot_type = 'HARD' if hard else 'SOFT'
139 137
        req = {'reboot': {'type': boot_type}}
140
        r = self.servers_post(server_id, 'action', json_data=req)
141
        r.release()
138
        self.servers_post(server_id, 'action', json_data=req)
142 139

  
143 140
    def get_server_metadata(self, server_id, key=''):
144 141
        """
......
188 185

  
189 186
        :param key: (str) the meta key
190 187
        """
191
        r = self.servers_delete(server_id, 'meta/' + key)
192
        r.release()
188
        self.servers_delete(server_id, 'meta/' + key)
193 189

  
194 190
    def list_flavors(self, detail=False):
195 191
        """
......
238 234
        """
239 235
        :param image_id: (str)
240 236
        """
241
        r = self.images_delete(image_id)
242
        r.release()
237
        self.images_delete(image_id)
243 238

  
244 239
    def get_image_metadata(self, image_id, key=''):
245 240
        """
......
286 281
        :param key: (str) metadatum key
287 282
        """
288 283
        command = path4url('meta', key)
289
        r = self.images_delete(image_id, command)
290
        r.release()
284
        self.images_delete(image_id, command)
b/kamaki/clients/compute/test.py
106 106
    status = None
107 107
    status_code = 200
108 108

  
109
    def release(self):
110
        pass
111

  
112 109

  
113 110
class ComputeRestClient(TestCase):
114 111

  
/dev/null
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, self.list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, self.list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34

  
35
class KamakiResponse(object):
36
    """An abstract HTTP Response object to handle a performed HTTPRequest.
37
    Subclass implementation required
38
    """
39

  
40
    def __init__(self, request, prefetched=False):
41
        self.request = request
42
        self.prefetched = prefetched
43

  
44
    def _get_response(self):
45
        """Wait for http response as late as possible"""
46
        if self.prefetched:
47
            return
48
        self = self.request.response
49
        self.prefetched = True
50

  
51
    def release(self):
52
        """Release the connection.
53
        """
54
        raise NotImplementedError
55

  
56
    @property
57
    def prefetched(self):
58
        """flag to avoid downloading more than nessecary"""
59
        return self._prefetched
60

  
61
    @prefetched.setter
62
    def prefetched(self, p):
63
        self._prefetched = p
64

  
65
    @property
66
    def content(self):
67
        """:returns: (binary) request response content (data)"""
68
        self._get_response()
69
        return self._content
70

  
71
    @content.setter
72
    def content(self, v):
73
        self._content = v
74

  
75
    @property
76
    def text(self):
77
        """(str)"""
78
        self._get_response()
79
        return self._text
80

  
81
    @text.setter
82
    def text(self, v):
83
        self._text = v
84

  
85
    @property
86
    def json(self):
87
        """(dict)"""
88
        self._get_response()
89
        return self._json
90

  
91
    @json.setter
92
    def json(self, v):
93
        self._json = v
94

  
95
    @property
96
    def headers(self):
97
        """(dict)"""
98
        self._get_response()
99
        return self._headers
100

  
101
    @headers.setter
102
    def headers(self, v):
103
        self._headers = v
104

  
105
    @property
106
    def status_code(self):
107
        """(int) optional"""
108
        self._get_response()
109
        return self._status_code
110

  
111
    @status_code.setter
112
    def status_code(self, v):
113
        self._status_code = v
114

  
115
    @property
116
    def status(self):
117
        """(str) useful in server error responses"""
118
        self._get_response()
119
        return self._status
120

  
121
    @status.setter
122
    def status(self, v):
123
        self._status = v
124

  
125
    @property
126
    def request(self):
127
        """(KamakiConnection) the source of this response object"""
128
        return self._request
129

  
130
    @request.setter
131
    def request(self, v):
132
        self._request = v
133

  
134

  
135
class KamakiConnection(object):
136
    """An abstract HTTP Connection mechanism. Subclass implementation required
137
    """
138

  
139
    def __init__(
140
            self,
141
            method=None, url=None, params={}, headers={}, poolsize=8):
142
        self.headers = headers
143
        self.params = params
144
        self.url = url
145
        self.path = ''
146
        self.method = method
147
        self.poolsize = poolsize
148

  
149
    @property
150
    def poolsize(self):
151
        return self._poolsize
152

  
153
    @poolsize.setter
154
    def poolsize(self, v):
155
        assert isinstance(v, (int, long)) and v > 0
156
        self._poolsize = v
157

  
158
    def set_header(self, name, value):
159
        assert name, 'KamakiConnection header key cannot be 0 or empty'
160
        self.headers['%s' % name] = '%s' % value
161

  
162
    def remove_header(self, name):
163
        try:
164
            self.headers.pop(name)
165
        except KeyError:
166
            pass
167

  
168
    def replace_headers(self, new_headers):
169
        self.headers = new_headers
170

  
171
    def reset_headers(self):
172
        self.replace_headers({})
173

  
174
    def set_param(self, name, value=None):
175
        assert name, 'KamakiConnection param key cannot be 0 or empty'
176
        self.params[unicode(name)] = value
177

  
178
    def remove_param(self, name):
179
        try:
180
            self.params.pop(name)
181
        except KeyError:
182
            pass
183

  
184
    def replace_params(self, new_params):
185
        self.params = new_params
186

  
187
    def reset_params(self):
188
        self.replace_params({})
189

  
190
    def set_url(self, url):
191
        self.url = url
192

  
193
    def set_path(self, path):
194
        self.path = path
195

  
196
    def set_method(self, method):
197
        self.method = method
198

  
199
    def perform_request(
200
            self,
201
            method=None,
202
            url=None,
203
            async_headers={},
204
            async_params={},
205
            data=None):
206
        raise NotImplementedError
/dev/null
1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, self.list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, self.list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34

  
35
class KamakiConnectionError(Exception):
36

  
37
    def __init__(self, message, errno=None):
38
        super(KamakiConnectionError, self).__init__(message)
39
        self.errno = errno if errno else 0
40

  
41

  
42
class KamakiResponseError(Exception):
43

  
44
    def __init__(self, message, errno=None):
45
        super(KamakiResponseError, self).__init__(message)
46
        self.errno = errno if errno else 0
/dev/null
1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, self.list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, self.list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from urlparse import urlparse
35
from urllib2 import quote
36
from objpool.http import get_http_connection
37
from traceback import format_stack
38

  
39
from kamaki.clients.connection import KamakiConnection, KamakiResponse
40
from kamaki.clients.connection.errors import KamakiConnectionError
41
from kamaki.clients.connection.errors import KamakiResponseError
42

  
43
from json import loads
44

  
45
from time import sleep
46
from httplib import ResponseNotReady
47

  
48

  
49
def _encode(v):
50
    if v and isinstance(v, unicode):
51
        return quote(v.encode('utf-8'))
52
    return v
53

  
54

  
55
class KamakiHTTPResponse(KamakiResponse):
56

  
57
    def _get_response(self):
58
        if self.prefetched:
59
            return
60

  
61
        try:
62
            while True:
63
                try:
64
                    r = self.request.getresponse()
65
                except ResponseNotReady:
66
                    sleep(0.001)
67
                else:
68
                    break
69
            self.prefetched = True
70
            headers = {}
71
            for k, v in r.getheaders():
72
                headers.update({k: v})
73
            self.headers = headers
74
            self.content = r.read()
75
            self.status_code = r.status
76
            self.status = r.reason
77
        finally:
78
            try:
79
                self.request.close()
80
            except Exception as err:
81
                from kamaki.clients import recvlog
82
                recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
83
                raise
84

  
85
    @property
86
    def text(self):
87
        """
88
        :returns: (str) content
89
        """
90
        self._get_response()
91
        return '%s' % self._content
92

  
93
    @text.setter
94
    def text(self, v):
95
        pass
96

  
97
    @property
98
    def json(self):
99
        """
100
        :returns: (dict) the json-formated content
101

  
102
        :raises KamakiResponseError: if content is not json formated
103
        """
104
        self._get_response()
105
        try:
106
            return loads(self._content)
107
        except ValueError as err:
108
            KamakiResponseError('Response not formated in JSON - %s' % err)
109

  
110
    @json.setter
111
    def json(self, v):
112
        pass
113

  
114
    def release(self):
115
        """ Release the connection. Should always be called if the response
116
        content hasn't been used.
117
        """
118
        if not self.prefetched:
119
            try:
120
                self.request.close()
121
            except Exception as err:
122
                from kamaki.clients import recvlog
123
                recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
124
                raise
125

  
126

  
127
class KamakiHTTPConnection(KamakiConnection):
128

  
129
    def _retrieve_connection_info(self, extra_params={}):
130
        """ Set self.url to scheme://netloc/?params
131
        :param extra_params: (dict) key:val for url parameters
132

  
133
        :returns: (scheme, netloc)
134
        """
135
        if self.url:
136
            url = self.url if self.url[-1] == '/' else (self.url + '/')
137
        else:
138
            url = 'http://127.0.0.1'
139
        if self.path:
140
            url += self.path[1:] if self.path[0] == '/' else self.path
141
        params = dict(self.params)
142
        params.update(extra_params)
143
        for i, (key, val) in enumerate(params.items()):
144
            val = _encode(val)
145
            url += '%s%s' % ('&' if i else '?', key)
146
            if val:
147
                url += '=%s' % val
148

  
149
        parsed = urlparse(url)
150
        self.url = url
151
        self.path = parsed.path or '/'
152
        if parsed.query:
153
            self.path += '?%s' % parsed.query
154
        return (parsed.scheme, parsed.netloc)
155

  
156
    def perform_request(
157
            self,
158
            method=None, data=None, async_headers={}, async_params={}):
159
        """
160
        :param method: (str) http method ('get', 'post', etc.)
161

  
162
        :param data: (binary object)
163

  
164
        :param async_headers: (dict) key:val headers that are used only for one
165
            request instance as opposed to self.headers, which remain to be
166
            used by following or parallel requests
167

  
168
        :param async_params: (dict) key:val url parameters that are used only
169
            for one request instance as opposed to self.params, which remain to
170
            be used by following or parallel requests
171

  
172
        :returns: (KamakiHTTPResponse) a response object
173

  
174
        :raises KamakiConnectionError: Connection failures
175
        """
176
        (scheme, netloc) = self._retrieve_connection_info(async_params)
177
        headers = dict(self.headers)
178
        for k, v in async_headers.items():
179
            v = _encode(v)
180
            headers[k] = v
181

  
182
        #de-unicode headers to prepare them for http
183
        http_headers = {}
184
        for k, v in headers.items():
185
            v = _encode(v)
186
            http_headers[k] = v
187

  
188
        #get connection from pool
189
        try:
190
            conn = get_http_connection(
191
                netloc=netloc,
192
                scheme=scheme,
193
                pool_size=self.poolsize)
194
        except ValueError as ve:
195
            raise KamakiConnectionError(
196
                'Cannot establish connection to %s %s' % (self.url, ve),
197
                errno=-1)
198
        try:
199
            #Be carefull, all non-body variables should not be unicode
200
            conn.request(
201
                method=str(method.upper()),
202
                url=str(self.path),
203
                headers=http_headers,
204
                body=data)
205
        except IOError as ioe:
206
            raise KamakiConnectionError(
207
                'Cannot connect to %s: %s' % (self.url, ioe.strerror),
208
                errno=ioe.errno)
209
        except Exception as err:
210
            from kamaki.clients import recvlog
211
            recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
212
            conn.close()
213
            raise
214
        return KamakiHTTPResponse(conn)
/dev/null
1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, self.list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, self.list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from unittest import TestCase
35
from mock import Mock, patch, call
36
from random import randrange
37
from urllib2 import quote
38

  
39
from kamaki.clients import connection
40
from kamaki.clients.connection import errors, kamakicon
41

  
42

  
43
def _encode(v):
44
    if v and isinstance(v, unicode):
45
        return quote(v.encode('utf-8'))
46
    return v
47

  
48

  
49
class KamakiConnection(TestCase):
50
    v_samples = {'title': 'value', 5: 'value'}
51
    n_samples = {'title': None, 5: None}
52
    false_samples = {None: 'value', 0: 'value'}
53

  
54
    def setUp(self):
55
        from kamaki.clients.connection import KamakiConnection as HTTPC
56
        self.conn = HTTPC()
57
        self.conn.reset_headers()
58
        self.conn.reset_params()
59

  
60
    def test_poolsize(self):
61

  
62
        def set_poolsize(poolsize):
63
            self.conn.poolsize = poolsize
64

  
65
        from kamaki.clients.connection import KamakiConnection as HTTPC
66
        for poolsize in ('non integer', -10, 0):
67
            err = AssertionError
68
            self.assertRaises(err, set_poolsize, poolsize)
69
        for poolsize in (1, 100, 1024 * 1024 * 1024 * 1024):
70
            self.conn.poolsize = poolsize
71
            self.assertEquals(self.conn.poolsize, poolsize)
72
            self.assertEquals(HTTPC(poolsize=poolsize).poolsize, poolsize)
73

  
74
    def test_set_header(self):
75
        cnn = self.conn
76
        for k, v in self.v_samples.items():
77
            cnn.set_header(k, v)
78
            self.assertEquals(cnn.headers[unicode(k)], unicode(v))
79
        for k, v in self.n_samples.items():
80
            cnn.set_header(k, v)
81
            self.assertEquals(cnn.headers[unicode(k)], unicode(v))
82
        for k, v in self.false_samples.items():
83
            self.assertRaises(AssertionError, cnn.set_header, k, v)
84
        self.assertEquals(len(cnn.headers), 2)
85

  
86
    def test_set_param(self):
87
        cnn = self.conn
88
        for k, v in self.v_samples.items():
89
            cnn.set_param(k, v)
90
            self.assertEquals(cnn.params[unicode(k)], v)
91
        for k, v in self.n_samples.items():
92
            cnn.set_param(k, v)
93
            self.assertEquals(cnn.params[unicode(k)], v)
94
        for k, v in self.false_samples.items():
95
            self.assertRaises(AssertionError, cnn.set_param, k, v)
96
        self.assertEquals(len(cnn.params), 2)
97

  
98
    def test_remove_header(self):
99
        cnn = self.conn
100
        for k, v in self.v_samples.items():
101
            cnn.headers[unicode(k)] = unicode(v)
102
        for k in self.v_samples:
103
            cnn.remove_header(k)
104
            self.assertFalse(k in cnn.headers)
105

  
106
    def test_remove_param(self):
107
        cnn = self.conn
108
        for k, v in self.v_samples.items():
109
            cnn.params[unicode(k)] = unicode(v)
110
        for k in self.v_samples:
111
            cnn.remove_param(k)
112
            self.assertFalse(k in cnn.params)
113

  
114
    def test_replace_headers(self):
115
        cnn = self.conn
116
        cnn.headers = self.v_samples
117
        cnn.replace_headers({1: 'one', 2: 'two'})
118
        for k in self.v_samples:
119
            self.assertFalse(k in cnn.headers)
120

  
121
    def test_replace_params(self):
122
        cnn = self.conn
123
        cnn.params = self.v_samples
124
        cnn.replace_params({1: 'one', 2: 'two'})
125
        for k in self.v_samples:
126
            self.assertFalse(k in cnn.params)
127

  
128
    def test_reset_headers(self):
129
        cnn = self.conn
130
        cnn.headers = self.v_samples
131
        cnn.reset_headers()
132
        self.assertFalse(cnn.headers)
133

  
134
    def test_reset_params(self):
135
        cnn = self.conn
136
        cnn.params = self.v_samples
137
        cnn.reset_params()
138
        self.assertFalse(cnn.params)
139

  
140
    def test_set_url(self):
141
        self.assertFalse(self.conn.url)
142
        sample_url = 'http://example.com'
143
        self.conn.set_url(sample_url)
144
        self.assertEquals(self.conn.url, sample_url)
145

  
146
    def test_set_path(self):
147
        self.assertFalse(self.conn.path)
148
        sample_path = '/example/local/path'
149
        self.conn.set_path(sample_path)
150
        self.assertEquals(self.conn.path, sample_path)
151

  
152
    def test_set_method(self):
153
        self.assertFalse(self.conn.method)
154
        sample_method = 'GET'
155
        self.conn.set_method(sample_method)
156
        self.assertEquals(self.conn.method, sample_method)
157

  
158
    def test_perform_request(self):
159
        self.assertRaises(NotImplementedError, self.conn.perform_request)
160

  
161

  
162
class KamakiHTTPConnection(TestCase):
163

  
164
    def setUp(self):
165
        self.conn = kamakicon.KamakiHTTPConnection()
166
        self.conn.reset_params()
167
        self.conn.reset_headers()
168

  
169
    def test__retrieve_connection_info(self):
170
        async_params = dict(param1='val1', param2=None, param3=42)
171
        r = self.conn._retrieve_connection_info(async_params)
172
        self.assertEquals(r, ('http', '127.0.0.1'))
173
        expected = '?%s' % '&'.join([(
174
            '%s=%s' % (k, v)) if v else (
175
            '%s' % k) for k, v in async_params.items()])
176
        self.assertEquals('http://127.0.0.1%s' % expected, self.conn.url)
177

  
178
        for schnet in (
179
            ('http', 'www.example.com'), ('https', 'www.example.com'),
180
            ('ftp', 'www.example.com'), ('ftps', 'www.example.com'),
181
            ('http', 'www.example.com/v1'), ('https', 'www.example.com/v1')):
182
            self.conn = kamakicon.KamakiHTTPConnection(url='%s://%s' % schnet)
183
            self.conn.url = '%s://%s' % schnet
184
            r = self.conn._retrieve_connection_info(async_params)
185
            if schnet[1].endswith('v1'):
186
                self.assertEquals(r, (schnet[0], schnet[1][:-3]))
187
            else:
188
                self.assertEquals(r, schnet)
189
            self.assertEquals(
190
                '%s://%s/%s' % (schnet[0], schnet[1], expected),
191
                self.conn.url)
192

  
193
    def test_perform_request(self):
194
        from httplib import HTTPConnection
195
        from objpool import http
196
        pr = self.conn.perform_request
197
        kwargs = dict(
198
            data='',
199
            method='GET',
200
            async_headers=dict(),
201
            async_params=dict())
202
        utf_test = u'\u03a6\u03bf\u03cd\u03c4\u03c3\u03bf\u03c2'
203
        utf_dict = dict(utf=utf_test)
204
        ascii_dict = dict(ascii1='myAscii', ascii2=None)
205
        kwargs0 = dict(
206
            data='',
207
            method='get',
208
            async_headers=utf_dict,
209
            async_params=ascii_dict)
210

  
211
        def get_expected():
212
            expected = []
213
            for k, v in kwargs0['async_params'].items():
214
                v = _encode(v)
215
                expected.append(('%s=%s' % (k, v)) if v else ('%s' % k))
216
            return '&'.join(expected)
217

  
218
        KCError = errors.KamakiConnectionError
219
        fakecon = HTTPConnection('X', 'Y')
220

  
221
        with patch.object(http, 'get_http_connection', return_value=fakecon):
222
            with patch.object(HTTPConnection, 'request') as request:
223
                r = pr(**kwargs)
224
                self.assertTrue(isinstance(r, kamakicon.KamakiHTTPResponse))
225
                self.assertEquals(
226
                    request.mock_calls[-1],
227
                    call(body='', headers={}, url='/', method='GET'))
228

  
229
                pr(**kwargs0)
230

  
231
                exp_headers = dict(kwargs0['async_headers'])
232
                exp_headers['utf'] = _encode(exp_headers['utf'])
233

  
234
                self.assertEquals(
235
                    request.mock_calls[-1],
236
                    call(
237
                        body=kwargs0['data'],
238
                        headers=exp_headers,
239
                        url='/?%s' % get_expected(),
240
                        method=kwargs0['method'].upper()))
241

  
242
                self.conn = kamakicon.KamakiHTTPConnection()
243
                (kwargs0['async_params'], kwargs0['async_headers']) = (
244
                    kwargs0['async_headers'], kwargs0['async_params'])
245
                kwargs0['async_headers']['ascii2'] = 'None'
246
                self.conn.perform_request(**kwargs0)
247
                self.assertEquals(
248
                    request.mock_calls[-1],
249
                    call(
250
                        body=kwargs0['data'],
251
                        headers=kwargs0['async_headers'],
252
                        url='/?%s' % get_expected(),
253
                        method=kwargs0['method'].upper()))
254

  
255
            err = IOError('IO Error')
256
            with patch.object(HTTPConnection, 'request', side_effect=err):
257
                self.assertRaises(KCError, pr, **kwargs)
258

  
259
        err = ValueError('Cannot Establish connection')
260
        with patch.object(http, 'get_http_connection', side_effect=err):
261
            self.assertRaises(KCError, pr, **kwargs)
262

  
263
        err = Exception('Any other error')
264
        with patch.object(http, 'get_http_connection', side_effect=err):
265
            self.assertRaises(KCError, pr, **kwargs)
266

  
267

  
268
class KamakiHTTPResponse(TestCase):
269

  
270
    class fakeResponse(object):
271
        sample = 'sample string'
272
        getheaders = Mock(return_value={})
273
        read = Mock(return_value=sample)
274
        status = Mock(return_value=None)
275
        reason = Mock(return_value=None)
276

  
277
    def setUp(self):
278
        from httplib import HTTPConnection
279
        self.HTC = HTTPConnection
280
        self.FR = self.fakeResponse
281

  
282
    def test_text(self):
283
        with patch.object(self.HTC, 'getresponse', return_value=self.FR()):
284
            self.resp = kamakicon.KamakiHTTPResponse(self.HTC('X', 'Y'))
285
            self.assertEquals(self.resp.text, self.FR.sample)
286
            sample2 = 'some other string'
287
            self.resp.text = sample2
288
            self.assertNotEquals(self.resp.text, sample2)
289

  
290
    def test_json(self):
291
        with patch.object(self.HTC, 'getresponse', return_value=self.FR()):
292
            self.resp = kamakicon.KamakiHTTPResponse(self.HTC('X', 'Y'))
293
            self.assertRaises(errors.KamakiResponseError, self.resp.json)
294
            sample2 = '{"antoher":"sample", "formated":"in_json"}'
295
            with patch.object(self.FR, 'read', return_value=sample2):
296
                self.resp = kamakicon.KamakiHTTPResponse(self.HTC('X', 'Y'))
297
                from json import loads
298
                self.assertEquals(loads(sample2), self.resp.json)
299

  
300
    def test_pool_lock(self):
301
        exceptions_left = 100
302
        while exceptions_left:
303
            kre = errors.KamakiResponseError
304
            with patch.object(self.HTC, 'close', return_value=True):
305
                self.resp = kamakicon.KamakiHTTPResponse(self.HTC('X', 'Y'))
306
                if randrange(10):
307
                    with patch.object(
308
                            self.HTC,
309
                            'getresponse',
310
                            return_value=self.FR()):
311
                        self.assertEquals(self.resp.text, self.FR.sample)
312
                else:
313
                    with patch.object(
314
                            self.HTC,
315
                            'getresponse',
316
                            side_effect=kre('A random error')):
317
                        try:
318
                            self.resp.text
319
                        except kre:
320
                            exceptions_left -= 1
321
                        else:
322
                            self.assertTrue(False)
323
                self.HTC.close.assert_called_with()
324

  
325

  
326
class KamakiResponse(TestCase):
327

  
328
    def setUp(self):
329
        self.resp = connection.KamakiResponse(
330
            'Abstract class, so test with fake request (str)')
331

  
332
    def _mock_get_response(foo):
333
        def mocker(self):
334
            self.resp._get_response = Mock()
335
            foo(self)
336
        return mocker
337

  
338
    def test_release(self):
339
        self.assertRaises(NotImplementedError, self.resp.release)
340

  
341
    def test_prefetched(self):
342
        self.assertFalse(self.resp.prefetched)
343
        self.resp.prefetched = True
344
        self.assertTrue(self.resp.prefetched)
345

  
346
    @_mock_get_response
347
    def test_content(self):
348
        rsp = self.resp
349
        for cont in ('Sample Content', u'\u03c7\u03cd\u03bd\u03c9\x00'):
350
            rsp.content = cont
351
            self.assertEquals(rsp.content, cont)
352

  
353
    (
354
        test_text,
355
        test_json,
356
        test_headers,
357
        test_status,
358
        test_status_code) = 5 * (test_content,)
359

  
360
    def test_request(self):
361
        r = self.resp.request
362
        self.assertTrue(isinstance(r, str))
363

  
364

  
365
if __name__ == '__main__':
366
    from sys import argv
367
    from kamaki.clients.test import runTestCase
368
    classes = dict(
369
        KamakiConnection=KamakiConnection,
370
        KamakiHTTPConnection=KamakiHTTPConnection,
371
        KamakiHTTPResponse=KamakiHTTPResponse,
372
        KamakiResponse=KamakiResponse)
373
    not_found = True
374
    for k, cls in classes.items():
375
        if not argv[1:] or argv[1] == k:
376
            not_found = False
377
            runTestCase(cls, '%s Client' % k, argv[2:])
378
    if not_found:
379
        print('TestCase %s not found' % argv[1])
b/kamaki/clients/cyclades/__init__.py
47 47
        :param server_id: integer (str or int)
48 48
        """
49 49
        req = {'start': {}}
50
        r = self.servers_post(server_id, 'action', json_data=req, success=202)
51
        r.release()
50
        self.servers_post(server_id, 'action', json_data=req, success=202)
52 51

  
53 52
    def shutdown_server(self, server_id):
54 53
        """Submit a shutdown request
......
56 55
        :param server_id: integer (str or int)
57 56
        """
58 57
        req = {'shutdown': {}}
59
        r = self.servers_post(server_id, 'action', json_data=req, success=202)
60
        r.release()
58
        self.servers_post(server_id, 'action', json_data=req, success=202)
61 59

  
62 60
    def get_server_console(self, server_id):
63 61
        """
......
93 91
        :param profile: (str) ENABLED | DISABLED | PROTECTED
94 92
        """
95 93
        req = {'firewallProfile': {'profile': profile}}
96
        r = self.servers_post(server_id, 'action', json_data=req, success=202)
97
        r.release()
94
        self.servers_post(server_id, 'action', json_data=req, success=202)
98 95

  
99 96
    def list_servers(self, detail=False, changes_since=None):
100 97
        """
......
189 186
        :param new_name: (str)
190 187
        """
191 188
        req = {'network': {'name': new_name}}
192
        r = self.networks_put(network_id=network_id, json_data=req)
193
        r.release()
189
        self.networks_put(network_id=network_id, json_data=req)
194 190

  
195 191
    def delete_network(self, network_id):
196 192
        """
......
199 195
        :raises ClientError: 421 Network in use
200 196
        """
201 197
        try:
202
            r = self.networks_delete(network_id)
198
            self.networks_delete(network_id)
203 199
        except ClientError as err:
204 200
            if err.status == 421:
205 201
                err.details = [
206 202
                    'Network may be still connected to at least one server']
207 203
            raise err
208
        r.release()
209 204

  
210 205
    def connect_server(self, server_id, network_id):
211 206
        """ Connect a server to a network
......
215 210
        :param network_id: integer (str or int)
216 211
        """
217 212
        req = {'add': {'serverRef': server_id}}
218
        r = self.networks_post(network_id, 'action', json_data=req)
219
        r.release()
213
        self.networks_post(network_id, 'action', json_data=req)
220 214

  
221 215
    def disconnect_server(self, server_id, nic_id):
222 216
        """
......
232 226
                net['id'],
233 227
                net['network_id']) for net in vm_nets if nic_id == net['id']]:
234 228
            req = {'remove': {'attachment': '%s' % nic_id}}
235
            r = self.networks_post(network_id, 'action', json_data=req)
236
            r.release()
229
            self.networks_post(network_id, 'action', json_data=req)
237 230
            num_of_disconnections += 1
238 231
        return num_of_disconnections
239 232

  
......
243 236
        """
244 237
        for nic in self.list_network_nics(netid):
245 238
            req = dict(remove=dict(attachment=nic))
246
            r = self.networks_post(netid, 'action', json_data=req)
247
            r.release()
239
            self.networks_post(netid, 'action', json_data=req)
248 240

  
249 241
    def wait_server(
250 242
            self,
b/kamaki/clients/cyclades/test.py
87 87
    status = None
88 88
    status_code = 200
89 89

  
90
    def release(self):
91
        pass
92

  
93 90
rest_pkg = 'kamaki.clients.cyclades.CycladesRestClient'
94 91
cyclades_pkg = 'kamaki.clients.cyclades.CycladesClient'
95 92

  
b/kamaki/clients/image/__init__.py
124 124
        for key, val in properties.items():
125 125
            async_headers['x-image-meta-property-%s' % key] = val
126 126

  
127
        r = self.post(path, success=200, async_headers=async_headers)
128
        r.release()
127
        self.post(path, success=200, async_headers=async_headers)
129 128

  
130 129
    def list_members(self, image_id):
131 130
        """
......
155 154
        :param member: (str) user to allow access to current user's images
156 155
        """
157 156
        path = path4url('images', image_id, 'members', member)
158
        r = self.put(path, success=204)
159
        r.release()
157
        self.put(path, success=204)
160 158

  
161 159
    def remove_member(self, image_id, member):
162 160
        """
......
165 163
        :param member: (str) user to deprive from current user's images
166 164
        """
167 165
        path = path4url('images', image_id, 'members', member)
168
        r = self.delete(path, success=204)
169
        r.release()
166
        self.delete(path, success=204)
170 167

  
171 168
    def set_members(self, image_id, members):
172 169
        """
......
176 173
        """
177 174
        path = path4url('images', image_id, 'members')
178 175
        req = {'memberships': [{'member_id': member} for member in members]}
179
        r = self.put(path, json=req, success=204)
180
        r.release()
176
        self.put(path, json=req, success=204)
b/kamaki/clients/image/test.py
132 132
    status = None
133 133
    status_code = 200
134 134

  
135
    def release(self):
136
        pass
137

  
138 135
image_pkg = 'kamaki.clients.image.ImageClient'
139 136

  
140 137

  
b/kamaki/clients/livetest/__init__.py
169 169
    return parser
170 170

  
171 171

  
172
"""
172 173
class Connection(Generic):
173 174

  
174 175
    def setUp(self):
......
212 213
                            assert False, 'Exception not raised as expected'
213 214
                response.request.close.assert_called_once_with()
214 215
            response.request.close()
216
"""
215 217

  
216 218

  
217 219
def main(argv, config=None):
218 220
    suiteFew = TestSuite()
219
    if len(argv) == 0 or argv[0] == 'connection':
220
        test_method = 'test_%s' % (argv[1] if len(argv) > 1 else '000')
221
        suiteFew.addTest(Connection(test_method, config))
221
    #if len(argv) == 0 or argv[0] == 'connection':
222
    #    test_method = 'test_%s' % (argv[1] if len(argv) > 1 else '000')
223
    #    suiteFew.addTest(Connection(test_method, config))
222 224
    if len(argv) == 0 or argv[0] == 'pithos':
223 225
        from kamaki.clients.livetest.pithos import Pithos
224 226
        test_method = 'test_%s' % (argv[1] if len(argv) > 1 else '000')
b/kamaki/clients/livetest/pithos.py
154 154
                if_modified_since=now_formated,
155 155
                success=(204, 304, 412))
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff