1 # Copyright 2012-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.storage import StorageClient
35 from kamaki.clients.utils import path4url
38 class PithosRestClient(StorageClient):
43 if_modified_since=None,
44 if_unmodified_since=None,
46 """ Full Pithos+ HEAD at account level
48 --- request parameters ---
50 :param until: (string) optional timestamp
52 --- request headers ---
54 :param if_modified_since: (string) Retrieve if account has changed
55 since provided timestamp
57 :param if_unmodified_since: (string) Retrieve if account has not
58 change since provided timestamp
60 :returns: ConnectionResponse
63 self._assert_account()
64 path = path4url(self.account)
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)
70 success = kwargs.pop('success', 204)
71 return self.head(path, *args, success=success, **kwargs)
78 show_only_shared=False,
80 if_modified_since=None,
81 if_unmodified_since=None,
83 """ Full Pithos+ GET at account level
85 --- request parameters ---
87 :param limit: (integer) The amount of results requested
88 (server will use default value if None)
90 :param marker: string Return containers with name
91 lexicographically after marker
93 :param format: (string) reply format can be json or xml
96 :param shared: (bool) If true, only shared containers will be
99 :param until: (string) optional timestamp
101 --- request headers ---
103 :param if_modified_since: (string) Retrieve if account has changed
104 since provided timestamp
106 :param if_unmodified_since: (string) Retrieve if account has not
107 changed since provided timestamp
109 :returns: ConnectionResponse
112 self._assert_account()
114 self.set_param('limit', limit, iff=limit)
115 self.set_param('marker', marker, iff=marker)
116 self.set_param('format', format, iff=format)
117 self.set_param('shared', iff=show_only_shared)
118 self.set_param('until', until, iff=until)
120 self.set_header('If-Modified-Since', if_modified_since)
121 self.set_header('If-Unmodified-Since', if_unmodified_since)
123 path = path4url(self.account)
124 success = kwargs.pop('success', (200, 204))
125 return self.get(path, *args, success=success, **kwargs)
135 """ Full Pithos+ POST at account level
137 --- request parameters ---
139 :param update: (bool) if True, Do not replace metadata/groups
141 --- request headers ---
143 :param groups: (dict) Optional user defined groups in the form
144 { 'group1':['user1', 'user2', ...],
145 'group2':['userA', 'userB', ...], }
147 :param metadata: (dict) Optional user defined metadata in the form
148 { 'name1': 'value1', 'name2': 'value2', ... }
150 :param quota: (integer) If supported, sets the Account quota
152 :param versioning: (string) If supported, sets the Account versioning
153 to 'auto' or some other supported versioning string
155 :returns: ConnectionResponse
158 self._assert_account()
160 self.set_param('update', iff=update)
163 for group, usernames in groups.items():
166 for user in usernames:
167 userstr = userstr + dlm + user
169 self.set_header('X-Account-Group-' + group, userstr)
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)
176 path = path4url(self.account)
177 success = kwargs.pop('success', 202)
178 return self.post(path, *args, success=success, **kwargs)
183 if_modified_since=None,
184 if_unmodified_since=None,
186 """ Full Pithos+ HEAD at container level
188 --- request params ---
190 :param until: (string) optional timestamp
192 --- request headers ---
194 :param if_modified_since: (string) Retrieve if account has changed
195 since provided timestamp
197 :param if_unmodified_since: (string) Retrieve if account has not
198 changed since provided timestamp
200 :returns: ConnectionResponse
203 self._assert_container()
205 self.set_param('until', until, iff=until)
207 self.set_header('If-Modified-Since', if_modified_since)
208 self.set_header('If-Unmodified-Since', if_unmodified_since)
210 path = path4url(self.account, self.container)
211 success = kwargs.pop('success', 204)
212 return self.head(path, *args, success=success, **kwargs)
223 show_only_shared=False,
225 if_modified_since=None,
226 if_unmodified_since=None,
228 """ Full Pithos+ GET at container level
230 --- request parameters ---
232 :param limit: (integer) The amount of results requested
233 (server will use default value if None)
235 :param marker: (string) Return containers with name lexicographically
238 :param prefix: (string) Return objects starting with prefix
240 :param delimiter: (string) Return objects up to the delimiter
242 :param path: (string) assume prefix = path and delimiter = /
243 (overwrites prefix and delimiter)
245 :param format: (string) reply format can be json or xml (default:json)
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 =, !=, <=, >=, <, >)
252 :param show_only_shared: (bool) If true, only shared containers will
253 be included in results
255 :param until: (string) optional timestamp
257 --- request headers ---
259 :param if_modified_since: (string) Retrieve if account has changed
260 since provided timestamp
262 :param if_unmodified_since: (string) Retrieve if account has not
263 changed since provided timestamp
265 :returns: ConnectionResponse
268 self._assert_container()
270 self.set_param('limit', limit, iff=limit)
271 self.set_param('marker', marker, iff=marker)
273 self.set_param('prefix', prefix, iff=prefix)
274 self.set_param('delimiter', delimiter, iff=delimiter)
276 self.set_param('path', path)
277 self.set_param('format', format, iff=format)
278 self.set_param('shared', iff=show_only_shared)
280 self.set_param('meta', ','.join(meta))
281 self.set_param('until', until, iff=until)
283 self.set_header('If-Modified-Since', if_modified_since)
284 self.set_header('If-Unmodified-Since', if_unmodified_since)
286 path = path4url(self.account, self.container)
287 success = kwargs.pop('success', 200)
288 return self.get(path, *args, success=success, **kwargs)
292 quota=None, versioning=None, metadata=None,
294 """ Full Pithos+ PUT at container level
296 --- request headers ---
298 :param quota: (integer) Size limit in KB
300 :param versioning: (string) 'auto' or other string supported by server
302 :param metadata: (dict) Optional user defined metadata in the form
303 { 'name1': 'value1', 'name2': 'value2', ... }
305 :returns: ConnectionResponse
307 self._assert_container()
309 self.set_header('X-Container-Policy-Quota', quota)
310 self.set_header('X-Container-Policy-Versioning', versioning)
312 for metaname, metaval in metadata.items():
313 self.set_header('X-Container-Meta-' + metaname, metaval)
315 path = path4url(self.account, self.container)
316 success = kwargs.pop('success', (201, 202))
317 return self.put(path, *args, success=success, **kwargs)
328 transfer_encoding=None,
330 """ Full Pithos+ POST at container level
332 --- request params ---
334 :param update: (bool) if True, Do not replace metadata/groups
336 :param format: (string) json (default) or xml
338 --- request headers ---
340 :param quota: (integer) Size limit in KB
342 :param versioning: (string) 'auto' or other string supported by server
344 :param metadata: (dict) Optional user defined metadata in the form
345 { 'name1': 'value1', 'name2': 'value2', ... }
347 :param content_type: (string) set a custom content type
349 :param content_length: (string) set a custrom content length
351 :param transfer_encoding: (string) set a custom transfer encoding
353 :returns: ConnectionResponse
355 self._assert_container()
357 self.set_param('update', iff=update)
358 self.set_param('format', format, iff=format)
360 self.set_header('X-Container-Policy-Quota', quota)
361 self.set_header('X-Container-Policy-Versioning', versioning)
363 for metaname, metaval in metadata.items():
364 self.set_header('X-Container-Meta-' + metaname, metaval)
365 self.set_header('Content-Type', content_type)
366 self.set_header('Content-Length', content_length)
367 self.set_header('Transfer-Encoding', transfer_encoding)
369 path = path4url(self.account, self.container)
370 success = kwargs.pop('success', 202)
371 return self.post(path, *args, success=success, **kwargs)
373 def container_delete(self, until=None, delimiter=None, *args, **kwargs):
374 """ Full Pithos+ DELETE at container level
376 --- request parameters ---
378 :param until: (timestamp string) if defined, container is purged up to
381 :returns: ConnectionResponse
384 self._assert_container()
386 self.set_param('until', until, iff=until)
387 self.set_param('delimiter', delimiter, iff=delimiter)
389 path = path4url(self.account, self.container)
390 success = kwargs.pop('success', 204)
391 return self.delete(path, *args, success=success, **kwargs)
397 if_etag_not_match=None,
398 if_modified_since=None,
399 if_unmodified_since=None,
401 """ Full Pithos+ HEAD at object level
403 --- request parameters ---
405 :param version: (string) optional version identified
407 --- request headers ---
409 :param if_etag_match: (string) if provided, return only results
410 with etag matching with this
412 :param if_etag_not_match: (string) if provided, return only results
413 with etag not matching with this
415 :param if_modified_since: (string) Retrieve if account has changed
416 since provided timestamp
418 :param if_unmodified_since: (string) Retrieve if account has not
419 changed since provided timestamp
421 :returns: ConnectionResponse
424 self._assert_container()
426 self.set_param('version', version, iff=version)
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)
433 path = path4url(self.account, self.container, obj)
434 success = kwargs.pop('success', 200)
435 return self.head(path, *args, success=success, **kwargs)
445 if_etag_not_match=None,
446 if_modified_since=None,
447 if_unmodified_since=None,
449 """ Full Pithos+ GET at object level
451 --- request parameters ---
453 :param format: (string) json (default) or xml
455 :param hashmap: (bool) Optional request for hashmap
457 :param version: (string) optional version identified
459 --- request headers ---
461 :param data_range: (string) Optional range of data to retrieve
463 :param if_range: (bool)
465 :param if_etag_match: (string) if provided, return only results
466 with etag matching with this
468 :param if_etag_not_match: (string) if provided, return only results
469 with etag not matching with this
471 :param if_modified_since: (string) Retrieve if account has changed
472 since provided timestamp
474 :param if_unmodified_since: (string) Retrieve if account has not
475 changed since provided timestamp
477 :returns: ConnectionResponse
480 self._assert_container()
482 self.set_param('format', format, iff=format)
483 self.set_param('hashmap', hashmap, iff=hashmap)
484 self.set_param('version', version, iff=version)
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)
493 path = path4url(self.account, self.container, obj)
494 success = kwargs.pop('success', 200)
495 return self.get(path, *args, success=success, **kwargs)
503 if_etag_not_match=None,
507 transfer_encoding=None,
512 content_encoding=None,
513 content_disposition=None,
519 """ Full Pithos+ PUT at object level
521 --- request parameters ---
523 :param format: (string) json (default) or xml
525 :param hashmap: (bool) Optional hashmap provided instead of data
527 --- request headers ---
529 :param if_etag_match: (string) if provided, return only results
530 with etag matching with this
532 :param if_etag_not_match: (string) if provided, return only results
533 with etag not matching with this
535 :param etag: (string) The MD5 hash of the object (optional to check
538 :param content_length: (integer) The size of the data written
540 :param content_type: (string) The MIME content type of the object
542 :param transfer_encoding: (string) Set to chunked to specify
543 incremental uploading (if used, Content-Length is ignored)
545 :param copy_from: (string) The source path in the form
546 /<container>/<object>
548 :param move_from: (string) The source path in the form
549 /<container>/<object>
551 :param source_account: (string) The source account to copy/move from
553 :param source_version: (string) The source version to copy from
555 :param conent_encoding: (string) The encoding of the object
557 :param content_disposition: (string) Presentation style of the object
559 :param manifest: (string) Object parts prefix in
560 /<container>/<object> form
562 :param permissions: (dict) Object permissions in the form (all fields
564 { 'read':[user1, group1, user2, ...],
565 'write':['user3, group2, group3, ...] }
567 :param public: (bool) If true, Object is publicly accessible,
570 :param metadata: (dict) Optional user defined metadata in the form
571 {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
573 :returns: ConnectionResponse
576 self._assert_container()
578 self.set_param('format', format, iff=format)
579 self.set_param('hashmap', hashmap, iff=hashmap)
580 self.set_param('delimiter', delimiter, iff=delimiter)
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)
598 for perm_type, perm_list in permissions.items():
600 perms = '' # Remove permissions
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)
607 for key, val in metadata.items():
608 self.set_header('X-Object-Meta-' + key, val)
610 path = path4url(self.account, self.container, obj)
611 success = kwargs.pop('success', 201)
612 return self.put(path, *args, success=success, **kwargs)
615 self, obj, destination,
617 ignore_content_type=False,
619 if_etag_not_match=None,
620 destination_account=None,
622 content_encoding=None,
623 content_disposition=None,
629 """ Full Pithos+ COPY at object level
631 --- request parameters ---
633 :param format: (string) json (default) or xml
635 :param ignore_content_type: (bool) Ignore the supplied Content-Type
637 --- request headers ---
639 :param if_etag_match: (string) if provided, copy only results
640 with etag matching with this
642 :param if_etag_not_match: (string) if provided, copy only results
643 with etag not matching with this
645 :param destination: (string) The destination path in the form
646 /<container>/<object>
648 :param destination_account: (string) The destination account to copy to
650 :param content_type: (string) The MIME content type of the object
652 :param content_encoding: (string) The encoding of the object
654 :param content_disposition: (string) Object resentation style
656 :param source_version: (string) The source version to copy from
658 :param permissions: (dict) Object permissions in the form
659 (all fields are optional)
660 { 'read':[user1, group1, user2, ...],
661 'write':['user3, group2, group3, ...] }
663 :param public: (bool) If true, Object is publicly accessible
665 :param metadata: (dict) Optional user defined metadata in the form
666 {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
667 Metadata are appended to the source metadata. In case of same
668 keys, they replace the old metadata
670 :returns: ConnectionResponse
673 self._assert_container()
675 self.set_param('format', format, iff=format)
676 self.set_param('ignore_content_type', iff=ignore_content_type)
678 self.set_header('If-Match', if_etag_match)
679 self.set_header('If-None-Match', if_etag_not_match)
680 self.set_header('Destination', destination)
681 self.set_header('Destination-Account', destination_account)
682 self.set_header('Content-Type', content_type)
683 self.set_header('Content-Encoding', content_encoding)
684 self.set_header('Content-Disposition', content_disposition)
685 self.set_header('X-Source-Version', source_version)
688 for perm_type, perm_list in permissions.items():
690 perms = '' # Remove permissions
692 perms += ';' if perms else ''
693 perms += '%s=%s' % (perm_type, ','.join(perm_list))
694 self.set_header('X-Object-Sharing', perms)
695 self.set_header('X-Object-Public', public)
697 for key, val in metadata.items():
698 self.set_header('X-Object-Meta-' + key, val)
700 path = path4url(self.account, self.container, obj)
701 success = kwargs.pop('success', 201)
702 return self.copy(path, *args, success=success, **kwargs)
707 ignore_content_type=False,
709 if_etag_not_match=None,
711 destination_account=None,
713 content_encoding=None,
714 content_disposition=None,
719 """ Full Pithos+ COPY at object level
721 --- request parameters ---
723 :param format: (string) json (default) or xml
725 :param ignore_content_type: (bool) Ignore the supplied Content-Type
727 --- request headers ---
729 :param if_etag_match: (string) if provided, return only results
730 with etag matching with this
732 :param if_etag_not_match: (string) if provided, return only results
733 with etag not matching with this
735 :param destination: (string) The destination path in the form
736 /<container>/<object>
738 :param destination_account: (string) The destination account to copy to
740 :param content_type: (string) The MIME content type of the object
742 :param content_encoding: (string) The encoding of the object
744 :param content_disposition: (string) Object presentation style
746 :param source_version: (string) The source version to copy from
748 :param permissions: (dict) Object permissions in the form
749 (all fields are optional)
750 { 'read':[user1, group1, user2, ...],
751 'write':['user3, group2, group3, ...] }
753 :param public: (bool) If true, Object is publicly accessible
755 :param metadata: (dict) Optional user defined metadata in the form
756 {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
758 :returns: ConnectionResponse
761 self._assert_container()
763 self.set_param('format', format, iff=format)
764 self.set_param('ignore_content_type', iff=ignore_content_type)
766 self.set_header('If-Match', if_etag_match)
767 self.set_header('If-None-Match', if_etag_not_match)
768 self.set_header('Destination', destination)
769 self.set_header('Destination-Account', destination_account)
770 self.set_header('Content-Type', content_type)
771 self.set_header('Content-Encoding', content_encoding)
772 self.set_header('Content-Disposition', content_disposition)
775 for perm_type, perm_list in permissions.items():
777 perms = '' # Remove permissions
779 perms += ';' if perms else ''
780 perms += '%s=%s' % (perm_type, ','.join(perm_list))
781 self.set_header('X-Object-Sharing', perms)
782 self.set_header('X-Object-Public', public)
784 for key, val in metadata.items():
785 self.set_header('X-Object-Meta-' + key, val)
787 path = path4url(self.account, self.container, object)
788 success = kwargs.pop('success', 201)
789 return self.move(path, *args, success=success, **kwargs)
796 if_etag_not_match=None,
800 transfer_encoding=None,
801 content_encoding=None,
802 content_disposition=None,
812 """ Full Pithos+ POST at object level
814 --- request parameters ---
816 :param format: (string) json (default) or xml
818 :param update: (bool) Do not replace metadata
820 --- request headers ---
822 :param if_etag_match: (string) if provided, return only results
823 with etag matching with this
825 :param if_etag_not_match: (string) if provided, return only results
826 with etag not matching with this
828 :param content_length: (string) The size of the data written
830 :param content_type: (string) The MIME content type of the object
832 :param content_range: (string) The range of data supplied
834 :param transfer_encoding: (string) Set to chunked to specify
835 incremental uploading (if used, Content-Length is ignored)
837 :param content_encoding: (string) The encoding of the object
839 :param content_disposition: (string) Object presentation style
841 :param source_object: (string) Update with data from the object at
842 path /<container>/<object>
844 :param source_account: (string) The source account to update from
846 :param source_version: (string) The source version to copy from
848 :param object_bytes: (integer) The updated objects final size
850 :param manifest: (string) Object parts prefix as /<container>/<object>
852 :param permissions: (dict) Object permissions in the form (all fields
854 { 'read':[user1, group1, user2, ...],
855 'write':['user3, group2, group3, ...] }
857 :param public: (bool) If true, Object is publicly accessible
859 :param metadata: (dict) Optional user defined metadata in the form
860 {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
862 :returns: ConnectionResponse
865 self._assert_container()
867 self.set_param('format', format, iff=format)
868 self.set_param('update', iff=update)
870 self.set_header('If-Match', if_etag_match)
871 self.set_header('If-None-Match', if_etag_not_match)
875 iff=not transfer_encoding)
876 self.set_header('Content-Type', content_type)
877 self.set_header('Content-Range', content_range)
878 self.set_header('Transfer-Encoding', transfer_encoding)
879 self.set_header('Content-Encoding', content_encoding)
880 self.set_header('Content-Disposition', content_disposition)
881 self.set_header('X-Source-Object', source_object)
882 self.set_header('X-Source-Account', source_account)
883 self.set_header('X-Source-Version', source_version)
884 self.set_header('X-Object-Bytes', object_bytes)
885 self.set_header('X-Object-Manifest', manifest)
888 for perm_type, perm_list in permissions.items():
890 perms = '' # Remove permissions
892 perms += ';' if perms else ''
893 perms += '%s=%s' % (perm_type, ','.join(perm_list))
894 self.set_header('X-Object-Sharing', perms)
895 self.set_header('X-Object-Public', public)
896 for key, val in metadata.items():
897 self.set_header('X-Object-Meta-' + key, val)
899 path = path4url(self.account, self.container, obj)
900 success = kwargs.pop('success', (202, 204))
901 return self.post(path, *args, success=success, **kwargs)
905 until=None, delimiter=None,
907 """ Full Pithos+ DELETE at object level
909 --- request parameters ---
911 :param until: (string) Optional timestamp
913 :returns: ConnectionResponse
915 self._assert_container()
917 self.set_param('until', until, iff=until)
918 self.set_param('delimiter', delimiter, iff=delimiter)
920 path = path4url(self.account, self.container, object)
921 success = kwargs.pop('success', 204)
922 return self.delete(path, *args, success=success, **kwargs)