From: Antony Chazapis Date: Tue, 24 Jan 2012 15:23:23 +0000 (+0200) Subject: Use format parameter for Conflict (409) replies. X-Git-Tag: pithos/v0.8.3~1 X-Git-Url: https://code.grnet.gr/git/pithos/commitdiff_plain/6b6b6c1e4e162f749b230b1fcd61eafb7f127a4a Use format parameter for Conflict (409) replies. Fixes #1934 --- diff --git a/docs/source/devguide.rst b/docs/source/devguide.rst index c781670..2ad3688 100644 --- a/docs/source/devguide.rst +++ b/docs/source/devguide.rst @@ -36,6 +36,7 @@ Revision Description \ Always reply with the MD5 in the ETag. \ Note that ``/login`` will only work if an external authentication system is defined. \ Include option to ignore Content-Type on ``COPY``/``MOVE``. +\ Use format parameter for conflict (409) replies. 0.7 (Nov 21, 2011) Suggest upload/download methods using hashmaps. \ Propose syncing algorithm. \ Support cross-account object copy and move. @@ -840,11 +841,11 @@ X-Object-Meta-* Optional user defined metadata ====================== =================================== Request Parameter Name Value ====================== =================================== -format Optional extended request type (can be ``json`` or ``xml``) +format Optional extended request/conflict response type (can be ``json`` or ``xml``) hashmap Optional hashmap provided instead of data (no value parameter) ====================== =================================== -The request is the object's data (or part of it), except if a hashmap is provided (using ``hashmap`` and ``format`` parameters). If using a hashmap and all different parts are stored in the server, the object is created, otherwise the server returns Conflict (409) with the list of the missing parts (in a simple text format, with one hash per line). +The request is the object's data (or part of it), except if a hashmap is provided (using ``hashmap`` and ``format`` parameters). If using a hashmap and all different parts are stored in the server, the object is created. Otherwise the server returns Conflict (409) with the list of the missing parts (in simple text format, with one hash per line, or in JSON/XML - depending on the ``format`` parameter). Hashmaps should be formatted as outlined in ``GET``. @@ -861,7 +862,7 @@ The ``X-Object-Sharing`` header may include either a ``read=...`` comma-separate Return Code Description ============================== ============================== 201 (Created) The object has been created -409 (Conflict) The object can not be created from the provided hashmap, or there are conflicting permissions (a list of missing hashes, or a list of conflicting sharing paths will be included in the reply - in simple text format) +409 (Conflict) The object can not be created from the provided hashmap, or there are conflicting permissions (a list of missing hashes, or a list of conflicting sharing paths will be included in the reply) 411 (Length Required) Missing ``Content-Length`` or ``Content-Type`` in the request 413 (Request Entity Too Large) Insufficient quota to complete the request 422 (Unprocessable Entity) The MD5 checksum of the data written to the storage system does not match the (optionally) supplied ETag value @@ -871,14 +872,6 @@ Return Code Description COPY """" -====================== =================================== -Request Parameter Name Value -====================== =================================== -ignore_content_type Ignore the supplied Content-Type -====================== =================================== - -| - ==================== ================================ Request Header Name Value ==================== ================================ @@ -898,6 +891,13 @@ X-Object-Meta-* Optional user defined metadata :sup:`*` *When using django locally with the supplied web server, use the ignore_content_type parameter, or do provide a valid Content-Type, as a type of text/plain is applied by default to all requests. Client software should always state ignore_content_type, except when a Content-Type is explicitly defined by the user.* +====================== =================================== +Request Parameter Name Value +====================== =================================== +format Optional conflict response type (can be ``json`` or ``xml``) +ignore_content_type Ignore the supplied Content-Type +====================== =================================== + Refer to ``PUT``/``POST`` for a description of request headers. Metadata is also copied, updated with any values defined. Sharing/publishing options are not copied. ========================== =============================== @@ -912,7 +912,7 @@ X-Object-Version The object's new version Return Code Description ============================== ============================== 201 (Created) The object has been created -409 (Conflict) There are conflicting permissions (a list of conflicting sharing paths will be included in the reply - in simple text format) +409 (Conflict) There are conflicting permissions (a list of conflicting sharing paths will be included in the reply) 413 (Request Entity Too Large) Insufficient quota to complete the request ============================== ============================== @@ -952,6 +952,7 @@ X-Object-Meta-* Optional user defined metadata ====================== ============================================ Request Parameter Name Value ====================== ============================================ +format Optional conflict response type (can be ``json`` or ``xml``) update Do not replace metadata (no value parameter) ====================== ============================================ @@ -989,7 +990,7 @@ Return Code Description ============================== ============================== 202 (Accepted) The request has been accepted (not a data update) 204 (No Content) The request succeeded (data updated) -409 (Conflict) There are conflicting permissions (a list of conflicting sharing paths will be included in the reply - in simple text format) +409 (Conflict) There are conflicting permissions (a list of conflicting sharing paths will be included in the reply) 411 (Length Required) Missing ``Content-Length`` in the request 413 (Request Entity Too Large) Insufficient quota to complete the request 416 (Range Not Satisfiable) The supplied range is invalid diff --git a/pithos/api/functions.py b/pithos/api/functions.py index 0dfb1ce..f7e3adf 100644 --- a/pithos/api/functions.py +++ b/pithos/api/functions.py @@ -46,12 +46,12 @@ from pithos.lib.filter import parse_filters from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound, Conflict, LengthRequired, PreconditionFailed, RequestEntityTooLarge, RangeNotSatisfiable, UnprocessableEntity) -from pithos.api.util import (rename_meta_key, format_header_key, printable_header_dict, get_account_headers, - put_account_headers, get_container_headers, put_container_headers, get_object_headers, put_object_headers, - update_manifest_meta, update_sharing_meta, update_public_meta, validate_modification_preconditions, - validate_matching_preconditions, split_container_object_string, copy_or_move_object, - get_int_parameter, get_content_length, get_content_range, socket_read_iterator, SaveToBackendHandler, - object_data_response, put_object_block, hashmap_md5, api_method, json_encode_decimal) +from pithos.api.util import (json_encode_decimal, rename_meta_key, format_header_key, printable_header_dict, + get_account_headers, put_account_headers, get_container_headers, put_container_headers, get_object_headers, + put_object_headers, update_manifest_meta, update_sharing_meta, update_public_meta, + validate_modification_preconditions, validate_matching_preconditions, split_container_object_string, + copy_or_move_object, get_int_parameter, get_content_length, get_content_range, socket_read_iterator, + SaveToBackendHandler, object_data_response, put_object_block, hashmap_md5, object_conflict_response, api_method) from pithos.backends.base import NotAllowedError, QuotaError @@ -853,13 +853,13 @@ def object_write(request, v_account, v_container, v_object): except NotAllowedError: raise Forbidden('Not allowed') except IndexError, e: - raise Conflict('\n'.join(e.data) + '\n') + raise Conflict(object_conflict_response(request, e.data)) except NameError: raise ItemNotFound('Container does not exist') except ValueError: raise BadRequest('Invalid sharing header') except AttributeError, e: - raise Conflict('\n'.join(e.data) + '\n') + raise Conflict(object_conflict_response(request, e.data)) except QuotaError: raise RequestEntityTooLarge('Quota exceeded') if 'ETag' not in meta: @@ -919,7 +919,7 @@ def object_write_form(request, v_account, v_container, v_object): response['X-Object-Version'] = version_id return response -@api_method('COPY') +@api_method('COPY', format_allowed=True) def object_copy(request, v_account, v_container, v_object): # Normal Response Codes: 201 # Error Response Codes: internalServerError (500), @@ -956,7 +956,7 @@ def object_copy(request, v_account, v_container, v_object): response['X-Object-Version'] = version_id return response -@api_method('MOVE') +@api_method('MOVE', format_allowed=True) def object_move(request, v_account, v_container, v_object): # Normal Response Codes: 201 # Error Response Codes: internalServerError (500), @@ -992,7 +992,7 @@ def object_move(request, v_account, v_container, v_object): response['X-Object-Version'] = version_id return response -@api_method('POST') +@api_method('POST', format_allowed=True) def object_update(request, v_account, v_container, v_object): # Normal Response Codes: 202, 204 # Error Response Codes: internalServerError (500), @@ -1044,7 +1044,7 @@ def object_update(request, v_account, v_container, v_object): except ValueError: raise BadRequest('Invalid sharing header') except AttributeError, e: - raise Conflict('\n'.join(e.data) + '\n') + raise Conflict(object_conflict_response(request, e.data)) if public is not None: try: request.backend.update_object_public(request.user_uniq, v_account, @@ -1190,7 +1190,7 @@ def object_update(request, v_account, v_container, v_object): except ValueError: raise BadRequest('Invalid sharing header') except AttributeError, e: - raise Conflict('\n'.join(e.data) + '\n') + raise Conflict(object_conflict_response(request, e.data)) except QuotaError: raise RequestEntityTooLarge('Quota exceeded') if public is not None: diff --git a/pithos/api/templates/conflicts.xml b/pithos/api/templates/conflicts.xml new file mode 100644 index 0000000..8353cb5 --- /dev/null +++ b/pithos/api/templates/conflicts.xml @@ -0,0 +1,7 @@ + + + + {% for conflict in conflicts %} + {{ conflict }} + {% endfor %} + diff --git a/pithos/api/util.py b/pithos/api/util.py index 4e3d437..5c8f076 100644 --- a/pithos/api/util.py +++ b/pithos/api/util.py @@ -41,6 +41,7 @@ from urllib import quote, unquote from django.conf import settings from django.http import HttpResponse +from django.template.loader import render_to_string from django.utils import simplejson as json from django.utils.http import http_date, parse_etags from django.utils.encoding import smart_unicode, smart_str @@ -322,7 +323,7 @@ def copy_or_move_object(request, src_account, src_container, src_name, dest_acco except ValueError: raise BadRequest('Invalid sharing header') except AttributeError, e: - raise Conflict('\n'.join(e.data) + '\n') + raise Conflict(object_conflict_response(request, e.data)) except QuotaError: raise RequestEntityTooLarge('Quota exceeded') if public is not None: @@ -758,6 +759,14 @@ def hashmap_md5(request, hashmap, size): md5.update(data + ('\x00' * pad)) return md5.hexdigest().lower() +def object_conflict_response(request, l): + if request.serialization == 'text': + return '\n'.join(l) + '\n' + if request.serialization == 'xml': + return render_to_string('conflicts.xml', {'conflicts': l}) + if request.serialization == 'json': + return json.dumps(l) + def get_backend(): backend = connect_backend(db_module=settings.BACKEND_DB_MODULE, db_connection=settings.BACKEND_DB_CONNECTION, diff --git a/pithos/lib/transfer.py b/pithos/lib/transfer.py index 2feecb1..7ad1527 100644 --- a/pithos/lib/transfer.py +++ b/pithos/lib/transfer.py @@ -33,6 +33,7 @@ import os import types +import json from hashmap import HashMap from binascii import hexlify, unhexlify @@ -64,7 +65,7 @@ def upload(client, path, container, prefix, name=None, mimetype=None): return v if type(fault.data) == types.StringType: - missing = fault.data.split('\n') + missing = json.loads(fault.data) elif type(fault.data) == types.ListType: missing = fault.data