Revision 55faa0bc

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

  
34 34
from kamaki.clients import ClientError
35
from kamaki.clients.compute.rest_api import ComputeClientApi
35
from kamaki.clients.compute.rest_api import ComputeRestClient
36 36
from kamaki.clients.utils import path4url
37 37

  
38 38

  
39
class ComputeClient(ComputeClientApi):
39
class ComputeClient(ComputeRestClient):
40 40
    """OpenStack Compute API 1.1 client"""
41 41

  
42 42
    def list_servers(self, detail=False):
b/kamaki/clients/compute/rest_api.py
36 36
import json
37 37

  
38 38

  
39
class ComputeClientApi(Client):
39
class ComputeRestClient(Client):
40 40

  
41 41
    def servers_get(self, server_id='', command='', success=200, **kwargs):
42 42
        """GET base_url/servers[/server_id][/command] request
b/kamaki/clients/compute/test.py
36 36
from itertools import product
37 37
from json import dumps
38 38

  
39
from kamaki.clients.compute import ComputeClient, ComputeClientApi
39
from kamaki.clients.compute import ComputeClient, ComputeRestClient
40 40
from kamaki.clients import ClientError
41 41

  
42 42

  
43
rest_pkg = 'kamaki.clients.compute.rest_api.ComputeClientApi'
43
rest_pkg = 'kamaki.clients.compute.rest_api.ComputeRestClient'
44 44
compute_pkg = 'kamaki.clients.compute.ComputeClient'
45 45

  
46 46
img_ref = "1m4g3-r3f3r3nc3"
......
111 111
        pass
112 112

  
113 113

  
114
class ComputeRestApi(TestCase):
114
class ComputeRest(TestCase):
115 115

  
116
    """Set up a ComputesRestApi thorough test"""
116
    """Set up a ComputesRest thorough test"""
117 117
    def setUp(self):
118 118
        self.url = 'http://cyclades.example.com'
119 119
        self.token = 'cyc14d3s70k3n'
120
        self.client = ComputeClientApi(self.url, self.token)
120
        self.client = ComputeRestClient(self.url, self.token)
121 121

  
122 122
    def tearDown(self):
123 123
        FR.json = vm_recv
......
447 447
    if not argv[1:] or argv[1] == 'Compute':
448 448
        not_found = False
449 449
        runTestCase(Compute, 'Compute Client', argv[2:])
450
    if not argv[1:] or argv[1] == 'ComputeRestApi':
450
    if not argv[1:] or argv[1] == 'ComputeRest':
451 451
        not_found = False
452
        runTestCase(ComputeRestApi, 'ComputeRestApi Client', argv[2:])
452
        runTestCase(ComputeRest, 'ComputeRest Client', argv[2:])
453 453
    if not_found:
454 454
        print('TestCase %s not found' % argv[1])
b/kamaki/clients/cyclades/__init__.py
34 34
from sys import stdout
35 35
from time import sleep
36 36

  
37
from kamaki.clients.cyclades.rest_api import CycladesClientApi
37
from kamaki.clients.cyclades.rest_api import CycladesRestClient
38 38
from kamaki.clients import ClientError
39 39

  
40 40

  
41
class CycladesClient(CycladesClientApi):
41
class CycladesClient(CycladesRestClient):
42 42
    """GRNet Cyclades API client"""
43 43

  
44 44
    def start_server(self, server_id):
b/kamaki/clients/cyclades/rest_api.py
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, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this 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 kamaki.clients.compute import ComputeClient
35
from kamaki.clients.utils import path4url
36
import json
37

  
38

  
39
class CycladesRestClient(ComputeClient):
40
    """GRNet Cyclades REST API Client"""
41

  
42
    def servers_get(
43
            self,
44
            server_id='',
45
            command='',
46
            success=200,
47
            changes_since=None,
48
            **kwargs):
49
        """GET base_url/servers[/server_id][/command] request
50

  
51
        :param server_id: integer (as int or str)
52

  
53
        :param command: 'ips', 'stats', or ''
54

  
55
        :param success: success code or list or tupple of accepted success
56
            codes. if server response code is not in this list, a ClientError
57
            raises
58

  
59
        :param changes_since: (date)
60

  
61
        :returns: request response
62
        """
63
        path = path4url('servers', server_id, command)
64
        self.set_param('changes-since', changes_since, changes_since)
65
        return self.get(path, success=success, **kwargs)
66

  
67
    def networks_get(
68
            self,
69
            network_id='',
70
            command='',
71
            success=(200, 203),
72
            **kwargs):
73
        """GET base_url/networks[/network_id][/command] request
74

  
75
        :param network_id: integer (str or int)
76

  
77
        :param command: (str) 'detail' or ''
78

  
79
        :param success: success code or list or tuple of accepted success
80
            codes. if server response code is not in this list, a ClientError
81
            raises
82

  
83
        :returns: request response
84
        """
85
        path = path4url('networks', network_id, command)
86
        return self.get(path, success=success, **kwargs)
87

  
88
    def networks_delete(
89
            self,
90
            network_id='',
91
            command='',
92
            success=204,
93
            **kwargs):
94
        """DEL ETE base_url/networks[/network_id][/command] request
95

  
96
        :param network_id: integer (str or int)
97

  
98
        :param command: (str) 'detail' or ''
99

  
100
        :param success: success code or list or tuple of accepted success
101
            codes. if server response code is not in this list, a ClientError
102
            raises
103

  
104
        :returns: request response
105
        """
106
        path = path4url('networks', network_id, command)
107
        return self.delete(path, success=success, **kwargs)
108

  
109
    def networks_post(
110
            self,
111
            network_id='',
112
            command='',
113
            json_data=None,
114
            success=202,
115
            **kwargs):
116
        """POST base_url/servers[/server_id]/[command] request
117

  
118
        :param network_id: integer (str or int)
119

  
120
        :param command: (str) 'detail' or ''
121

  
122
        :param json_data: (dict) will be send as data
123

  
124
        :param success: success code or list or tuple of accepted success
