Revision c608d6e9

/dev/null
1
#a Copyright 2011 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 import Client, ClientError
35
from kamaki.clients.utils import filter_in, filter_out, path4url
36

  
37

  
38
class StorageClient(Client):
39
    """OpenStack Object Storage API 1.0 client"""
40

  
41
    def __init__(self, base_url, token, account=None, container=None):
42
        super(StorageClient, self).__init__(base_url, token)
43
        self.account = account
44
        self.container = container
45

  
46
    def _assert_account(self):
47
        if not self.account:
48
            raise ClientError("No account provided")
49

  
50
    def _assert_container(self):
51
        self._assert_account()
52
        if not self.container:
53
            raise ClientError("No container provided")
54

  
55
    def get_account_info(self):
56
        """
57
        :returns: (dict)
58
        """
59
        self._assert_account()
60
        path = path4url(self.account)
61
        r = self.head(path, success=(204, 401))
62
        if r.status_code == 401:
63
            raise ClientError("No authorization", status=401)
64
        reply = r.headers
65
        return reply
66

  
67
    def replace_account_meta(self, metapairs):
68
        """
69
        :param metapais: (dict) key:val metadata pairs
70
        """
71
        self._assert_account()
72
        path = path4url(self.account)
73
        for key, val in metapairs.items():
74
            self.set_header('X-Account-Meta-' + key, val)
75
        r = self.post(path, success=202)
76
        r.release()
77

  
78
    def del_account_meta(self, metakey):
79
        """
80
        :param metakey: (str) metadatum key
81
        """
82
        headers = self.get_account_info()
83
        self.headers = filter_out(
84
            headers,
85
            'X-Account-Meta-' + metakey,
86
            exactMatch=True)
87
        if len(self.headers) == len(headers):
88
            raise ClientError('X-Account-Meta-%s not found' % metakey, 404)
89
        path = path4url(self.account)
90
        r = self.post(path, success=202)
91
        r.release()
92

  
93
    def create_container(self, container):
94
        """
95
        :param container: (str)
96

  
97
        :raises ClientError: 202 Container already exists
98
        """
99
        self._assert_account()
100
        path = path4url(self.account, container)
101
        r = self.put(path, success=(201, 202))
102
        r.release()
103
        if r.status_code == 202:
104
            raise ClientError("Container already exists", r.status_code)
105

  
106
    def get_container_info(self, container):
107
        """
108
        :param container: (str)
109

  
110
        :returns: (dict)
111

  
112
        :raises ClientError: 404 Container does not exist
113
        """
114
        self._assert_account()
115
        path = path4url(self.account, container)
116
        r = self.head(path, success=(204, 404))
117
        if r.status_code == 404:
118
            raise ClientError("Container does not exist", r.status_code)
119
        reply = r.headers
120
        return reply
121

  
122
    def delete_container(self, container):
123
        """
124
        :param container: (str)
125

  
126
        :raises ClientError: 404 Container does not exist
127
        :raises ClientError: 409 Container not empty
128
        """
129
        self._assert_account()
130
        path = path4url(self.account, container)
131
        r = self.delete(path, success=(204, 404, 409))
132
        if r.status_code == 404:
133
            raise ClientError("Container does not exist", r.status_code)
134
        elif r.status_code == 409:
135
            raise ClientError("Container is not empty", r.status_code)
136

  
137
    def list_containers(self):
138
        """
139
        :returns: (dict)
140
        """
141
        self._assert_account()
142
        self.set_param('format', 'json')
143
        path = path4url(self.account)
144
        r = self.get(path, success=(200, 204))
145
        reply = r.json
146
        return reply
147

  
148
    def upload_object(self, obj, f, size=None):
149
        """ A simple (naive) implementation.
150

  
151
        :param obj: (str)
152

  
153
        :param f: an open for reading file descriptor
154

  
155
        :param size: (int) number of bytes to upload
156
        """
157
        self._assert_container()
158
        path = path4url(self.account, self.container, obj)
159
        data = f.read(size) if size is not None else f.read()
160
        r = self.put(path, data=data, success=201)
161
        r.release()
162

  
163
    def create_object(
164
            self, obj,
165
            content_type='application/octet-stream', content_length=0):
166
        """
167
        :param obj: (str) directory-object name
168
        :param content_type: (str) explicitly set content_type
169
        :param content_length: (int) explicitly set content length
170
        """
171
        self._assert_container()
172
        path = path4url(self.account, self.container, obj)
173
        self.set_header('Content-Type', content_type)
174
        self.set_header('Content-length', str(content_length))
175
        r = self.put(path, success=201)
176
        r.release()
177

  
178
    def create_directory(self, obj):
179
        """
180
        :param obj: (str) directory-object name
181
        """
182
        self._assert_container()
183
        path = path4url(self.account, self.container, obj)
184
        self.set_header('Content-Type', 'application/directory')
185
        self.set_header('Content-length', '0')
186
        r = self.put(path, success=201)
187
        r.release()
188

  
189
    def get_object_info(self, obj):
190
        """
191
        :param obj: (str)
192

  
193
        :returns: (dict)
194
        """
