Revision a8326bef

b/docs/source/devguide.rst
25 25
=========================  ================================
26 26
Revision                   Description
27 27
=========================  ================================
28
0.5 (July 21, 2011)        Object update from another object's data.
28
0.5 (July 22, 2011)        Object update from another object's data.
29 29
\                          Support object truncate.
30 30
\                          Create object using a standard HTML form.
31 31
\                          Purge container/object history.
32 32
\                          List other accounts that share objects with a user.
33 33
\                          List shared containers/objects.
34 34
\                          Update implementation guidelines.
35
\                          Check preconditions when creating/updating objects.
35 36
0.4 (July 01, 2011)        Object permissions and account groups.
36 37
\                          Control versioning behavior and container quotas with container policy directives.
37 38
\                          Support updating/deleting individual metadata with ``POST``.
......
159 160
HEAD
160 161
""""
161 162

  
163
====================  ===========================
164
Request Header Name   Value
165
====================  ===========================
166
If-Modified-Since     Retrieve if account has changed since provided timestamp
167
If-Unmodified-Since   Retrieve if account has not changed since provided timestamp
168
====================  ===========================
169

  
170
|
171

  
162 172
======================  ===================================
163 173
Request Parameter Name  Value
164 174
======================  ===================================
......
247 257
POST
248 258
""""
249 259

  
250
======================  ============================================
251
Request Parameter Name  Value
252
======================  ============================================
253
update                  Do not replace metadata/groups (no value parameter)
254
======================  ============================================
255

  
256
|
257

  
258 260
====================  ===========================
259 261
Request Header Name   Value
260 262
====================  ===========================
......
262 264
X-Account-Meta-*      Optional user defined metadata
263 265
====================  ===========================
264 266

  
267
|
268

  
269
======================  ============================================
270
Request Parameter Name  Value
271
======================  ============================================
272
update                  Do not replace metadata/groups (no value parameter)
273
======================  ============================================
274

  
265 275
No reply content/headers.
266 276

  
267 277
The operation will overwrite all user defined metadata, except if ``update`` is defined.
......
293 303
HEAD
294 304
""""
295 305

  
306
====================  ===========================
307
Request Header Name   Value
308
====================  ===========================
309
If-Modified-Since     Retrieve if container has changed since provided timestamp
310
If-Unmodified-Since   Retrieve if container has not changed since provided timestamp
311
====================  ===========================
312

  
313
|
314

  
296 315
======================  ===================================
297 316
Request Parameter Name  Value
298 317
======================  ===================================
......
437 456
POST
438 457
""""
439 458

  
440
======================  ============================================
441
Request Parameter Name  Value
442
======================  ============================================
443
update                  Do not replace metadata/policy (no value parameter)
444
======================  ============================================
445

  
446
|
447

  
448 459
====================  ================================
449 460
Request Header Name   Value
450 461
====================  ================================
......
452 463
X-Container-Meta-*    Optional user defined metadata
453 464
====================  ================================
454 465

  
466
|
467

  
468
======================  ============================================
469
Request Parameter Name  Value
470
======================  ============================================
471
update                  Do not replace metadata/policy (no value parameter)
472
======================  ============================================
473

  
455 474
No reply content/headers.
456 475

  
457 476
The operation will overwrite all user defined metadata, except if ``update`` is defined.
......
506 525
HEAD
507 526
""""
508 527

  
528
====================  ================================
529
Request Header Name   Value
530
====================  ================================
531
If-Match              Retrieve if ETags match
532
If-None-Match         Retrieve if ETags don't match
533
If-Modified-Since     Retrieve if object has changed since provided timestamp
534
If-Unmodified-Since   Retrieve if object has not changed since provided timestamp
535
====================  ================================
536

  
537
|
538

  
509 539
======================  ===================================
510 540
Request Parameter Name  Value
511 541
======================  ===================================
......
645 675
====================  ================================
646 676
Request Header Name   Value
647 677
====================  ================================
678
If-Match              Put if ETags match with current object
679
If-None-Match         Put if ETags don't match with current object
648 680
ETag                  The MD5 hash of the object (optional to check written data)
649 681
Content-Length        The size of the data written
650 682
Content-Type          The MIME content type of the object
......
712 744
====================  ================================
713 745
Request Header Name   Value
714 746
====================  ================================
747
If-Match              Proceed if ETags match with object
748
If-None-Match         Proceed if ETags don't match with object
715 749
Destination           The destination path in the form ``/<container>/<object>``
716 750
Content-Type          The MIME content type of the object (optional)
717 751
Content-Encoding      The encoding of the object (optional)
......
744 778
POST
745 779
""""
746 780

  
747
======================  ============================================
748
Request Parameter Name  Value
749
======================  ============================================
750
update                  Do not replace metadata (no value parameter)
751
======================  ============================================
752

  
753
|
754

  
755 781
====================  ================================
756 782
Request Header Name   Value
757 783
====================  ================================
784
If-Match              Proceed if ETags match with object
785
If-None-Match         Proceed if ETags don't match with object
758 786
Content-Length        The size of the data written (optional, to update)
759 787
Content-Type          The MIME content type of the object (optional, to update)
760 788
Content-Range         The range of data supplied (optional, to update)
......
770 798
X-Object-Meta-*       Optional user defined metadata
771 799
====================  ================================
772 800

  
801
|
802

  
803
======================  ============================================
804
Request Parameter Name  Value
805
======================  ============================================
806
update                  Do not replace metadata (no value parameter)
807
======================  ============================================
808

  
773 809
The ``Content-Encoding``, ``Content-Disposition``, ``X-Object-Manifest`` and ``X-Object-Meta-*`` headers are considered to be user defined metadata. An operation without the ``update`` parameter will overwrite all previous values and remove any keys not supplied. When using ``update`` any metadata with an empty value will be deleted.
774 810

  
775 811
To change permissions, include an ``X-Object-Sharing`` header (as defined in ``PUT``). To publish, include an ``X-Object-Public`` header, with a value of ``true``. If no such headers are defined, no changes will be applied to sharing/public. Use empty values to remove permissions/unpublish (unpublishing also works with ``false`` as a header value). Sharing options are applied to the object - not its versions.
......
886 922
* Container policies to manage behavior and limits.
887 923
* Headers ``X-Container-Block-*`` at the container level, exposing the underlying storage characteristics.
888 924
* All metadata replies, at all levels, include latest modification information.
889
* At all levels, a ``GET`` request may use ``If-Modified-Since`` and ``If-Unmodified-Since`` headers.
925
* At all levels, a ``HEAD`` or ``GET`` request may use ``If-Modified-Since`` and ``If-Unmodified-Since`` headers.
890 926
* Container/object lists include all associated metadata if the reply is of type json/xml. Some names are kept to their OOS API equivalents for compatibility.
891 927
* Option to include only shared containers/objects in listings.
892 928
* Object metadata allowed, in addition to ``X-Object-Meta-*``: ``Content-Encoding``, ``Content-Disposition``, ``X-Object-Manifest``. These are all replaced with every update operation, except if using the ``update`` parameter (in which case individual keys can also be deleted). Deleting meta by providing empty values also works when copying/moving an object.
......
896 932
* Object create using ``POST`` to support standard HTML forms.
897 933
* 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``.
898 934
* Object ``MOVE`` support.
935
* Conditional object create/update operations, using ``If-Match`` and ``If-None-Match`` headers.
899 936
* Time-variant account/container listings via the ``until`` parameter.
900 937
* Object versions - parameter ``version`` in ``HEAD``/``GET`` (list versions with ``GET``), ``X-Object-Version-*`` meta in replies, ``X-Source-Version`` in ``PUT``/``COPY``.
901 938
* Sharing/publishing with ``X-Object-Sharing``, ``X-Object-Public`` at the object level. Cross-user operations are allowed - controlled by sharing directives. Permissions may include groups defined with ``X-Account-Group-*`` at the account level. These apply to the object - not its versions.
......
948 985
Implementation Guidelines
949 986
^^^^^^^^^^^^^^^^^^^^^^^^^
950 987

  
951
Pithos clients should use the ``pithos`` and ``trash`` containers for active and inactive objects respectively. If any of these containers is not found, the client software should create it, without interrupting the user's workflow. The ``home`` element corresponds to ``pithos`` and the ``trash`` element to ``trash``. Use ``PUT`` with the ``X-Move-From`` header, or ``MOVE`` to transfer objects from one container to the other. Use ``DELETE`` to remove from ``pithos`` without trashing, or to remove from ``trash``. When moving objects, detect naming conflicts with the ``If-Match`` or ``If-None-Match`` headers (**TBD**). Such conflicts should be resolved by the user.
988
Pithos clients should use the ``pithos`` and ``trash`` containers for active and inactive objects respectively. If any of these containers is not found, the client software should create it, without interrupting the user's workflow. The ``home`` element corresponds to ``pithos`` and the ``trash`` element to ``trash``. Use ``PUT`` with the ``X-Move-From`` header, or ``MOVE`` to transfer objects from one container to the other. Use ``DELETE`` to remove from ``pithos`` without trashing, or to remove from ``trash``. When moving objects, detect naming conflicts with the ``If-Match`` or ``If-None-Match`` headers. Such conflicts should be resolved by the user.
952 989

  
953 990
Object names should use the ``/`` delimiter to impose a hierarchy of folders and files.
954 991

  
b/pithos/api/functions.py
189 189
    except NotAllowedError:
190 190
        raise Unauthorized('Access denied')
191 191
    
192
    validate_modification_preconditions(request, meta)
193
    
192 194
    response = HttpResponse(status=204)
193 195
    put_account_headers(response, meta, groups)
194 196
    return response
......
303 305
    except NameError:
304 306
        raise ItemNotFound('Container does not exist')
305 307
    
308
    validate_modification_preconditions(request, meta)
309
    
306 310
    response = HttpResponse(status=204)
307 311
    put_container_headers(response, meta, policy)
308 312
    return response
......
322 326
        ret = 201
323 327
    except NotAllowedError:
324 328
        raise Unauthorized('Access denied')
329
    except ValueError:
330
        raise BadRequest('Invalid policy header')
325 331
    except NameError:
326 332
        ret = 202
327 333
    
......
519 525
    update_sharing_meta(permissions, v_account, v_container, v_object, meta)
520 526
    update_public_meta(public, meta)
521 527
    
528
    # Evaluate conditions.
529
    validate_modification_preconditions(request, meta)
530
    try:
531
        validate_matching_preconditions(request, meta)
532
    except NotModified:
533
        response = HttpResponse(status=304)
534
        response['ETag'] = meta['hash']
535
        return response
536
    
522 537
    response = HttpResponse(status=200)
523 538
    put_object_headers(response, meta)
524 539
    return response
......
652 667
    if not request.GET.get('format'):
653 668
        request.serialization = 'text'
654 669
    
670
    # Evaluate conditions.
671
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
672
        try:
673
            meta = backend.get_object_meta(request.user, v_account, v_container, v_object)
674
        except NotAllowedError:
675
            raise Unauthorized('Access denied')
676
        except NameError:
677
            meta = {}
678
        validate_matching_preconditions(request, meta)
679
    
655 680
    copy_from = request.META.get('HTTP_X_COPY_FROM')
656 681
    move_from = request.META.get('HTTP_X_MOVE_FROM')
657 682
    if copy_from or move_from:
......
800 825
        dest_container, dest_name = split_container_object_string(dest_path)
801 826
    except ValueError:
802 827
        raise BadRequest('Invalid Destination header')
828
    
829
    # Evaluate conditions.
830
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
831
        src_version = request.META.get('HTTP_X_SOURCE_VERSION')
832
        try:
833
            meta = backend.get_object_meta(request.user, v_account, v_container, v_object, src_version)
834
        except NotAllowedError:
835
            raise Unauthorized('Access denied')
836
        except (NameError, IndexError):
837
            raise ItemNotFound('Container or object does not exist')
838
        validate_matching_preconditions(request, meta)
839
    
803 840
    copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=False)
804 841
    return HttpResponse(status=201)
805 842

  
......
818 855
        dest_container, dest_name = split_container_object_string(dest_path)
819 856
    except ValueError:
820 857
        raise BadRequest('Invalid Destination header')
858
    
859
    # Evaluate conditions.
860
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
861
        try:
862
            meta = backend.get_object_meta(request.user, v_account, v_container, v_object)
863
        except NotAllowedError:
864
            raise Unauthorized('Access denied')
865
        except NameError:
866
            raise ItemNotFound('Container or object does not exist')
867
        validate_matching_preconditions(request, meta)
868
    
821 869
    copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=True)
822 870
    return HttpResponse(status=201)
823 871

  
......
840 888
        raise Unauthorized('Access denied')
841 889
    except NameError:
842 890
        raise ItemNotFound('Object does not exist')
891
    
892
    # Evaluate conditions.
893
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
894
        validate_matching_preconditions(request, prev_meta)
895
    
843 896
    # If replacing, keep previous values of 'Content-Type' and 'hash'.
844 897
    replace = True
845 898
    if 'update' in request.GET:
b/pithos/api/util.py
228 228
def validate_matching_preconditions(request, meta):
229 229
    """Check that the ETag conforms with the preconditions set."""
230 230
    
231
    if 'hash' not in meta:
232
        return # TODO: Always return?
231
    hash = meta.get('hash', None)
233 232
    
234 233
    if_match = request.META.get('HTTP_IF_MATCH')
235
    if if_match is not None and if_match != '*':
236
        if meta['hash'] not in [x.lower() for x in parse_etags(if_match)]:
237
            raise PreconditionFailed('Resource Etag does not match')
234
    if if_match is not None:
235
        if hash is None:
236
            raise PreconditionFailed('Resource does not exist')
237
        if if_match != '*' and hash not in [x.lower() for x in parse_etags(if_match)]:
238
            raise PreconditionFailed('Resource ETag does not match')
238 239
    
239 240
    if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
240 241
    if if_none_match is not None:
241
        if if_none_match == '*' or meta['hash'] in [x.lower() for x in parse_etags(if_none_match)]:
242
            raise NotModified('Resource Etag matches')
242
        # TODO: If this passes, must ignore If-Modified-Since header.
243
        if hash is not None:
244
            if if_none_match == '*' or hash in [x.lower() for x in parse_etags(if_none_match)]:
245
                # TODO: Continue if an If-Modified-Since header is present.
246
                if request.method in ('HEAD', 'GET'):
247
                    raise NotModified('Resource ETag matches')
248
                raise PreconditionFailed('Resource exists or ETag matches')
243 249

  
244 250
def split_container_object_string(s):
245 251
    if not len(s) > 0 or s[0] != '/':
......
262 268
            backend.copy_object(request.user, v_account, src_container, src_name, dest_container, dest_name, meta, False, permissions, src_version)
263 269
    except NotAllowedError:
264 270
        raise Unauthorized('Access denied')
265
    except NameError, IndexError:
271
    except (NameError, IndexError):
266 272
        raise ItemNotFound('Container or object does not exist')
267 273
    except ValueError:
268 274
        raise BadRequest('Invalid sharing header')

Also available in: Unified diff