125
            codes. if server response code is not in this list, a ClientError
126
            raises
127

  
128
        :returns: request response
129
        """
130
        data = json_data
131
        if json_data is not None:
132
            data = json.dumps(json_data)
133
            self.set_header('Content-Type', 'application/json')
134
            self.set_header('Content-Length', len(data))
135

  
136
        path = path4url('networks', network_id, command)
137
        return self.post(path, data=data, success=success, **kwargs)
138

  
139
    def networks_put(
140
            self,
141
            network_id='',
142
            command='',
143
            json_data=None,
144
            success=204,
145
            **kwargs):
146
        """PUT base_url/servers[/server_id]/[command] request
147

  
148
        :param network_id: integer (str or int)
149

  
150
        :param command: (str) 'detail' or ''
151

  
152
        :param json_data: (dict) will be send as data
153

  
154
        :param success: success code or list or tuple of accepted success
155
            codes. if server response code is not in this list, a ClientError
156
            raises
157

  
158
        :returns: request response
159
        """
160
        data = json_data
161
        if json_data is not None:
162
            data = json.dumps(json_data)
163
            self.set_header('Content-Type', 'application/json')
164
            self.set_header('Content-Length', len(data))
165

  
166
        path = path4url('networks', network_id, command)
167
        return self.put(path, data=data, success=success, **kwargs)
b/kamaki/clients/cyclades/test.py
35 35
from itertools import product
36 36

  
37 37
from kamaki.clients import ClientError
38
from kamaki.clients.cyclades import CycladesClient, CycladesClientApi
38
from kamaki.clients.cyclades import CycladesClient, CycladesRestClient
39 39

  
40 40
img_ref = "1m4g3-r3f3r3nc3"
41 41
vm_name = "my new VM"
......
90 90
    def release(self):
91 91
        pass
92 92

  
93
rest_pkg = 'kamaki.clients.cyclades.CycladesClientApi'
93
rest_pkg = 'kamaki.clients.cyclades.CycladesRestClient'
94 94
cyclades_pkg = 'kamaki.clients.cyclades.CycladesClient'
95 95

  
96 96

  
97
class CycladesRestApi(TestCase):
97
class CycladesRest(TestCase):
98 98

  
99 99
    """Set up a Cyclades thorough test"""
100 100
    def setUp(self):
101 101
        self.url = 'http://cyclades.example.com'
102 102
        self.token = 'cyc14d3s70k3n'
103
        self.client = CycladesClientApi(self.url, self.token)
103
        self.client = CycladesRestClient(self.url, self.token)
104 104

  
105 105
    def tearDown(self):
106 106
        FR.json = vm_recv
......
444 444
    if not argv[1:] or argv[1] == 'Cyclades':
445 445
        not_found = False
446 446
        runTestCase(Cyclades, 'Cyclades Client', argv[2:])
447
    if not argv[1:] or argv[1] == 'CycladesRestApi':
447
    if not argv[1:] or argv[1] == 'CycladesRest':
448 448
        not_found = False
449
        runTestCase(CycladesRestApi, 'CycladesRestApi Client', argv[2:])
449
        runTestCase(CycladesRest, 'CycladesRest Client', argv[2:])
450 450
    if not_found:
451 451
        print('TestCase %s not found' % argv[1])
b/kamaki/clients/pithos/__init__.py
40 40
from binascii import hexlify
41 41

  
42 42
from kamaki.clients import SilentEvent, sendlog
43
from kamaki.clients.pithos_rest_api import PithosRestAPI
43
from kamaki.clients.pithos.rest_api import PithosRestClient
44 44
from kamaki.clients.storage import ClientError
45 45
from kamaki.clients.utils import path4url, filter_in
46 46
from StringIO import StringIO
......
65 65
    return (start, end)
66 66

  
67 67

  
68
class PithosClient(PithosRestAPI):
68
class PithosClient(PithosRestClient):
69 69
    """GRNet Pithos API client"""
70 70

  
71 71
    def __init__(self, base_url, token, account=None, container=None):
b/kamaki/clients/pithos/rest_api.py
1
# Copyright 2012-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, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this 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 kamaki.clients.storage import StorageClient
35
from kamaki.clients.utils import path4url
36

  
37

  
38
class PithosRestClient(StorageClient):
39

  
40
    def account_head(
41
            self,
42
            until=None,
43
            if_modified_since=None,
44
            if_unmodified_since=None,
45
            *args, **kwargs):
46
        """ Full Pithos+ HEAD at account level
47

  
48
        --- request parameters ---
49

  
50
        :param until: (string) optional timestamp
51

  
52
        --- request headers ---
53

  
54
        :param if_modified_since: (string) Retrieve if account has changed
55
            since provided timestamp
56

  
57
        :param if_unmodified_since: (string) Retrieve if account has not
58
            change since provided timestamp
59

  
60
        :returns: ConnectionResponse
61
        """
62

  
63
        self._assert_account()
64
        path = path4url(self.account)
65

  
66
        self.set_param('until', until, iff=until)
67
        self.set_header('If-Modified-Since', if_modified_since)
68
        self.set_header('If-Unmodified-Since', if_unmodified_since)
69

  
70
        success = kwargs.pop('success', 204)
71
        return self.head(path, *args, success=success, **kwargs)
72

  
73
    def account_get(
74
            self,
75
            limit=None,
76
            marker=None,
77
            format='json',
78
            show_only_shared=False,
79
            until=None,
80
            if_modified_since=None,
81
            if_unmodified_since=None,
82
            *args, **kwargs):
83
        """  Full Pithos+ GET at account level
84

  
85
        --- request parameters ---
86

  
87
        :param limit: (integer) The amount of results requested
88
            (server will use default value if None)
89

  
90
        :param marker: string Return containers with name
91
            lexicographically after marker
92

  
93
        :param format: (string) reply format can be json or xml
94
            (default: json)
95

  
96
        :param shared: (bool) If true, only shared containers will be
97
            included in results
98

  
99
        :param until: (string) optional timestamp
