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 published, False, unpublished
569 :param metadata: (dict) Optional user defined metadata in the form
570 {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
572 :returns: ConnectionResponse
575 self._assert_container()
577 self.set_param('format', format, iff=format)
578 self.set_param('hashmap', hashmap, iff=hashmap)
579 self.set_param('delimiter', delimiter, iff=delimiter)
581 self.set_header('If-Match', if_etag_match)
582 self.set_header('If-None-Match', if_etag_not_match)
583 self.set_header('ETag', etag)
584 self.set_header('Content-Length', content_length)
585 self.set_header('Content-Type', content_type)
586 self.set_header('Transfer-Encoding', transfer_encoding)
587 self.set_header('X-Copy-From', copy_from)
588 self.set_header('X-Move-From', move_from)
589 self.set_header('X-Source-Account', source_account)
590 self.set_header('X-Source-Version', source_version)
591 self.set_header('Content-Encoding', content_encoding)
592 self.set_header('Content-Disposition', content_disposition)
593 self.set_header('X-Object-Manifest', manifest)
597 for perm_type, perm_list in permissions.items():
599 perms = '' # Remove permissions
601 perms += ';' if perms else ''
602 perms += '%s=%s' % (perm_type, ','.join(perm_list))
603 self.set_header('X-Object-Sharing', perms)
604 self.set_header('X-Object-Public', public, public is not None)
606 for key, val in metadata.items():
607 self.set_header('X-Object-Meta-' + key, val)
609 path = path4url(self.account, self.container, obj)
610 success = kwargs.pop('success', 201)
611 return self.put(path, *args, success=success, **kwargs)
614 self, obj, destination,
616 ignore_content_type=False,
618 if_etag_not_match=None,
619 destination_account=None,
621 content_encoding=None,
622 content_disposition=None,
628 """ Full Pithos+ COPY at object level
630 --- request parameters ---
632 :param format: (string) json (default) or xml
634 :param ignore_content_type: (bool) Ignore the supplied Content-Type
636 --- request headers ---
638 :param if_etag_match: (string) if provided, copy only results
639 with etag matching with this
641 :param if_etag_not_match: (string) if provided, copy only results
642 with etag not matching with this
644 :param destination: (string) The destination path in the form
645 /<container>/<object>
647 :param destination_account: (string) The destination account to copy to
649 :param content_type: (string) The MIME content type of the object
651 :param content_encoding: (string) The encoding of the object
653 :param content_disposition: (string) Object resentation style
655 :param source_version: (string) The source version to copy from
657 :param permissions: (dict) Object permissions in the form
658 (all fields are optional)
659 { 'read':[user1, group1, user2, ...],
660 'write':['user3, group2, group3, ...] }
662 :param public: (bool) If true, Object is published, False, unpublished
664 :param metadata: (dict) Optional user defined metadata in the form
665 {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
666 Metadata are appended to the source metadata. In case of same
667 keys, they replace the old metadata
669 :returns: ConnectionResponse
672 self._assert_container()
674 self.set_param('format', format, iff=format)
675 self.set_param('ignore_content_type', iff=ignore_content_type)
677 self.set_header('If-Match', if_etag_match)
678 self.set_header('If-None-Match', if_etag_not_match)
679 self.set_header('Destination', destination)
680 self.set_header('Destination-Account', destination_account)
681 self.set_header('Content-Type', content_type)
682 self.set_header('Content-Encoding', content_encoding)
683 self.set_header('Content-Disposition', content_disposition)
684 self.set_header('X-Source-Version', source_version)
687 for perm_type, perm_list in permissions.items():
689 perms = '' # Remove permissions
691 perms += ';' if perms else ''
692 perms += '%s=%s' % (perm_type, ','.join(perm_list))
693 self.set_header('X-Object-Sharing', perms)
694 self.set_header('X-Object-Public', public, public is not None)
696 for key, val in metadata.items():
697 self.set_header('X-Object-Meta-' + key, val)
699 path = path4url(self.account, self.container, obj)
700 success = kwargs.pop('success', 201)
701 return self.copy(path, *args, success=success, **kwargs)
706 ignore_content_type=False,
708 if_etag_not_match=None,
710 destination_account=None,
712 content_encoding=None,
713 content_disposition=None,
718 """ Full Pithos+ COPY at object level
720 --- request parameters ---
722 :param format: (string) json (default) or xml
724 :param ignore_content_type: (bool) Ignore the supplied Content-Type
726 --- request headers ---
728 :param if_etag_match: (string) if provided, return only results
729 with etag matching with this
731 :param if_etag_not_match: (string) if provided, return only results
732 with etag not matching with this
734 :param destination: (string) The destination path in the form
735 /<container>/<object>
737 :param destination_account: (string) The destination account to copy to
739 :param content_type: (string) The MIME content type of the object
741 :param content_encoding: (string) The encoding of the object
743 :param content_disposition: (string) Object presentation style
745 :param source_version: (string) The source version to copy from
747 :param permissions: (dict) Object permissions in the form
748 (all fields are optional)
749 { 'read':[user1, group1, user2, ...],
750 'write':['user3, group2, group3, ...] }
752 :param public: (bool) If true, Object is published, False, unpublished
754 :param metadata: (dict) Optional user defined metadata in the form
755 {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
757 :returns: ConnectionResponse
760 self._assert_container()
762 self.set_param('format', format, iff=format)
763 self.set_param('ignore_content_type', iff=ignore_content_type)
765 self.set_header('If-Match', if_etag_match)
766 self.set_header('If-None-Match', if_etag_not_match)
767 self.set_header('Destination', destination)
768 self.set_header('Destination-Account', destination_account)
769 self.set_header('Content-Type', content_type)
770 self.set_header('Content-Encoding', content_encoding)
771 self.set_header('Content-Disposition', content_disposition)
774 for perm_type, perm_list in permissions.items():
776 perms = '' # Remove permissions
778 perms += ';' if perms else ''
779 perms += '%s=%s' % (perm_type, ','.join(perm_list))
780 self.set_header('X-Object-Sharing', perms)
781 self.set_header('X-Object-Public', public, public is not None)
783 for key, val in metadata.items():
784 self.set_header('X-Object-Meta-' + key, val)
786 path = path4url(self.account, self.container, object)
787 success = kwargs.pop('success', 201)
788 return self.move(path, *args, success=success, **kwargs)
795 if_etag_not_match=None,
799 transfer_encoding=None,
800 content_encoding=None,
801 content_disposition=None,
811 """ Full Pithos+ POST at object level
813 --- request parameters ---
815 :param format: (string) json (default) or xml
817 :param update: (bool) Do not replace metadata
819 --- request headers ---
821 :param if_etag_match: (string) if provided, return only results
822 with etag matching with this
824 :param if_etag_not_match: (string) if provided, return only results
825 with etag not matching with this
827 :param content_length: (string) The size of the data written
829 :param content_type: (string) The MIME content type of the object
831 :param content_range: (string) The range of data supplied
833 :param transfer_encoding: (string) Set to chunked to specify
834 incremental uploading (if used, Content-Length is ignored)
836 :param content_encoding: (string) The encoding of the object
838 :param content_disposition: (string) Object presentation style
840 :param source_object: (string) Update with data from the object at
841 path /<container>/<object>
843 :param source_account: (string) The source account to update from
845 :param source_version: (string) The source version to copy from
847 :param object_bytes: (integer) The updated objects final size
849 :param manifest: (string) Object parts prefix as /<container>/<object>
851 :param permissions: (dict) Object permissions in the form (all fields
853 { 'read':[user1, group1, user2, ...],
854 'write':['user3, group2, group3, ...] }
856 :param public: (bool) If true, Object is published, False, unpublished
858 :param metadata: (dict) Optional user defined metadata in the form
859 {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
861 :returns: ConnectionResponse
864 self._assert_container()
866 self.set_param('format', format, iff=format)
867 self.set_param('update', iff=update)
869 self.set_header('If-Match', if_etag_match)
870 self.set_header('If-None-Match', if_etag_not_match)
874 iff=not transfer_encoding)
875 self.set_header('Content-Type', content_type)
876 self.set_header('Content-Range', content_range)
877 self.set_header('Transfer-Encoding', transfer_encoding)
878 self.set_header('Content-Encoding', content_encoding)
879 self.set_header('Content-Disposition', content_disposition)
880 self.set_header('X-Source-Object', source_object)
881 self.set_header('X-Source-Account', source_account)
882 self.set_header('X-Source-Version', source_version)
883 self.set_header('X-Object-Bytes', object_bytes)
884 self.set_header('X-Object-Manifest', manifest)
887 for perm_type, perm_list in permissions.items():
889 perms = '' # Remove permissions
891 perms += ';' if perms else ''
892 perms += '%s=%s' % (perm_type, ','.join(perm_list))
893 self.set_header('X-Object-Sharing', perms)
894 self.set_header('X-Object-Public', public, public is not None)
895 for key, val in metadata.items():
896 self.set_header('X-Object-Meta-' + key, val)
898 path = path4url(self.account, self.container, obj)
899 success = kwargs.pop('success', (202, 204))
900 return self.post(path, *args, success=success, **kwargs)
904 until=None, delimiter=None,
906 """ Full Pithos+ DELETE at object level
908 --- request parameters ---
910 :param until: (string) Optional timestamp
912 :returns: ConnectionResponse
914 self._assert_container()
916 self.set_param('until', until, iff=until)
917 self.set_param('delimiter', delimiter, iff=delimiter)
919 path = path4url(self.account, self.container, object)
920 success = kwargs.pop('success', 204)
921 return self.delete(path, *args, success=success, **kwargs)