Revision 37bee317
b/docs/source/devguide.rst | ||
---|---|---|
25 | 25 |
========================= ================================ |
26 | 26 |
Revision Description |
27 | 27 |
========================= ================================ |
28 |
0.8 (Dec 16, 2011) Update allowed versioning values.
|
|
28 |
0.8 (Dec 19, 2011) Update allowed versioning values.
|
|
29 | 29 |
\ Change policy/meta formatting in JSON/XML replies. |
30 | 30 |
\ Document that all non-ASCII characters in headers should be URL-encoded. |
31 | 31 |
\ Support metadata-based queries when listing objects at the container level. |
32 | 32 |
\ Note Content-Type issue when using the internal django web server. |
33 |
\ Add object UUID field. |
|
33 | 34 |
0.7 (Nov 21, 2011) Suggest upload/download methods using hashmaps. |
34 | 35 |
\ Propose syncing algorithm. |
35 | 36 |
\ Support cross-account object copy and move. |
... | ... | |
478 | 479 |
content-disposition The presentation style of the object (optional) |
479 | 480 |
last_modified The last object modification date (regardless of version) |
480 | 481 |
x_object_hash The Merkle hash |
482 |
x_object_uuid The object's UUID |
|
481 | 483 |
x_object_version The object's version identifier |
482 | 484 |
x_object_version_timestamp The object's version timestamp |
483 | 485 |
x_object_modified_by The user that committed the object's version |
... | ... | |
505 | 507 |
"last_modified": "2011-12-02T08:10:41.565891+00:00", |
506 | 508 |
"x_object_meta": {"asdf": "qwerty"}, |
507 | 509 |
"x_object_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", |
510 |
"x_object_uuid": "8ed9af1b-c948-4bb6-82b0-48344f5c822c", |
|
508 | 511 |
"x_object_version": 98, |
509 | 512 |
"x_object_version_timestamp": "1322813441.565891", |
510 | 513 |
"x_object_modified_by": "user"}, ...] |
... | ... | |
525 | 528 |
<key>asdf</key><value>qwerty</value> |
526 | 529 |
</x_object_meta> |
527 | 530 |
<x_object_hash>e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855</x_object_hash> |
531 |
<x_object_uuid>8ed9af1b-c948-4bb6-82b0-48344f5c822c</x_object_uuid> |
|
528 | 532 |
<x_object_version>98</x_object_version> |
529 | 533 |
<x_object_version_timestamp>1322813441.565891</x_object_version_timestamp> |
530 | 534 |
<x_object_modified_by>chazapis</x_object_modified_by> |
... | ... | |
680 | 684 |
Content-Encoding The encoding of the object (optional) |
681 | 685 |
Content-Disposition The presentation style of the object (optional) |
682 | 686 |
X-Object-Hash The Merkle hash |
687 |
X-Object-UUID The object's UUID |
|
683 | 688 |
X-Object-Version The object's version identifier |
684 | 689 |
X-Object-Version-Timestamp The object's version timestamp |
685 | 690 |
X-Object-Modified-By The user that comitted the object's version |
... | ... | |
776 | 781 |
Content-Encoding The encoding of the object (optional) |
777 | 782 |
Content-Disposition The presentation style of the object (optional) |
778 | 783 |
X-Object-Hash The Merkle hash |
784 |
X-Object-UUID The object's UUID |
|
779 | 785 |
X-Object-Version The object's version identifier |
780 | 786 |
X-Object-Version-Timestamp The object's version timestamp |
781 | 787 |
X-Object-Modified-By The user that comitted the object's version |
... | ... | |
1063 | 1069 |
* Object hashmap retrieval through ``GET`` and the ``format`` parameter. |
1064 | 1070 |
* Object create via hashmap through ``PUT`` and the ``format`` parameter. |
1065 | 1071 |
* The object's Merkle hash is always returned in the ``X-Object-Hash`` header. |
1072 |
* The object's UUID is always returned in the ``X-Object-UUID`` header. The UUID remains unchanged, even when the object's data or metadata changes, or the object is moved to another path (is renamed). A new UUID is assigned when creating or copying an object. |
|
1066 | 1073 |
* Object create using ``POST`` to support standard HTML forms. |
1067 | 1074 |
* Partial object updates through ``POST``, using the ``Content-Length``, ``Content-Type``, ``Content-Range`` and ``Transfer-Encoding`` headers. Use another object's data to update with ``X-Source-Object`` and ``X-Source-Version``. Truncate with ``X-Object-Bytes``. New ETag corresponds to the Merkle hash of the object's hashmap. |
1068 | 1075 |
* Include new version identifier in replies for object replace/change requests. |
b/pithos/api/functions.py | ||
---|---|---|
551 | 551 |
else: |
552 | 552 |
rename_meta_key(meta, 'hash', 'x_object_hash') # Will be replaced by ETag. |
553 | 553 |
rename_meta_key(meta, 'ETag', 'hash') |
554 |
rename_meta_key(meta, 'uuid', 'x_object_uuid') |
|
554 | 555 |
rename_meta_key(meta, 'modified', 'last_modified') |
555 | 556 |
rename_meta_key(meta, 'modified_by', 'x_object_modified_by') |
556 | 557 |
rename_meta_key(meta, 'version', 'x_object_version') |
b/pithos/api/util.py | ||
---|---|---|
186 | 186 |
response['Last-Modified'] = http_date(int(meta['modified'])) |
187 | 187 |
if not restricted: |
188 | 188 |
response['X-Object-Hash'] = meta['hash'] |
189 |
response['X-Object-UUID'] = meta['uuid'] |
|
189 | 190 |
response['X-Object-Modified-By'] = smart_str(meta['modified_by'], strings_only=True) |
190 | 191 |
response['X-Object-Version'] = meta['version'] |
191 | 192 |
response['X-Object-Version-Timestamp'] = http_date(int(meta['version_timestamp'])) |
b/pithos/backends/base.py | ||
---|---|---|
531 | 531 |
""" |
532 | 532 |
return [] |
533 | 533 |
|
534 |
def get_uuid(self, user, uuid): |
|
535 |
"""Return the (account, container, name) for the UUID given. |
|
536 |
|
|
537 |
Raises: |
|
538 |
NotAllowedError: Operation not permitted |
|
539 |
|
|
540 |
NameError: UUID does not exist |
|
541 |
""" |
|
542 |
return None |
|
543 |
|
|
534 | 544 |
def get_public(self, user, public): |
535 | 545 |
"""Return the (account, container, name) for the public id given. |
536 | 546 |
|
b/pithos/backends/lib/sqlalchemy/node.py | ||
---|---|---|
599 | 599 |
"""Return a sequence of values for the properties of |
600 | 600 |
the version specified by serial and the keys, in the order given. |
601 | 601 |
If keys is empty, return all properties in the order |
602 |
(serial, node, hash, size, source, mtime, muser, cluster). |
|
602 |
(serial, node, hash, size, source, mtime, muser, uuid, cluster).
|
|
603 | 603 |
""" |
604 | 604 |
|
605 | 605 |
v = self.versions.alias() |
606 |
s = select([v.c.serial, v.c.node, v.c.hash, v.c.size, |
|
607 |
v.c.source, v.c.mtime, v.c.muser, v.c.cluster], v.c.serial == serial) |
|
606 |
s = select([v.c.serial, v.c.node, v.c.hash, |
|
607 |
v.c.size, v.c.source, v.c.mtime, |
|
608 |
v.c.muser, v.c.uuid, v.c.cluster], v.c.serial == serial) |
|
608 | 609 |
rp = self.conn.execute(s) |
609 | 610 |
r = rp.fetchone() |
610 | 611 |
rp.close() |
... | ... | |
897 | 898 |
rp.close() |
898 | 899 |
|
899 | 900 |
return matches, prefixes |
901 |
|
|
902 |
def latest_uuid(self, uuid): |
|
903 |
"""Return a (path, serial) tuple, for the latest version of the given uuid.""" |
|
904 |
|
|
905 |
v = self.versions.alias('v') |
|
906 |
n = self.nodes.alias('n') |
|
907 |
s = select([n.c.path, v.c.serial]) |
|
908 |
filtered = select([func.max(self.versions.c.serial)]) |
|
909 |
s = s.where(v.c.serial == filtered.where(self.versions.c.uuid == uuid)) |
|
910 |
s = s.where(n.c.node == v.c.node) |
|
911 |
|
|
912 |
r = self.conn.execute(s) |
|
913 |
l = r.fetchone() |
|
914 |
r.close() |
|
915 |
return l |
b/pithos/backends/lib/sqlite/node.py | ||
---|---|---|
499 | 499 |
"""Return a sequence of values for the properties of |
500 | 500 |
the version specified by serial and the keys, in the order given. |
501 | 501 |
If keys is empty, return all properties in the order |
502 |
(serial, node, hash, size, source, mtime, muser, cluster). |
|
502 |
(serial, node, hash, size, source, mtime, muser, uuid, cluster).
|
|
503 | 503 |
""" |
504 | 504 |
|
505 |
q = ("select serial, node, hash, size, source, mtime, muser, cluster " |
|
505 |
q = ("select serial, node, hash, size, source, mtime, muser, uuid, cluster "
|
|
506 | 506 |
"from versions " |
507 | 507 |
"where serial = ?") |
508 | 508 |
self.execute(q, (serial,)) |
... | ... | |
653 | 653 |
q = ("select distinct a.key " |
654 | 654 |
"from attributes a, versions v, nodes n " |
655 | 655 |
"where v.serial = (select max(serial) " |
656 |
"from versions " |
|
657 |
"where node = v.node and mtime < ?) " |
|
656 |
"from versions "
|
|
657 |
"where node = v.node and mtime < ?) "
|
|
658 | 658 |
"and v.cluster != ? " |
659 | 659 |
"and v.node in (select node " |
660 |
"from nodes " |
|
661 |
"where parent = ?) " |
|
660 |
"from nodes "
|
|
661 |
"where parent = ?) "
|
|
662 | 662 |
"and a.serial = v.serial " |
663 | 663 |
"and a.domain = ? " |
664 | 664 |
"and n.node = v.node") |
... | ... | |
728 | 728 |
q = ("select distinct n.path, v.serial " |
729 | 729 |
"from attributes a, versions v, nodes n " |
730 | 730 |
"where v.serial = (select max(serial) " |
731 |
"from versions " |
|
732 |
"where node = v.node and mtime < ?) " |
|
731 |
"from versions "
|
|
732 |
"where node = v.node and mtime < ?) "
|
|
733 | 733 |
"and v.cluster != ? " |
734 | 734 |
"and v.node in (select node " |
735 |
"from nodes " |
|
736 |
"where parent = ?) " |
|
735 |
"from nodes "
|
|
736 |
"where parent = ?) "
|
|
737 | 737 |
"and a.serial = v.serial " |
738 | 738 |
"and n.node = v.node " |
739 | 739 |
"and n.path > ? and n.path < ?") |
... | ... | |
795 | 795 |
execute(q, args) |
796 | 796 |
|
797 | 797 |
return matches, prefixes |
798 |
|
|
799 |
def latest_uuid(self, uuid): |
|
800 |
"""Return a (path, serial) tuple, for the latest version of the given uuid.""" |
|
801 |
|
|
802 |
q = ("select n.path, v.serial " |
|
803 |
"from versions v, nodes n " |
|
804 |
"where v.serial = (select max(serial) " |
|
805 |
"from versions " |
|
806 |
"where uuid = ?) " |
|
807 |
"and n.node = v.node") |
|
808 |
self.execute(q, (uuid,)) |
|
809 |
return self.fetchone() |
b/pithos/backends/modular.py | ||
---|---|---|
34 | 34 |
import sys |
35 | 35 |
import os |
36 | 36 |
import time |
37 |
import uuid |
|
37 |
import uuid as uuidlib
|
|
38 | 38 |
import logging |
39 | 39 |
import binascii |
40 | 40 |
|
... | ... | |
96 | 96 |
for x in ['READ', 'WRITE']: |
97 | 97 |
setattr(self, x, getattr(self.db_module, x)) |
98 | 98 |
self.node = self.db_module.Node(**params) |
99 |
for x in ['ROOTNODE', 'SERIAL', 'HASH', 'SIZE', 'MTIME', 'MUSER', 'CLUSTER']: |
|
99 |
for x in ['ROOTNODE', 'SERIAL', 'HASH', 'SIZE', 'MTIME', 'MUSER', 'UUID', 'CLUSTER']:
|
|
100 | 100 |
setattr(self, x, getattr(self.db_module, x)) |
101 | 101 |
|
102 | 102 |
__import__(block_module) |
... | ... | |
630 | 630 |
return [[x[self.SERIAL], x[self.MTIME]] for x in versions if x[self.CLUSTER] != CLUSTER_DELETED] |
631 | 631 |
|
632 | 632 |
@backend_method |
633 |
def get_uuid(self, user, uuid): |
|
634 |
"""Return the (account, container, name) for the UUID given.""" |
|
635 |
logger.debug("get_uuid: %s", uuid) |
|
636 |
info = self.node.latest_uuid(uuid) |
|
637 |
if info is None: |
|
638 |
raise NameError |
|
639 |
path, serial = info |
|
640 |
account, container, name = path.split('/', 2) |
|
641 |
self._can_read(user, account, container, name) |
|
642 |
return (account, container, name) |
|
643 |
|
|
644 |
@backend_method |
|
633 | 645 |
def get_public(self, user, public): |
634 | 646 |
"""Return the (account, container, name) for the public id given.""" |
635 | 647 |
logger.debug("get_public: %s", public) |
636 | 648 |
if public is None or public < ULTIMATE_ANSWER: |
637 | 649 |
raise NameError |
638 | 650 |
path = self.permissions.public_path(public - ULTIMATE_ANSWER) |
651 |
if path is None: |
|
652 |
raise NameError |
|
639 | 653 |
account, container, name = path.split('/', 2) |
640 | 654 |
self._can_read(user, account, container, name) |
641 | 655 |
return (account, container, name) |
... | ... | |
669 | 683 |
|
670 | 684 |
# Path functions. |
671 | 685 |
|
672 |
def _generate_uuid(): |
|
673 |
return str(uuid.uuid4()) |
|
686 |
def _generate_uuid(self):
|
|
687 |
return str(uuidlib.uuid4())
|
|
674 | 688 |
|
675 | 689 |
def _put_object_node(self, path, parent, name): |
676 | 690 |
path = '/'.join((path, name)) |
... | ... | |
756 | 770 |
if size is None: |
757 | 771 |
hash = src_hash # This way hash can be set to None. |
758 | 772 |
size = src_size |
759 |
uniq = self._generate_uuid() if is_copy or src_version_id is None else props[self.UUID]
|
|
773 |
uuid = self._generate_uuid() if is_copy or src_version_id is None else props[self.UUID]
|
|
760 | 774 |
|
761 | 775 |
if src_version_id is not None: |
762 | 776 |
self.node.version_recluster(src_version_id, CLUSTER_HISTORY) |
763 |
dest_version_id, mtime = self.node.version_create(node, hash, size, src_version_id, user, uniq, cluster)
|
|
777 |
dest_version_id, mtime = self.node.version_create(node, hash, size, src_version_id, user, uuid, cluster)
|
|
764 | 778 |
return src_version_id, dest_version_id |
765 | 779 |
|
766 | 780 |
def _put_metadata_duplicate(self, src_version_id, dest_version_id, domain, meta, replace=False): |
Also available in: Unified diff