100

  
101
        --- request headers ---
102

  
103
        :param if_modified_since: (string) Retrieve if account has changed
104
            since provided timestamp
105

  
106
        :param if_unmodified_since: (string) Retrieve if account has not
107
            changed since provided timestamp
108

  
109
        :returns: ConnectionResponse
110
        """
111

  
112
        self._assert_account()
113

  
114
        self.set_param('format', format, iff=format)
115
        self.set_param('limit', limit, iff=limit)
116
        self.set_param('marker', marker, iff=marker)
117
        self.set_param('shared', iff=show_only_shared)
118
        self.set_param('until', until, iff=until)
119

  
120
        self.set_header('If-Modified-Since', if_modified_since)
121
        self.set_header('If-Unmodified-Since', if_unmodified_since)
122

  
123
        path = path4url(self.account)
124
        success = kwargs.pop('success', (200, 204))
125
        return self.get(path, *args, success=success, **kwargs)
126

  
127
    def account_post(
128
            self,
129
            update=True,
130
            groups={},
131
            metadata=None,
132
            quota=None,
133
            versioning=None,
134
            *args, **kwargs):
135
        """ Full Pithos+ POST at account level
136

  
137
        --- request parameters ---
138

  
139
        :param update: (bool) if True, Do not replace metadata/groups
140

  
141
        --- request headers ---
142

  
143
        :param groups: (dict) Optional user defined groups in the form
144
            { 'group1':['user1', 'user2', ...],
145
            'group2':['userA', 'userB', ...], }
146

  
147
        :param metadata: (dict) Optional user defined metadata in the form
148
            { 'name1': 'value1', 'name2': 'value2', ... }
149

  
150
        :param quota: (integer) If supported, sets the Account quota
151

  
152
        :param versioning: (string) If supported, sets the Account versioning
153
            to 'auto' or some other supported versioning string
154

  
155
        :returns: ConnectionResponse
156
        """
157

  
158
        self._assert_account()
159

  
160
        self.set_param('update', iff=update)
161

  
162
        if groups:
163
            for group, usernames in groups.items():
164
                userstr = ''
165
                dlm = ''
166
                for user in usernames:
167
                    userstr = userstr + dlm + user
168
                    dlm = ','
169
                self.set_header('X-Account-Group-' + group, userstr)
170
        if metadata:
171
            for metaname, metaval in metadata.items():
172
                self.set_header('X-Account-Meta-' + metaname, metaval)
173
        self.set_header('X-Account-Policy-Quota', quota)
174
        self.set_header('X-Account-Policy-Versioning', versioning)
175

  
176
        path = path4url(self.account)
177
        success = kwargs.pop('success', 202)
178
        return self.post(path, *args, success=success, **kwargs)
179

  
180
    def container_head(
181
            self,
182
            until=None,
183
            if_modified_since=None,
184
            if_unmodified_since=None,
185
            *args, **kwargs):
186
        """ Full Pithos+ HEAD at container level
187

  
188
        --- request params ---
189

  
190
        :param until: (string) optional timestamp
191

  
192
        --- request headers ---
193

  
194
        :param if_modified_since: (string) Retrieve if account has changed
195
            since provided timestamp
196

  
197
        :param if_unmodified_since: (string) Retrieve if account has not
198
            changed since provided timestamp
199

  
200
        :returns: ConnectionResponse
201
        """
202

  
203
        self._assert_container()
204

  
205
        self.set_param('until', until, iff=until)
206

  
207
        self.set_header('If-Modified-Since', if_modified_since)
208
        self.set_header('If-Unmodified-Since', if_unmodified_since)
209

  
210
        path = path4url(self.account, self.container)
211
        success = kwargs.pop('success', 204)
212
        return self.head(path, *args, success=success, **kwargs)
213

  
214
    def container_get(
215
            self,
216
            limit=None,
217
            marker=None,
218
            prefix=None,
219
            delimiter=None,
220
            path=None,
221
            format='json',
222
            meta=[],
223
            show_only_shared=False,
224
            until=None,
225
            if_modified_since=None,
226
            if_unmodified_since=None,
227
            *args, **kwargs):
228
        """ Full Pithos+ GET at container level
229

  
230
        --- request parameters ---
231

  
232
        :param limit: (integer) The amount of results requested
233
            (server will use default value if None)
234

  
235
        :param marker: (string) Return containers with name lexicographically
236
            after marker
237

  
238
        :param prefix: (string) Return objects starting with prefix
239

  
240
        :param delimiter: (string) Return objects up to the delimiter
241

  
242
        :param path: (string) assume prefix = path and delimiter = /
243
            (overwrites prefix and delimiter)
244

  
245
        :param format: (string) reply format can be json or xml (default:json)
246

  
247
        :param meta: (list) Return objects that satisfy the key queries in
248
            the specified comma separated list (use <key>, !<key> for
249
            existence queries, <key><op><value> for value queries, where <op>
250
            can be one of =, !=, <=, >=, <, >)
251

  
252
        :param shared: (bool) If true, only shared containers will be included
253
            in results
254

  
255
        :param until: (string) optional timestamp
256

  
257
        --- request headers ---
258

  
259
        :param if_modified_since: (string) Retrieve if account has changed
260
            since provided timestamp
261

  
262
        :param if_unmodified_since: (string) Retrieve if account has not
263
            changed since provided timestamp
264

  
265
        :returns: ConnectionResponse
266
        """
267

  
268
        self._assert_container()
269

  
270
        self.set_param('format', format, iff=format)
271
        self.set_param('limit', limit, iff=limit)
272
        self.set_param('marker', marker, iff=marker)
273
        if not path:
274
            self.set_param('prefix', prefix, iff=prefix)
275
            self.set_param('delimiter', delimiter, iff=delimiter)
276
        else:
277
            self.set_param('path', path)
278
        self.set_param('shared', iff=show_only_shared)
279
        if meta:
280
            self.set_param('meta',  ','.join(meta))
281
        self.set_param('until', until, iff=until)
282

  
283
        self.set_header('If-Modified-Since', if_modified_since)
284
        self.set_header('If-Unmodified-Since', if_unmodified_since)
285

  
286
        path = path4url(self.account, self.container)
287
        success = kwargs.pop('success', 200)
288
        return self.get(path, *args, success=success, **kwargs)
289

  
290
    def container_put(
291
            self,
292
            quota=None, versioning=None, metadata=None,
293
            *args, **kwargs):
294
        """ Full Pithos+ PUT at container level
