1 # Copyright 2011-2013 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
34 from kamaki.clients import Client, ClientError
35 from kamaki.clients.utils import filter_in, filter_out, path4url
38 class StorageClient(Client):
39 """OpenStack Object Storage API 1.0 client"""
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
46 def _assert_account(self):
48 raise ClientError("No account provided")
50 def _assert_container(self):
51 self._assert_account()
52 if not self.container:
53 raise ClientError("No container provided")
55 def get_account_info(self):
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)
67 def replace_account_meta(self, metapairs):
69 :param metapais: (dict) key:val metadata pairs
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)
77 def del_account_meta(self, metakey):
79 :param metakey: (str) metadatum key
81 headers = self.get_account_info()
82 self.headers = filter_out(
84 'X-Account-Meta-' + metakey,
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)
91 def create_container(self, container):
93 :param container: (str)
95 :raises ClientError: 202 Container already exists
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)
103 def get_container_info(self, container):
105 :param container: (str)
109 :raises ClientError: 404 Container does not exist
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)
119 def delete_container(self, container):
121 :param container: (str)
123 :raises ClientError: 404 Container does not exist
124 :raises ClientError: 409 Container not empty
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)
134 def list_containers(self):
138 self._assert_account()
139 self.set_param('format', 'json')
140 path = path4url(self.account)
141 r = self.get(path, success=(200, 204))
145 def upload_object(self, obj, f, size=None):
146 """ A simple (naive) implementation.
150 :param f: an open for reading file descriptor
152 :param size: (int) number of bytes to upload
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)
161 content_type='application/octet-stream', content_length=0):
163 :param obj: (str) directory-object name
165 :param content_type: (str) explicitly set content_type
167 :param content_length: (int) explicitly set content length
169 :returns: (dict) object creation headers
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)
178 def create_directory(self, obj):
180 :param obj: (str) directory-object name
182 :returns: (dict) request headers
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)
191 def get_object_info(self, obj):
197 self._assert_container()
198 path = path4url(self.account, self.container, obj)
199 r = self.head(path, success=200)
203 def get_object_meta(self, obj):
209 r = filter_in(self.get_object_info(obj), 'X-Object-Meta-')
211 for (key, val) in r.items():
212 metakey = key.split('-')[-1]
216 def del_object_meta(self, obj, metakey):
220 :param metakey: (str) the metadatum key
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)
227 def replace_object_meta(self, metapairs):
229 :param metapairs: (dict) key:val metadata
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)
238 self, src_container, src_object, dst_container,
240 """Copy an objects from src_contaier:src_object to
241 dst_container[:dst_object]
243 :param src_container: (str)
245 :param src_object: (str)
247 :param dst_container: (str)
249 :param dst_object: (str)
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)
259 self, src_container, src_object, dst_container,
261 """Move an objects from src_contaier:src_object to
262 dst_container[:dst_object]
264 :param src_container: (str)
266 :param src_object: (str)
268 :param dst_container: (str)
270 :param dst_object: (str)
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)
279 def delete_object(self, obj):
283 :raises ClientError: 404 Object not found
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)
300 :param limit: (integer) The amount of results requested
302 :param marker: (string) Return containers with name lexicographically
305 :param prefix: (string) Return objects starting with prefix
307 :param format: (string) reply format can be json or xml (default:json)
309 :param delimiter: (string) Return objects up to the delimiter
311 :param path: (string) assume prefix = path and delimiter = /
312 (overwrites prefix and delimiter)
316 :raises ClientError: 404 Invalid account
318 self._assert_container()
319 restpath = path4url(self.account, self.container)
321 self.set_param('format', format or 'json')
323 self.set_param('limit', limit, iff=limit)
324 self.set_param('marker', marker, iff=marker)
326 self.set_param('path', path)
328 self.set_param('prefix', prefix, iff=prefix)
329 self.set_param('delimiter', delimiter, iff=delimiter)
331 r = self.get(restpath, success=(200, 204, 304, 404), )
332 if r.status_code == 404:
334 "Invalid account (%s) for that container" % self.account,
336 elif r.status_code == 304:
340 def list_objects_in_path(self, path_prefix):
342 :param path_prefix: (str)
344 :raises ClientError: 404 Invalid account
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:
355 "Invalid account (%s) for that container" % self.account,