Needs database update.
Fixes #1451
-Upgrade notes.
+UPGRADE
+=======
+
+0.7.9 -> 0.7.10
+---------------
+* Update settings.py (BACKEND*, SERVICE_NAME, *_EMAIL, *_TARGET)
+* Update 'attributes' table in mysql:
+
+ mysql> update attributes set `key`='ETag' where `key`='hash';
+
========================= ================================
Revision Description
========================= ================================
-0.7 (Oct 21, 2011) Suggest upload/download methods using hashmaps.
+0.7 (Nov 21, 2011) Suggest upload/download methods using hashmaps.
\ Propose syncing algorithm.
\ Support cross-account object copy and move.
\ Pass token as a request parameter when using ``POST`` via an HTML form.
\ Use container ``POST`` to upload missing blocks of data.
\ Report policy in account headers.
\ Add insufficient quota reply.
+\ Use special meta to always report Merkle hash.
0.6 (Sept 13, 2011) Reply with Merkle hash as the ETag when updating objects.
\ Include version id in object replace/change replies.
\ Change conflict (409) replies format to text.
content_encoding The encoding of the object (optional)
content-disposition The presentation style of the object (optional)
last_modified The last object modification date (regardless of version)
+x_object_hash The Merkle hash
x_object_version The object's version identifier
x_object_version_timestamp The object's version timestamp
x_object_modified_by The user that committed the object's version
Last-Modified The last object modification date (regardless of version)
Content-Encoding The encoding of the object (optional)
Content-Disposition The presentation style of the object (optional)
+X-Object-Hash The Merkle hash
X-Object-Version The object's version identifier
X-Object-Version-Timestamp The object's version timestamp
X-Object-Modified-By The user that comitted the object's version
Last-Modified The last object modification date (regardless of version)
Content-Encoding The encoding of the object (optional)
Content-Disposition The presentation style of the object (optional)
+X-Object-Hash The Merkle hash
X-Object-Version The object's version identifier
X-Object-Version-Timestamp The object's version timestamp
X-Object-Modified-By The user that comitted the object's version
* Multi-range object ``GET`` support as outlined in RFC2616.
* Object hashmap retrieval through ``GET`` and the ``format`` parameter.
* Object create via hashmap through ``PUT`` and the ``format`` parameter.
+* The object's Merkle hash is always returned in the ``X-Object-Hash`` header.
* Object create using ``POST`` to support standard HTML forms.
* 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.
* Include new version identifier in replies for object replace/change requests.
except NameError:
pass
else:
+ rename_meta_key(meta, 'hash', 'x_object_hash') # Will be replaced by ETag.
+ rename_meta_key(meta, 'ETag', 'hash')
rename_meta_key(meta, 'modified', 'last_modified')
rename_meta_key(meta, 'modified_by', 'x_object_modified_by')
rename_meta_key(meta, 'version', 'x_object_version')
validate_matching_preconditions(request, meta)
except NotModified:
response = HttpResponse(status=304)
- response['ETag'] = meta['hash']
+ response['ETag'] = meta['ETag']
return response
response = HttpResponse(status=200)
validate_matching_preconditions(request, meta)
except NotModified:
response = HttpResponse(status=304)
- response['ETag'] = meta['hash']
+ response['ETag'] = meta['ETag']
return response
sizes = []
except:
raise BadRequest('Invalid data formatting')
- meta.update({'hash': hashmap_hash(request, hashmap)}) # Update ETag.
+ meta.update({'ETag': hashmap_hash(request, hashmap)}) # Update ETag.
else:
md5 = hashlib.md5()
size = 0
hashmap.append(request.backend.put_block(data))
md5.update(data)
- meta['hash'] = md5.hexdigest().lower()
+ meta['ETag'] = md5.hexdigest().lower()
etag = request.META.get('HTTP_ETAG')
- if etag and parse_etags(etag)[0].lower() != meta['hash']:
+ if etag and parse_etags(etag)[0].lower() != meta['ETag']:
raise UnprocessableEntity('Object ETag does not match')
try:
raise ItemNotFound('Object does not exist')
response = HttpResponse(status=201)
- response['ETag'] = meta['hash']
+ response['ETag'] = meta['ETag']
response['X-Object-Version'] = version_id
return response
hashmap.append(request.backend.put_block(data))
md5.update(data)
- meta['hash'] = md5.hexdigest().lower()
+ meta['ETag'] = md5.hexdigest().lower()
try:
version_id = request.backend.update_object_hashmap(request.user_uniq,
raise RequestEntityTooLarge('Quota exceeded')
response = HttpResponse(status=201)
- response['ETag'] = meta['hash']
+ response['ETag'] = meta['ETag']
response['X-Object-Version'] = version_id
return response
if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
validate_matching_preconditions(request, prev_meta)
- # If replacing, keep previous values of 'Content-Type' and 'hash'.
+ # If replacing, keep previous values of 'Content-Type' and 'ETag'.
replace = True
if 'update' in request.GET:
replace = False
if replace:
- for k in ('Content-Type', 'hash'):
+ for k in ('Content-Type', 'ETag'):
if k in prev_meta:
meta[k] = prev_meta[k]
if dest_bytes is not None and dest_bytes < size:
size = dest_bytes
hashmap = hashmap[:(int((size - 1) / request.backend.block_size) + 1)]
- meta.update({'hash': hashmap_hash(request, hashmap)}) # Update ETag.
+ meta.update({'ETag': hashmap_hash(request, hashmap)}) # Update ETag.
try:
version_id = request.backend.update_object_hashmap(request.user_uniq,
v_account, v_container, v_object, size, hashmap, meta,
raise ItemNotFound('Object does not exist')
response = HttpResponse(status=204)
- response['ETag'] = meta['hash']
+ response['ETag'] = meta['ETag']
response['X-Object-Version'] = version_id
return response
return meta, get_sharing(request), get_public(request)
def put_object_headers(response, meta, restricted=False):
- response['ETag'] = meta['hash']
+ response['ETag'] = meta['ETag']
response['Content-Length'] = meta['bytes']
response['Content-Type'] = meta.get('Content-Type', 'application/octet-stream')
response['Last-Modified'] = http_date(int(meta['modified']))
if not restricted:
+ response['X-Object-Hash'] = meta['hash']
response['X-Object-Modified-By'] = smart_str(meta['modified_by'], strings_only=True)
response['X-Object-Version'] = meta['version']
response['X-Object-Version-Timestamp'] = http_date(int(meta['version_timestamp']))
"""Update metadata if the object has an X-Object-Manifest."""
if 'X-Object-Manifest' in meta:
- hash = ''
+ etag = ''
bytes = 0
try:
src_container, src_name = split_container_object_string('/' + meta['X-Object-Manifest'])
for x in objects:
src_meta = request.backend.get_object_meta(request.user_uniq,
v_account, src_container, x[0], x[1])
- hash += src_meta['hash']
+ etag += src_meta['ETag']
bytes += src_meta['bytes']
except:
# Ignore errors.
return
meta['bytes'] = bytes
md5 = hashlib.md5()
- md5.update(hash)
- meta['hash'] = md5.hexdigest().lower()
+ md5.update(etag)
+ meta['ETag'] = md5.hexdigest().lower()
def update_sharing_meta(request, permissions, v_account, v_container, v_object, meta):
if permissions is None:
def validate_matching_preconditions(request, meta):
"""Check that the ETag conforms with the preconditions set."""
- hash = meta.get('hash', None)
+ etag = meta.get('ETag', None)
if_match = request.META.get('HTTP_IF_MATCH')
if if_match is not None:
- if hash is None:
+ if etag is None:
raise PreconditionFailed('Resource does not exist')
- if if_match != '*' and hash not in [x.lower() for x in parse_etags(if_match)]:
+ if if_match != '*' and etag not in [x.lower() for x in parse_etags(if_match)]:
raise PreconditionFailed('Resource ETag does not match')
if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
if if_none_match is not None:
# TODO: If this passes, must ignore If-Modified-Since header.
- if hash is not None:
- if if_none_match == '*' or hash in [x.lower() for x in parse_etags(if_none_match)]:
+ if etag is not None:
+ if if_none_match == '*' or etag in [x.lower() for x in parse_etags(if_none_match)]:
# TODO: Continue if an If-Modified-Since header is present.
if request.method in ('HEAD', 'GET'):
raise NotModified('Resource ETag matches')
ranges = [(0, size)]
ret = 200
except ValueError:
- if if_range != meta['hash']:
+ if if_range != meta['ETag']:
ranges = [(0, size)]
ret = 200
'bytes': The total data size
+ 'hash': The hashmap hash
+
'modified': Last modification timestamp (overall)
'modified_by': The user that committed the object (version requested)
if self._get_statistics(node)[0] > 0:
raise IndexError('Container is not empty')
- hashes = self.node.node_purge_children(node, until, CLUSTER_HISTORY)
+ hashes = self.node.node_purge_children(node, inf, CLUSTER_HISTORY)
for h in hashes:
self.store.map_delete(h)
self.node.node_purge_children(node, inf, CLUSTER_DELETED)
modified = del_props[self.MTIME]
meta = dict(self.node.attribute_get(props[self.SERIAL]))
- meta.update({'name': name, 'bytes': props[self.SIZE]})
+ meta.update({'name': name, 'bytes': props[self.SIZE], 'hash':props[self.HASH]})
meta.update({'version': props[self.SERIAL], 'version_timestamp': props[self.MTIME]})
meta.update({'modified': modified, 'modified_by': props[self.MUSER]})
return meta
hashes += self.node.node_purge(node, until, CLUSTER_HISTORY)
for h in hashes:
self.store.map_delete(h)
- self.node.node_purge_children(node, until, CLUSTER_DELETED)
+ self.node.node_purge(node, until, CLUSTER_DELETED)
try:
props = self._get_version(node)
except NameError:
- pass
- else:
self.permissions.access_clear(path)
return
raise ItemNotFound('Object does not exist')
update_manifest_meta(request, v_account, meta)
- response = HttpResponse(status=204)
- put_object_meta(response, meta, True)
+ response = HttpResponse(status=200)
+ put_object_headers(response, meta, True)
return response
@api_method('GET', user_required=False)
validate_matching_preconditions(request, meta)
except NotModified:
response = HttpResponse(status=304)
- response['ETag'] = meta['hash']
+ response['ETag'] = meta['ETag']
return response
sizes = []