295

  
296
        --- request headers ---
297

  
298
        :param quota: (integer) Size limit in KB
299

  
300
        :param versioning: (string) 'auto' or other string supported by server
301

  
302
        :param metadata: (dict) Optional user defined metadata in the form
303
            { 'name1': 'value1', 'name2': 'value2', ... }
304

  
305
        :returns: ConnectionResponse
306
        """
307
        self._assert_container()
308

  
309
        if metadata:
310
            for metaname, metaval in metadata.items():
311
                self.set_header('X-Container-Meta-' + metaname, metaval)
312
        self.set_header('X-Container-Policy-Quota', quota)
313
        self.set_header('X-Container-Policy-Versioning', versioning)
314

  
315
        path = path4url(self.account, self.container)
316
        success = kwargs.pop('success', (201, 202))
317
        return self.put(path, *args, success=success, **kwargs)
318

  
319
    def container_post(
320
            self,
321
            update=True,
322
            format='json',
323
            quota=None,
324
            versioning=None,
325
            metadata=None,
326
            content_type=None,
327
            content_length=None,
328
            transfer_encoding=None,
329
            *args, **kwargs):
330
        """ Full Pithos+ POST at container level
331

  
332
        --- request params ---
333

  
334
        :param update: (bool)  if True, Do not replace metadata/groups
335

  
336
        :param format: (string) json (default) or xml
337

  
338
        --- request headers ---
339

  
340
        :param quota: (integer) Size limit in KB
341

  
342
        :param versioning: (string) 'auto' or other string supported by server
343

  
344
        :param metadata: (dict) Optional user defined metadata in the form
345
            { 'name1': 'value1', 'name2': 'value2', ... }
346

  
347
        :param content_type: (string) set a custom content type
348

  
349
        :param content_length: (string) set a custrom content length
350

  
351
        :param transfer_encoding: (string) set a custrom transfer encoding
352

  
353
        :returns: ConnectionResponse
354
        """
355
        self._assert_container()
356

  
357
        self.set_param('format', format, iff=format)
358
        self.set_param('update', iff=update)
359

  
360
        if metadata:
361
            for metaname, metaval in metadata.items():
362
                self.set_header('X-Container-Meta-' + metaname, metaval)
363
        self.set_header('X-Container-Policy-Quota', quota)
364
        self.set_header('X-Container-Policy-Versioning', versioning)
365
        self.set_header('Content-Type', content_type)
366
        self.set_header('Content-Length', content_length)
367
        self.set_header('Transfer-Encoding', transfer_encoding)
368

  
369
        path = path4url(self.account, self.container)
370
        success = kwargs.pop('success', 202)
371
        return self.post(path, *args, success=success, **kwargs)
372

  
373
    def container_delete(self, until=None, delimiter=None, *args, **kwargs):
374
        """ Full Pithos+ DELETE at container level
375

  
376
        --- request parameters ---
377

  
378
        :param until: (timestamp string) if defined, container is purged up to
379
            that time
380

  
381
        :returns: ConnectionResponse
382
        """
383

  
384
        self._assert_container()
385

  
386
        self.set_param('until', until, iff=until)
387
        self.set_param('delimiter', delimiter, iff=delimiter)
388

  
389
        path = path4url(self.account, self.container)
390
        success = kwargs.pop('success', 204)
391
        return self.delete(path, success=success)
392

  
393
    def object_head(
394
            self, object,
395
            version=None,
396
            if_etag_match=None,
397
            if_etag_not_match=None,
398
            if_modified_since=None,
399
            if_unmodified_since=None,
400
            *args, **kwargs):
401
        """ Full Pithos+ HEAD at object level
402

  
403
        --- request parameters ---
404

  
405
        :param version: (string) optional version identified
406

  
407
        --- request headers ---
408

  
409
        :param if_etag_match: (string) if provided, return only results
410
            with etag matching with this
411

  
412
        :param if_etag_not_match: (string) if provided, return only results
413
            with etag not matching with this
414

  
415
        :param if_modified_since: (string) Retrieve if account has changed
416
            since provided timestamp
417

  
418
        :param if_unmodified_since: (string) Retrieve if account has not
419
            changed since provided timestamp
420

  
421
        :returns: ConnectionResponse
422
        """
423

  
424
        self._assert_container()
425

  
426
        self.set_param('version', version, iff=version)
427

  
428
        self.set_header('If-Match', if_etag_match)
429
        self.set_header('If-None-Match', if_etag_not_match)
430
        self.set_header('If-Modified-Since', if_modified_since)
431
        self.set_header('If-Unmodified-Since', if_unmodified_since)
432

  
433
        path = path4url(self.account, self.container, object)
434
        success = kwargs.pop('success', 200)
435
        return self.head(path, *args, success=success, **kwargs)
436

  
437
    def object_get(
438
            self, object,
439
            format='json',
440
            hashmap=False,
441
            version=None,
442
            data_range=None,
443
            if_range=False,
444
            if_etag_match=None,
445
            if_etag_not_match=None,
446
            if_modified_since=None,
447
            if_unmodified_since=None,
448
            *args, **kwargs):
449
        """ Full Pithos+ GET at object level
450

  
451
        --- request parameters ---
452

  
453
        :param format: (string) json (default) or xml
454

  
455
        :param hashmap: (bool) Optional request for hashmap
456

  
457
        :param version: (string) optional version identified
458

  
459
        --- request headers ---
460

  
461
        :param data_range: (string) Optional range of data to retrieve