195
        self._assert_container()
196
        path = path4url(self.account, self.container, obj)
197
        r = self.head(path, success=200)
198
        reply = r.headers
199
        return reply
200

  
201
    def get_object_meta(self, obj):
202
        """
203
        :param obj: (str)
204

  
205
        :returns: (dict)
206
        """
207
        r = filter_in(self.get_object_info(obj), 'X-Object-Meta-')
208
        reply = {}
209
        for (key, val) in r.items():
210
            metakey = key.split('-')[-1]
211
            reply[metakey] = val
212
        return reply
213

  
214
    def del_object_meta(self, obj, metakey):
215
        """
216
        :param obj: (str)
217

  
218
        :param metakey: (str) the metadatum key
219
        """
220
        self._assert_container()
221
        self.set_header('X-Object-Meta-' + metakey, '')
222
        path = path4url(self.account, self.container, obj)
223
        r = self.post(path, success=202)
224
        r.release()
225

  
226
    def replace_object_meta(self, metapairs):
227
        """
228
        :param metapairs: (dict) key:val metadata
229
        """
230
        self._assert_container()
231
        path = path4url(self.account, self.container)
232
        for key, val in metapairs.items():
233
            self.set_header('X-Object-Meta-' + key, val)
234
        r = self.post(path, success=202)
235
        r.release()
236

  
237
    def copy_object(
238
            self, src_container, src_object, dst_container,
239
            dst_object=False):
240
        """Copy an objects from src_contaier:src_object to
241
            dst_container[:dst_object]
242

  
243
        :param src_container: (str)
244

  
245
        :param src_object: (str)
246

  
247
        :param dst_container: (str)
248

  
249
        :param dst_object: (str)
250
        """
251
        self._assert_account()
252
        dst_object = dst_object or src_object
253
        dst_path = path4url(self.account, dst_container, dst_object)
254
        self.set_header('X-Copy-From', path4url(src_container, src_object))
255
        self.set_header('Content-Length', 0)
256
        r = self.put(dst_path, success=201)
257
        r.release()
258

  
259
    def move_object(
260
            self, src_container, src_object, dst_container,
261
            dst_object=False):
262
        """Move an objects from src_contaier:src_object to
263
            dst_container[:dst_object]
264

  
265
        :param src_container: (str)
266

  
267
        :param src_object: (str)
268

  
269
        :param dst_container: (str)
270

  
271
        :param dst_object: (str)
272
        """
273
        self._assert_account()
274
        dst_object = dst_object or src_object
275
        dst_path = path4url(self.account, dst_container, dst_object)
276
        self.set_header('X-Move-From', path4url(src_container, src_object))
277
        self.set_header('Content-Length', 0)
278
        r = self.put(dst_path, success=201)
279
        r.release()
280

  
281
    def delete_object(self, obj):
282
        """
283
        :param obj: (str)
284

  
285
        :raises ClientError: 404 Object not found
286
        """
287
        self._assert_container()
288
        path = path4url(self.account, self.container, obj)
289
        r = self.delete(path, success=(204, 404))
290
        if r.status_code == 404:
291
            raise ClientError("Object %s not found" % obj, r.status_code)
292

  
293
    def list_objects(self):
294
        """
295
        :returns: (dict)
296

  
297
        :raises ClientError: 404 Invalid account
298
        """
299
        self._assert_container()
300
        path = path4url(self.account, self.container)
301
        self.set_param('format', 'json')
302
        r = self.get(path, success=(200, 204, 304, 404), )
303
        if r.status_code == 404:
304
            raise ClientError(
305
                "Invalid account (%s) for that container" % self.account,
306
                r.status_code)
307
        elif r.status_code == 304:
308
            return []
309
        return r.json
310

  
311
    def list_objects_in_path(self, path_prefix):
312
        """
313
        :param path_prefix: (str)
314

  
315
        :raises ClientError: 404 Invalid account
316

  
317
        :returns: (dict)
318
        """
319
        self._assert_container()
320
        path = path4url(self.account, self.container)
321
        self.set_param('format', 'json')
322
        self.set_param('path', path_prefix)
323
        r = self.get(path, success=(200, 204, 404))
324
        if r.status_code == 404:
325
            raise ClientError(
326
                "Invalid account (%s) for that container" % self.account,
327
                r.status_code)
328
        reply = r.json
329
        return reply
b/kamaki/clients/test.py
38 38
from kamaki.clients.astakos.test import Astakos
39 39
from kamaki.clients.cyclades.test import Cyclades
40 40
from kamaki.clients.image.test import Image
41
from kamaki.clients.storage.test import Storage
41 42
from kamaki.clients.pithos.test import Pithos
42 43

  
43 44

  
b/setup.py
65 65
        'kamaki.clients',
66 66
        'kamaki.clients.livetest',
67 67
        'kamaki.clients.image',
68
        'kamaki.clients.storage',
68 69
        'kamaki.clients.pithos',
69 70
        'kamaki.clients.astakos',
70 71
        'kamaki.clients.cyclades',

Also available in: Unified diff