Minor typos
[kamaki] / kamaki / clients / storage / __init__.py
1 # 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