462

  
463
        :param if_range: (bool)
464

  
465
        :param if_etag_match: (string) if provided, return only results
466
            with etag matching with this
467

  
468
        :param if_etag_not_match: (string) if provided, return only results
469
            with etag not matching with this
470

  
471
        :param if_modified_since: (string) Retrieve if account has changed
472
            since provided timestamp
473

  
474
        :param if_unmodified_since: (string) Retrieve if account has not
475
            changed since provided timestamp
476

  
477
        :returns: ConnectionResponse
478
        """
479

  
480
        self._assert_container()
481

  
482
        self.set_param('format', format, iff=format)
483
        self.set_param('version', version, iff=version)
484
        self.set_param('hashmap', hashmap, iff=hashmap)
485

  
486
        self.set_header('Range', data_range)
487
        self.set_header('If-Range', '', if_range and data_range)
488
        self.set_header('If-Match', if_etag_match, )
489
        self.set_header('If-None-Match', if_etag_not_match)
490
        self.set_header('If-Modified-Since', if_modified_since)
491
        self.set_header('If-Unmodified-Since', if_unmodified_since)
492

  
493
        path = path4url(self.account, self.container, object)
494
        success = kwargs.pop('success', 200)
495
        return self.get(path, *args, success=success, **kwargs)
496

  
497
    def object_put(
498
            self, object,
499
            format='json',
500
            hashmap=False,
501
            delimiter=None,
502
            if_etag_match=None,
503
            if_etag_not_match=None,
504
            etag=None,
505
            content_length=None,
506
            content_type=None,
507
            transfer_encoding=None,
508
            copy_from=None,
509
            move_from=None,
510
            source_account=None,
511
            source_version=None,
512
            content_encoding=None,
513
            content_disposition=None,
514
            manifest=None,
515
            permissions=None,
516
            public=None,
517
            metadata=None,
518
            *args, **kwargs):
519
        """ Full Pithos+ PUT at object level
520

  
521
        --- request parameters ---
522

  
523
        :param format: (string) json (default) or xml
524

  
525
        :param hashmap: (bool) Optional hashmap provided instead of data
526

  
527
        --- request headers ---
528

  
529
        :param if_etag_match: (string) if provided, return only results
530
            with etag matching with this
531

  
532
        :param if_etag_not_match: (string) if provided, return only results
533
            with etag not matching with this
534

  
535
        :param etag: (string) The MD5 hash of the object (optional to check
536
            written data)
537

  
538
        :param content_length: (integer) The size of the data written
539

  
540
        :param content_type: (string) The MIME content type of the object
541

  
542
        :param transfer_encoding: (string) Set to chunked to specify
543
            incremental uploading (if used, Content-Length is ignored)
544

  
545
        :param copy_from: (string) The source path in the form
546
            /<container>/<object>
547

  
548
        :param move_from: (string) The source path in the form
549
            /<container>/<object>
550

  
551
        :param source_account: (string) The source account to copy/move from
552

  
553
        :param source_version: (string) The source version to copy from
554

  
555
        :param conent_encoding: (string) The encoding of the object
556

  
557
        :param content_disposition: (string) Presentation style of the object
558

  
559
        :param manifest: (string) Object parts prefix in
560
            /<container>/<object> form
561

  
562
        :param permissions: (dict) Object permissions in the form (all fields
563
            are optional)
