Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / storage / __init__.py @ e3f01d64

History | View | Annotate | Download (11.5 kB)

1
#a Copyright 2011-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 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
        self.post(path, success=202)
76

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

    
91
    def create_container(self, container):
92
        """
93
        :param container: (str)
94

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

    
103
    def get_container_info(self, container):
104
        """
105
        :param container: (str)
106

107
        :returns: (dict)
108

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

    
119
    def delete_container(self, container):
120
        """
121
        :param container: (str)
122

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

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

    
145
    def upload_object(self, obj, f, size=None):
146
        """ A simple (naive) implementation.
147

148
        :param obj: (str)
149

150
        :param f: an open for reading file descriptor
151

152
        :param size: (int) number of bytes to upload
153
        """
154
        self._assert_container()
155
        path = path4url(self.account, self.container, obj)
156
        data = f.read(size) if size else f.read()
157
        self.put(path, data=data, success=201)
158

    
159
    def create_object(
160
            self, obj,
161
            content_type='application/octet-stream', content_length=0):
162
        """
163
        :param obj: (str) directory-object name
164

165
        :param content_type: (str) explicitly set content_type
166

167
        :param content_length: (int) explicitly set content length
168

169
        :returns: (dict) object creation headers
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
        return r.headers
177

    
178
    def create_directory(self, obj):
179
        """
180
        :param obj: (str) directory-object name
181

182
        :returns: (dict) request headers
183
        """
184
        self._assert_container()
185
        path = path4url(self.account, self.container, obj)
186
        self.set_header('Content-Type', 'application/directory')
187
        self.set_header('Content-length', '0')
188
        r = self.put(path, success=201)
189
        return r.headers
190

    
191
    def get_object_info(self, obj):
192
        """
193
        :param obj: (str)
194

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

    
203
    def get_object_meta(self, obj):
204
        """
205
        :param obj: (str)
206

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

    
216
    def del_object_meta(self, obj, metakey):
217
        """
218
        :param obj: (str)
219

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

    
227
    def replace_object_meta(self, metapairs):
228
        """
229
        :param metapairs: (dict) key:val metadata
230
        """
231
        self._assert_container()
232
        path = path4url(self.account, self.container)
233
        for key, val in metapairs.items():
234
            self.set_header('X-Object-Meta-' + key, val)
235
        self.post(path, success=202)
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
        self.put(dst_path, success=201)
257

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

264
        :param src_container: (str)
265

266
        :param src_object: (str)
267

268
        :param dst_container: (str)
269

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

    
279
    def delete_object(self, obj):
280
        """
281
        :param obj: (str)
282

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

    
291
    def list_objects(
292
            self,
293
            limit=None,
294
            marker=None,
295
            prefix=None,
296
            format=None,
297
            delimiter=None,
298
            path=None):
299
        """
300
        :param limit: (integer) The amount of results requested
301

302
        :param marker: (string) Return containers with name lexicographically
303
            after marker
304

305
        :param prefix: (string) Return objects starting with prefix
306

307
        :param format: (string) reply format can be json or xml (default:json)
308

309
        :param delimiter: (string) Return objects up to the delimiter
310

311
        :param path: (string) assume prefix = path and delimiter = /
312
            (overwrites prefix and delimiter)
313

314
        :returns: (dict)
315

316
        :raises ClientError: 404 Invalid account
317
        """
318
        self._assert_container()
319
        restpath = path4url(self.account, self.container)
320

    
321
        self.set_param('format', format or 'json')
322

    
323
        self.set_param('limit', limit, iff=limit)
324
        self.set_param('marker', marker, iff=marker)
325
        if path:
326
            self.set_param('path', path)
327
        else:
328
            self.set_param('prefix', prefix, iff=prefix)
329
            self.set_param('delimiter', delimiter, iff=delimiter)
330

    
331
        r = self.get(restpath, success=(200, 204, 304, 404), )
332
        if r.status_code == 404:
333
            raise ClientError(
334
                "Invalid account (%s) for that container" % self.account,
335
                r.status_code)
336
        elif r.status_code == 304:
337
            return []
338
        return r.json
339

    
340
    def list_objects_in_path(self, path_prefix):
341
        """
342
        :param path_prefix: (str)
343

344
        :raises ClientError: 404 Invalid account
345

346
        :returns: (dict)
347
        """
348
        self._assert_container()
349
        path = path4url(self.account, self.container)
350
        self.set_param('format', 'json')
351
        self.set_param('path', path_prefix)
352
        r = self.get(path, success=(200, 204, 404))
353
        if r.status_code == 404:
354
            raise ClientError(
355
                "Invalid account (%s) for that container" % self.account,
356
                r.status_code)
357
        reply = r.json
358
        return reply