564
            { 'read':[user1, group1, user2, ...],
565
            'write':['user3, group2, group3, ...] }
566

  
567
        :param public: (bool) If true, Object is publicly accessible,
568
            if false, not
569

  
570
        :param metadata: (dict) Optional user defined metadata in the form
571
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
572

  
573
        :returns: ConnectionResponse
574
        """
575

  
576
        self._assert_container()
577

  
578
        self.set_param('format', format, iff=format)
579
        self.set_param('hashmap', hashmap, iff=hashmap)
580
        self.set_param('delimiter', delimiter, iff=delimiter)
581

  
582
        self.set_header('If-Match', if_etag_match)
583
        self.set_header('If-None-Match', if_etag_not_match)
584
        self.set_header('ETag', etag)
585
        self.set_header('Content-Length', content_length)
586
        self.set_header('Content-Type', content_type)
587
        self.set_header('Transfer-Encoding', transfer_encoding)
588
        self.set_header('X-Copy-From', copy_from)
589
        self.set_header('X-Move-From', move_from)
590
        self.set_header('X-Source-Account', source_account)
591
        self.set_header('X-Source-Version', source_version)
592
        self.set_header('Content-Encoding', content_encoding)
593
        self.set_header('Content-Disposition', content_disposition)
594
        self.set_header('X-Object-Manifest', manifest)
595
        if permissions:
596
            perms = None
597
            if permissions:
598
                for perm_type, perm_list in permissions.items():
599
                    if not perms:
600
                        perms = ''  # Remove permissions
601
                    if perm_list:
602
                        perms += ';' if perms else ''
603
                        perms += '%s=%s' % (perm_type, ','.join(perm_list))
604
            self.set_header('X-Object-Sharing', perms)
605
        self.set_header('X-Object-Public', public)
606
        if metadata:
607
            for key, val in metadata.items():
608
                self.set_header('X-Object-Meta-' + key, val)
609

  
610
        path = path4url(self.account, self.container, object)
611
        success = kwargs.pop('success', 201)
612
        return self.put(path, *args, success=success, **kwargs)
613

  
614
    def object_copy(
615
            self, object, destination,
616
            format='json',
617
            ignore_content_type=False,
618
            if_etag_match=None,
619
            if_etag_not_match=None,
620
            destination_account=None,
621
            content_type=None,
622
            content_encoding=None,
623
            content_disposition=None,
624
            source_version=None,
625
            permissions=None,
626
            public=False,
627
            metadata=None,
628
            *args, **kwargs):
629
        """ Full Pithos+ COPY at object level
630

  
631
        --- request parameters ---
632

  
633
        :param format: (string) json (default) or xml
634

  
635
        :param ignore_content_type: (bool) Ignore the supplied Content-Type
636

  
637
        --- request headers ---
638

  
639
        :param if_etag_match: (string) if provided, copy only results
640
            with etag matching with this
641

  
642
        :param if_etag_not_match: (string) if provided, copy only results
643
            with etag not matching with this
644

  
645
        :param destination: (string) The destination path in the form
646
            /<container>/<object>
647

  
648
        :param destination_account: (string) The destination account to copy to
649

  
650
        :param content_type: (string) The MIME content type of the object
651

  
652
        :param content_encoding: (string) The encoding of the object
653

  
654
        :param content_disposition: (string) Object resentation style
655

  
656
        :param source_version: (string) The source version to copy from
657

  
658
        :param permissions: (dict) Object permissions in the form
659
            (all fields are optional)
660
            { 'read':[user1, group1, user2, ...],
661
            'write':['user3, group2, group3, ...] }
662

  
663
        :param permissions: update permissions
664

  
665
        :param public: (bool) If true, Object is publicly accessible
666

  
667
        :param metadata: (dict) Optional user defined metadata in the form
668
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
669
            Metadata are appended to the source metadata. In case of same
670
            keys, they replace the old metadata
671

  
672
        :returns: ConnectionResponse
673
        """
674

  
675
        self._assert_container()
676

  
677
        self.set_param('format', format, iff=format)
678
        self.set_param('ignore_content_type', iff=ignore_content_type)
679

  
680
        self.set_header('If-Match', if_etag_match)
681
        self.set_header('If-None-Match', if_etag_not_match)
682
        self.set_header('Destination', destination)
683
        self.set_header('Destination-Account', destination_account)
684
        self.set_header('Content-Type', content_type)
685
        self.set_header('Content-Encoding', content_encoding)
686
        self.set_header('Content-Disposition', content_disposition)
687
        self.set_header('X-Source-Version', source_version)
688
        if permissions:
689
            perms = ''
690
            for perm_type, perm_list in permissions.items():
691
                if not perms:
692
                    perms = ''  # Remove permissions
693
                if perm_list:
694
                    perms += ';' if perms else ''
695
                    perms += '%s=%s' % (perm_type, ','.join(perm_list))
696
            self.set_header('X-Object-Sharing', perms)
697
        self.set_header('X-Object-Public', public)
698
        if metadata:
699
            for key, val in metadata.items():
700
                self.set_header('X-Object-Meta-' + key, val)
701

  
702
        path = path4url(self.account, self.container, object)
703
        success = kwargs.pop('success', 201)
704
        return self.copy(path, *args, success=success, **kwargs)
705

  
706
    def object_move(
707
            self, object,
708
            format='json',
709
            ignore_content_type=False,
710
            if_etag_match=None,
711
            if_etag_not_match=None,
712
            destination=None,
713
            destination_account=None,
714
            content_type=None,
715
            content_encoding=None,
716
            content_disposition=None,
717
            permissions={},
718
            public=False,
719
            metadata={},
720
            *args, **kwargs):
721
        """ Full Pithos+ COPY at object level
722

  
723
        --- request parameters ---
724

  
725
        :param format: (string) json (default) or xml
726

  
727
        :param ignore_content_type: (bool) Ignore the supplied Content-Type
728

  
729
        --- request headers ---
730

  
731
        :param if_etag_match: (string) if provided, return only results
732
            with etag matching with this
733

  
734
        :param if_etag_not_match: (string) if provided, return only results
735
            with etag not matching with this
736

  
737
        :param destination: (string) The destination path in the form
738
            /<container>/<object>
739

  
740
        :param destination_account: (string) The destination account to copy to
741

  
742
        :param content_type: (string) The MIME content type of the object
743

  
744
        :param content_encoding: (string) The encoding of the object
745

  
746
        :param content_disposition: (string) Object presentation style
747

  
748
        :param source_version: (string) The source version to copy from
749

  
750
        :param permissions: (dict) Object permissions in the form
751
            (all fields are optional)
752
            { 'read':[user1, group1, user2, ...],
753
            'write':['user3, group2, group3, ...] }
754

  
755
        :param public: (bool) If true, Object is publicly accessible
756

  
757
        :param metadata: (dict) Optional user defined metadata in the form
758
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
759

  
760
        :returns: ConnectionResponse
761
        """
762

  
763
        self._assert_container()
764

  
765
        self.set_param('format', format, iff=format)
766
        self.set_param('ignore_content_type', iff=ignore_content_type)
767

  
768
        self.set_header('If-Match', if_etag_match)
769
        self.set_header('If-None-Match', if_etag_not_match)
770
        self.set_header('Destination', destination)
771
        self.set_header('Destination-Account', destination_account)
772
        self.set_header('Content-Type', content_type)
773
        self.set_header('Content-Encoding', content_encoding)
774
        self.set_header('Content-Disposition', content_disposition)
775
        if permissions:
776
            perms = ''
777
            for perm_type, perm_list in permissions.items():
778
                if not perms:
779
                    perms = ''  # Remove permissions
780
                if perm_list:
781
                    perms += ';' if perms else ''
782
                    perms += '%s=%s' % (perm_type, ','.join(perm_list))
783
            self.set_header('X-Object-Sharing', perms)
784
        self.set_header('X-Object-Public', public)
785
        if metadata:
786
            for key, val in metadata.items():
787
                self.set_header('X-Object-Meta-' + key, val)
788

  
789
        path = path4url(self.account, self.container, object)
790
        success = kwargs.pop('success', 201)
791
        return self.move(path, *args, success=success, **kwargs)
792

  
793
    def object_post(
794
            self, object,
795
            format='json',
796
            update=True,
797
            if_etag_match=None,
798
            if_etag_not_match=None,
799
            content_length=None,
800
            content_type=None,
801
            content_range=None,
802
            transfer_encoding=None,
803
            content_encoding=None,
804
            content_disposition=None,
805
            source_object=None,
806
            source_account=None,
807
            source_version=None,
808
            object_bytes=None,
809
            manifest=None,
810
            permissions={},
811
            public=False,
812
            metadata={},
813
            *args, **kwargs):
814
        """ Full Pithos+ POST at object level
815

  
816
        --- request parameters ---
817

  
818
        :param format: (string) json (default) or xml
819

  
820
        :param update: (bool) Do not replace metadata
821

  
822
        --- request headers ---
823

  
824
        :param if_etag_match: (string) if provided, return only results
825
            with etag matching with this
826

  
827
        :param if_etag_not_match: (string) if provided, return only results
828
            with etag not matching with this
829

  
830
        :param content_length: (string) The size of the data written
831

  
832
        :param content_type: (string) The MIME content type of the object
833

  
834
        :param content_range: (string) The range of data supplied
835

  
836
        :param transfer_encoding: (string) Set to chunked to specify
837
            incremental uploading (if used, Content-Length is ignored)
838

  
839
        :param content_encoding: (string) The encoding of the object
840

  
841
        :param content_disposition: (string) Object presentation style
842

  
843
        :param source_object: (string) Update with data from the object at
844
            path /<container>/<object>
845

  
846
        :param source_account: (string) The source account to update from
847

  
848
        :param source_version: (string) The source version to copy from
849

  
850
        :param object_bytes: (integer) The updated objects final size
851

  
852
        :param manifest: (string) Object parts prefix as /<container>/<object>
853

  
854
        :param permissions: (dict) Object permissions in the form (all fields
855
            are optional)
856
            { 'read':[user1, group1, user2, ...],
857
            'write':['user3, group2, group3, ...] }
858

  
859
        :param public: (bool) If true, Object is publicly accessible
860

  
861
        :param metadata: (dict) Optional user defined metadata in the form
862
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
863

  
864
        :returns: ConnectionResponse
865
        """
866

  
867
        self._assert_container()
868

  
869
        self.set_param('format', format, iff=format)
870
        self.set_param('update', iff=update)
871

  
872
        self.set_header('If-Match', if_etag_match)
873
        self.set_header('If-None-Match', if_etag_not_match)
874
        self.set_header(
875
            'Content-Length',
876
            content_length,
877
            iff=not transfer_encoding)
878
        self.set_header('Content-Type', content_type)
879
        self.set_header('Content-Range', content_range)
880
        self.set_header('Transfer-Encoding', transfer_encoding)
881
        self.set_header('Content-Encoding', content_encoding)
882
        self.set_header('Content-Disposition', content_disposition)
883
        self.set_header('X-Source-Object', source_object)
884
        self.set_header('X-Source-Account', source_account)
885
        self.set_header('X-Source-Version', source_version)
886
        self.set_header('X-Object-Bytes', object_bytes)
887
        self.set_header('X-Object-Manifest', manifest)
888
        if permissions:
889
            perms = ''
890
            for perm_type, perm_list in permissions.items():
891
                if not perms:
892
                    perms = ''  # Remove permissions
893
                if perm_list:
894
                    perms += ';' if perms else ''
895
                    perms += '%s=%s' % (perm_type, ','.join(perm_list))
896
                self.set_header('X-Object-Sharing', perms)
897
        self.set_header('X-Object-Public', public)
898
        for key, val in metadata.items():
899
            self.set_header('X-Object-Meta-' + key, val)
900

  
901
        path = path4url(self.account, self.container, object)
902
        success = kwargs.pop('success', (202, 204))
903
        return self.post(path, *args, success=success, **kwargs)
904

  
905
    def object_delete(
906
            self, object,
907
            until=None, delimiter=None,
908
            *args, **kwargs):
909
        """ Full Pithos+ DELETE at object level
910

  
911
        --- request parameters ---
912

  
913
        :param until: (string) Optional timestamp
914

  
915
        :returns: ConnectionResponse
916
        """
917
        self._assert_container()
918

  
919
        self.set_param('until', until, iff=until)
920
        self.set_param('delimiter', delimiter, iff=delimiter)
921

  
922
        path = path4url(self.account, self.container, object)
923
        success = kwargs.pop('success', 204)
924
        return self.delete(path, *args, success=success, **kwargs)
b/kamaki/clients/pithos/test.py
37 37
from os import urandom
38 38

  
39 39
from kamaki.clients import ClientError
40
from kamaki.clients.pithos import PithosClient as PC
40
from kamaki.clients.pithos import PithosClient, PithosRestClient
41 41

  
42 42
pithos_pkg = 'kamaki.clients.pithos.PithosClient'
43 43

  
......
149 149
        pass
150 150

  
151 151

  
152
class PithosRest(TestCase):
153

  
154
    def setUp(self):
155
        self.url = 'https://www.example.com/pithos'
156
        self.token = 'p17h0570k3n'
157
        self.client = PithosClient(self.url, self.token)
158
        self.client.account = user_id
159
        self.client.container = 'c0nt@1n3r_i'
160

  
161
    def tearDown(self):
162
        FR.headers = dict()
163
        FR.status_code = 200
164
        FR.json = dict()
165
        FR.content = FR.json
166
        for f in self.files:
167
            f.close()
168

  
169

  
152 170
class Pithos(TestCase):
153 171

  
154 172
    files = []
......
175 193
    def setUp(self):
176 194
        self.url = 'https://www.example.com/pithos'
177 195
        self.token = 'p17h0570k3n'
178
        self.client = PC(self.url, self.token)
196
        self.client = PithosClient(self.url, self.token)
179 197
        self.client.account = user_id
180 198
        self.client.container = 'c0nt@1n3r_i'
181 199

  
......
334 352
    def test_get_object_info(self):
335 353
        FR.headers = object_info
336 354
        version = 'v3r510n'
337
        with patch.object(PC, 'object_head', return_value=FR()) as head:
355
        with patch.object(
356
                PithosClient, 'object_head',
357
                return_value=FR()) as head:
338 358
            r = self.client.get_object_info(obj)
339 359
            self.assertEqual(r, object_info)
340 360
            r = self.client.get_object_info(obj, version=version)
......
342 362
                call(obj, version=None),
343 363
                call(obj, version=version)])
344 364
        with patch.object(
345
                PC, 'object_head',
365
                PithosClient, 'object_head',
346 366
                side_effect=ClientError('Obj not found', 404)):
347 367
            self.assertRaises(
348 368
                ClientError,
......
580 600
        FR.json = object_hashmap
581 601
        for empty in (304, 412):
582 602
            with patch.object(
583
                    PC, 'object_get',
603
                    PithosClient, 'object_get',
584 604
                    side_effect=ClientError('Empty', status=empty)):
585 605
                r = self.client.get_object_hashmap(obj)
586 606
                self.assertEqual(r, {})
......
599 619
            if_modified_since='some date here',
600 620
            if_unmodified_since='some date here',
601 621
            data_range='10-20')
602
        with patch.object(PC, 'object_get', return_value=FR()) as get:
622
        with patch.object(
623
                PithosClient, 'object_get',
624
                return_value=FR()) as get:
603 625
            r = self.client.get_object_hashmap(obj)
604 626
            self.assertEqual(r, object_hashmap)
605 627
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
......
637 659

  
638 660
    def test_get_account_meta(self):
639 661
        key = 'x-account-meta-'
640
        with patch.object(PC, 'get_account_info', return_value=account_info):
662
        with patch.object(
663
                PithosClient, 'get_account_info',
664
                return_value=account_info):
641 665
            r = self.client.get_account_meta()
642 666
            keys = [k for k in r if k.startswith(key)]
643 667
            self.assertFalse(keys)
......
645 669
        acc_info['%sk1' % key] = 'v1'
646 670
        acc_info['%sk2' % key] = 'v2'
647 671
        acc_info['%sk3' % key] = 'v3'
648
        with patch.object(PC, 'get_account_info', return_value=acc_info):
672
        with patch.object(
673
                PithosClient, 'get_account_info',
674
                return_value=acc_info):
649 675
            r = self.client.get_account_meta()
650 676
            for k in [k for k in acc_info if k.startswith(key)]:
651 677
                self.assertEqual(r[k], acc_info[k])
652 678

  
653 679
    def test_get_account_group(self):
654 680
        key = 'x-account-group-'
655
        with patch.object(PC, 'get_account_info', return_value=account_info):
681
        with patch.object(
682
                PithosClient, 'get_account_info',
683
                return_value=account_info):
656 684
            r = self.client.get_account_group()
657 685
            keys = [k for k in r if k.startswith(key)]
658 686
            self.assertFalse(keys)
......
660 688
        acc_info['%sk1' % key] = 'g1'
661 689
        acc_info['%sk2' % key] = 'g2'
662 690
        acc_info['%sk3' % key] = 'g3'
663
        with patch.object(PC, 'get_account_info', return_value=acc_info):
691
        with patch.object(
692
                PithosClient, 'get_account_info',
693
                return_value=acc_info):
664 694
            r = self.client.get_account_group()
665 695
            for k in [k for k in acc_info if k.startswith(key)]:
666 696
                self.assertEqual(r[k], acc_info[k])
......
726 756
        container_plus[key] = metaval
727 757
        for ret in ((container_info, {}), (container_plus, {key: metaval})):
728 758
            with patch.object(
729
                    PC,
759
                    PithosClient,
730 760
                    'get_container_info',
731 761
                    return_value=ret[0]) as GCI:
732 762
                for until in (None, somedate):
......
744 774
                (container_info, {key: ''}),
745 775
                (container_plus, {key: metaval})):
746 776
            with patch.object(
747
                    PC,
777
                    PithosClient,
748 778
                    'get_container_info',
749 779
                    return_value=ret[0]) as GCI:
750 780
                for until in (None, somedate):
......
798 828
        oinfo = dict(object_info)
799 829
        val = 'pubL1c'
800 830
        oinfo['x-object-public'] = val
801
        with patch.object(PC, 'get_object_info', return_value=oinfo) as GOF:
831
        with patch.object(
832
                PithosClient, 'get_object_info',
833
                return_value=oinfo) as GOF:
802 834
            r = self.client.publish_object(obj)
803 835
            self.assertEqual(
804 836
                post.mock_calls[-1],
......
816 848
        expected = dict(read='u1,g1,u2', write='u1')
817 849
        info['x-object-sharing'] = '; '.join(
818 850
            ['%s=%s' % (k, v) for k, v in expected.items()])
819
        with patch.object(PC, 'get_object_info', return_value=info) as GOF:
851
        with patch.object(
852
                PithosClient, 'get_object_info',
853
                return_value=info) as GOF:
820 854
            r = self.client.get_object_sharing(obj)
821 855
            self.assertEqual(GOF.mock_calls[-1], call(obj))
822 856
            self.assert_dicts_are_equal(r, expected)
......
920 954
        info = dict(object_info)
921 955
        info['content-length'] = file_size
922 956
        block_size = container_info['x-container-block-size']
923
        with patch.object(PC, 'get_object_info', return_value=info) as GOI:
957
        with patch.object(
958
                PithosClient, 'get_object_info',
959
                return_value=info) as GOI:
924 960
            for start, end in (
925 961
                    (0, file_size + 1),
926 962
                    (file_size + 1, file_size + 2)):
......
995 1031
if __name__ == '__main__':
996 1032
    from sys import argv
997 1033
    from kamaki.clients.test import runTestCase
998
    runTestCase(Pithos, 'Pithos+ Client', argv[1:])
1034
    not_found = True
1035
    if not argv[1:] or argv[1] == 'Pithos':
1036
        not_found = False
1037
        runTestCase(Pithos, 'Pithos Client', argv[2:])
1038
    if not argv[1:] or argv[1] == 'PithosRest':
1039
        not_found = False
1040
        runTestCase(PithosRest, 'PithosRest Client', argv[2:])
1041
    if not_found:
1042
        print('TestCase %s not found' % argv[1])
/dev/null
1
# Copyright 2012-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, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this 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
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff