Merge "Use separate headers for versioned_writes' stack and history modes"
This commit is contained in:
commit
f1c3ceb48d
@ -666,6 +666,27 @@ X-Fresh-Metadata:
|
|||||||
in: header
|
in: header
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
|
X-History-Location:
|
||||||
|
description: |
|
||||||
|
The URL-encoded UTF-8 representation of the container that stores
|
||||||
|
previous versions of objects. If neither this nor ``X-Versions-Location``
|
||||||
|
is set, versioning is disabled for this container. ``X-History-Location``
|
||||||
|
and ``X-Versions-Location`` cannot both be set at the same time. For more
|
||||||
|
information about object versioning, see `Object versioning
|
||||||
|
<http://docs.openstack.org/ developer/swift/api/object_versioning.html>`_.
|
||||||
|
in: header
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
X-History-Location_resp:
|
||||||
|
description: |
|
||||||
|
If present, this container has versioning enabled and the value
|
||||||
|
is the UTF-8 encoded name of another container.
|
||||||
|
For more information about object versioning,
|
||||||
|
see `Object versioning <http://docs.openstack.org/developer/
|
||||||
|
swift/api/object_versioning.html>`_.
|
||||||
|
in: header
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
X-Newest:
|
X-Newest:
|
||||||
description: |
|
description: |
|
||||||
If set to true , Object Storage queries all
|
If set to true , Object Storage queries all
|
||||||
@ -728,9 +749,17 @@ X-Remove-Container-name:
|
|||||||
in: header
|
in: header
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
X-Remove-History-Location:
|
||||||
|
description: |
|
||||||
|
Set to any value to disable versioning. Note that this disables version
|
||||||
|
that was set via ``X-Versions-Location`` as well.
|
||||||
|
in: header
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
X-Remove-Versions-Location:
|
X-Remove-Versions-Location:
|
||||||
description: |
|
description: |
|
||||||
Set to any value to disable versioning.
|
Set to any value to disable versioning. Note that this disables version
|
||||||
|
that was set via ``X-History-Location`` as well.
|
||||||
in: header
|
in: header
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
@ -801,10 +830,11 @@ X-Trans-Id-Extra:
|
|||||||
X-Versions-Location:
|
X-Versions-Location:
|
||||||
description: |
|
description: |
|
||||||
The URL-encoded UTF-8 representation of the container that stores
|
The URL-encoded UTF-8 representation of the container that stores
|
||||||
previous versions of objects. If not set, versioning is disabled
|
previous versions of objects. If neither this nor ``X-History-Location``
|
||||||
for this container. For more information about object versioning,
|
is set, versioning is disabled for this container. ``X-Versions-Location``
|
||||||
see `Object versioning <http://docs.openstack.org/developer/
|
and ``X-History-Location`` cannot both be set at the same time. For more
|
||||||
swift/api/object_versioning.html>`_.
|
information about object versioning, see `Object versioning
|
||||||
|
<http://docs.openstack.org/ developer/swift/api/object_versioning.html>`_.
|
||||||
in: header
|
in: header
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
@ -818,26 +848,6 @@ X-Versions-Location_resp:
|
|||||||
in: header
|
in: header
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
X-Versions-Mode:
|
|
||||||
description: |
|
|
||||||
The versioning mode for this container. The value must be either
|
|
||||||
``stack`` or ``history``. If not set, ``stack`` mode will be used.
|
|
||||||
This setting has no impact unless ``X-Versions-Location`` is set
|
|
||||||
for the container. For more information about object versioning,
|
|
||||||
see `Object versioning <http://docs.openstack.org/developer/
|
|
||||||
swift/api/object_versioning.html>`_.
|
|
||||||
in: header
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
X-Versions-Mode_resp:
|
|
||||||
description: |
|
|
||||||
If set, this is the versioning mode for this container. The value is either
|
|
||||||
``stack`` or ``history``. For more information about object versioning,
|
|
||||||
see `Object versioning <http://docs.openstack.org/developer/
|
|
||||||
swift/api/object_versioning.html>`_.
|
|
||||||
in: header
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
|
|
||||||
# variables in path
|
# variables in path
|
||||||
account:
|
account:
|
||||||
|
@ -85,7 +85,7 @@ Response Parameters
|
|||||||
- X-Container-Sync-Key: X-Container-Sync-Key_resp
|
- X-Container-Sync-Key: X-Container-Sync-Key_resp
|
||||||
- X-Container-Sync-To: X-Container-Sync-To_resp
|
- X-Container-Sync-To: X-Container-Sync-To_resp
|
||||||
- X-Versions-Location: X-Versions-Location_resp
|
- X-Versions-Location: X-Versions-Location_resp
|
||||||
- X-Versions-Mode: X-Versions-Mode_resp
|
- X-History-Location: X-History-Location_resp
|
||||||
- X-Timestamp: X-Timestamp
|
- X-Timestamp: X-Timestamp
|
||||||
- X-Trans-Id: X-Trans-Id
|
- X-Trans-Id: X-Trans-Id
|
||||||
- Content_Type: Content-Type_listing_resp
|
- Content_Type: Content-Type_listing_resp
|
||||||
@ -199,7 +199,7 @@ Request
|
|||||||
- X-Container-Sync-To: X-Container-Sync-To
|
- X-Container-Sync-To: X-Container-Sync-To
|
||||||
- X-Container-Sync-Key: X-Container-Sync-Key
|
- X-Container-Sync-Key: X-Container-Sync-Key
|
||||||
- X-Versions-Location: X-Versions-Location
|
- X-Versions-Location: X-Versions-Location
|
||||||
- X-Versions-Mode: X-Versions-Mode
|
- X-History-Location: X-History-Location
|
||||||
- X-Container-Meta-name: X-Container-Meta-name_req
|
- X-Container-Meta-name: X-Container-Meta-name_req
|
||||||
- X-Container-Meta-Access-Control-Allow-Origin: X-Container-Meta-Access-Control-Allow-Origin
|
- X-Container-Meta-Access-Control-Allow-Origin: X-Container-Meta-Access-Control-Allow-Origin
|
||||||
- X-Container-Meta-Access-Control-Max-Age: X-Container-Meta-Access-Control-Max-Age
|
- X-Container-Meta-Access-Control-Max-Age: X-Container-Meta-Access-Control-Max-Age
|
||||||
@ -335,8 +335,9 @@ Request
|
|||||||
- X-Container-Sync-To: X-Container-Sync-To
|
- X-Container-Sync-To: X-Container-Sync-To
|
||||||
- X-Container-Sync-Key: X-Container-Sync-Key
|
- X-Container-Sync-Key: X-Container-Sync-Key
|
||||||
- X-Versions-Location: X-Versions-Location
|
- X-Versions-Location: X-Versions-Location
|
||||||
- X-Versions-Mode: X-Versions-Mode
|
- X-History-Location: X-History-Location
|
||||||
- X-Remove-Versions-Location: X-Remove-Versions-Location
|
- X-Remove-Versions-Location: X-Remove-Versions-Location
|
||||||
|
- X-Remove-History-Location: X-Remove-History-Location
|
||||||
- X-Container-Meta-name: X-Container-Meta-name_req
|
- X-Container-Meta-name: X-Container-Meta-name_req
|
||||||
- X-Container-Meta-Access-Control-Allow-Origin: X-Container-Meta-Access-Control-Allow-Origin
|
- X-Container-Meta-Access-Control-Allow-Origin: X-Container-Meta-Access-Control-Allow-Origin
|
||||||
- X-Container-Meta-Access-Control-Max-Age: X-Container-Meta-Access-Control-Max-Age
|
- X-Container-Meta-Access-Control-Max-Age: X-Container-Meta-Access-Control-Max-Age
|
||||||
@ -440,7 +441,7 @@ Response Parameters
|
|||||||
- X-Trans-Id: X-Trans-Id
|
- X-Trans-Id: X-Trans-Id
|
||||||
- Content-Type: Content-Type_cud_resp
|
- Content-Type: Content-Type_cud_resp
|
||||||
- X-Versions-Location: X-Versions-Location_resp
|
- X-Versions-Location: X-Versions-Location_resp
|
||||||
- X-Versions-Mode: X-Versions-Mode_resp
|
- X-History-Location: X-History-Location_resp
|
||||||
- X-Storage-Policy: X-Storage-Policy
|
- X-Storage-Policy: X-Storage-Policy
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,30 +20,37 @@ To allow object versioning within a cluster, the cloud provider should add the
|
|||||||
``allow_versioned_writes`` option to ``true`` in the
|
``allow_versioned_writes`` option to ``true`` in the
|
||||||
``[filter:versioned_writes]`` section of the proxy-server configuration file.
|
``[filter:versioned_writes]`` section of the proxy-server configuration file.
|
||||||
|
|
||||||
The ``X-Versions-Location`` header defines the
|
To enable object versioning for a container, you must specify an "archive
|
||||||
container that holds the non-current versions of your objects. You
|
container" that will retain non-current versions via either the
|
||||||
must UTF-8-encode and then URL-encode the container name before you
|
``X-Versions-Location`` or ``X-History-Location`` header. These two headers
|
||||||
include it in the ``X-Versions-Location`` header. This header enables
|
enable two distinct modes of operation. Either mode may be used within a
|
||||||
object versioning for all objects in the container. With a comparable
|
cluster, but only one mode may be active for any given container. You must
|
||||||
``archive`` container in place, changes to objects in the ``current``
|
UTF-8-encode and then URL-encode the container name before you include it in
|
||||||
container automatically create non-current versions in the ``archive``
|
the header.
|
||||||
container.
|
|
||||||
|
|
||||||
The ``X-Versions-Mode`` header defines the behavior of ``DELETE`` requests to
|
For both modes, **PUT** requests will archive any pre-existing objects before
|
||||||
objects in the versioned container. In the default ``stack`` mode, deleting an
|
writing new data, and **GET** requests will serve the current version. **COPY**
|
||||||
object will restore the most-recent version from the ``archive`` container,
|
requests behave like a **GET** followed by a **PUT**; that is, if the copy
|
||||||
overwriting the curent version. Alternatively you may specify ``history``
|
*source* is in a versioned container then the current version will be copied,
|
||||||
mode, where deleting an object will copy the current version to the
|
and if the copy *destination* is in a versioned container then any pre-existing
|
||||||
``archive`` then remove it from the ``current`` container.
|
object will be archived before writing new data.
|
||||||
|
|
||||||
Example Using ``stack`` Mode
|
If object versioning was enabled using ``X-History-Location``, then object
|
||||||
----------------------------
|
**DELETE** requests will copy the current version to the archive container then
|
||||||
|
remove it from the versioned container.
|
||||||
|
|
||||||
|
If object versioning was enabled using ``X-Versions-Location``, then object
|
||||||
|
**DELETE** requests will restore the most-recent version from the archive
|
||||||
|
container, overwriting the curent version.
|
||||||
|
|
||||||
|
Example Using ``X-Versions-Location``
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
#. Create the ``current`` container:
|
#. Create the ``current`` container:
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
# curl -i $publicURL/current -X PUT -H "Content-Length: 0" -H "X-Auth-Token: $token" -H "X-Versions-Location: archive" -H "X-Versions-Mode: stack"
|
# curl -i $publicURL/current -X PUT -H "Content-Length: 0" -H "X-Auth-Token: $token" -H "X-Versions-Location: archive"
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
@ -169,14 +176,14 @@ Example Using ``stack`` Mode
|
|||||||
on it. If want to completely remove an object and you have five
|
on it. If want to completely remove an object and you have five
|
||||||
versions of it, you must **DELETE** it five times.
|
versions of it, you must **DELETE** it five times.
|
||||||
|
|
||||||
Example Using ``history`` Mode
|
Example Using ``X-History-Location``
|
||||||
------------------------------
|
------------------------------------
|
||||||
|
|
||||||
#. Create the ``current`` container:
|
#. Create the ``current`` container:
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
# curl -i $publicURL/current -X PUT -H "Content-Length: 0" -H "X-Auth-Token: $token" -H "X-Versions-Location: archive" -H "X-Versions-Mode: history"
|
# curl -i $publicURL/current -X PUT -H "Content-Length: 0" -H "X-Auth-Token: $token" -H "X-History-Location: archive"
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
@ -266,7 +273,7 @@ Example Using ``history`` Mode
|
|||||||
#. Issue a **DELETE** request to a versioned object to copy the
|
#. Issue a **DELETE** request to a versioned object to copy the
|
||||||
current version of the object to the archive container then delete it from
|
current version of the object to the archive container then delete it from
|
||||||
the current container. Subsequent **GET** requests to the object in the
|
the current container. Subsequent **GET** requests to the object in the
|
||||||
current container will return 404 Not Found.
|
current container will return ``404 Not Found``.
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
|
@ -15,21 +15,48 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Object versioning in swift is implemented by setting a flag on the container
|
Object versioning in swift is implemented by setting a flag on the container
|
||||||
to tell swift to version all objects in the container. The flag is the
|
to tell swift to version all objects in the container. The value of the flag is
|
||||||
``X-Versions-Location`` header on the container, and its value is the
|
the container where the versions are stored (commonly referred to as the
|
||||||
container where the versions are stored.
|
"archive container"). The flag itself is one of two headers, which determines
|
||||||
|
how object ``DELETE`` requests are handled:
|
||||||
|
|
||||||
|
* ``X-History-Location``
|
||||||
|
|
||||||
|
On ``DELETE``, copy the current version of the object to the archive
|
||||||
|
container, write a zero-byte "delete marker" object that notes when the
|
||||||
|
delete took place, and delete the object from the versioned container. The
|
||||||
|
object will no longer appear in container listings for the versioned
|
||||||
|
container and future requests there will return ``404 Not Found``. However,
|
||||||
|
the content will still be recoverable from the archive container.
|
||||||
|
|
||||||
|
* ``X-Versions-Location``
|
||||||
|
|
||||||
|
On ``DELETE``, only remove the current version of the object. If any
|
||||||
|
previous versions exist in the archive container, the most recent one is
|
||||||
|
copied over the current version, and the copy in the archive container is
|
||||||
|
deleted. As a result, if you have 5 total versions of the object, you must
|
||||||
|
delete the object 5 times for that object name to start responding with
|
||||||
|
``404 Not Found``.
|
||||||
|
|
||||||
|
Either header may be used for the various containers within an account, but
|
||||||
|
only one may be set for any given container. Attempting to set both
|
||||||
|
simulataneously will result in a ``400 Bad Request`` response.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
It is recommended to use a different ``X-Versions-Location`` container for
|
It is recommended to use a different archive container for
|
||||||
each container that is being versioned.
|
each container that is being versioned.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Enabling versioning on an archive container is not recommended.
|
||||||
|
|
||||||
When data is ``PUT`` into a versioned container (a container with the
|
When data is ``PUT`` into a versioned container (a container with the
|
||||||
versioning flag turned on), the existing data in the file is redirected to a
|
versioning flag turned on), the existing data in the file is redirected to a
|
||||||
new object and the data in the ``PUT`` request is saved as the data for the
|
new object in the archive container and the data in the ``PUT`` request is
|
||||||
versioned object. The new object name (for the previous version) is
|
saved as the data for the versioned object. The new object name (for the
|
||||||
``<archive_container>/<length><object_name>/<timestamp>``, where ``length``
|
previous version) is ``<archive_container>/<length><object_name>/<timestamp>``,
|
||||||
is the 3-character zero-padded hexadecimal length of the ``<object_name>`` and
|
where ``length`` is the 3-character zero-padded hexadecimal length of the
|
||||||
``<timestamp>`` is the timestamp of when the previous version was created.
|
``<object_name>`` and ``<timestamp>`` is the timestamp of when the previous
|
||||||
|
version was created.
|
||||||
|
|
||||||
A ``GET`` to a versioned object will return the current version of the object
|
A ``GET`` to a versioned object will return the current version of the object
|
||||||
without having to do any request redirects or metadata lookups.
|
without having to do any request redirects or metadata lookups.
|
||||||
@ -39,38 +66,15 @@ but will not create a new version of the object. In other words, new versions
|
|||||||
are only created when the content of the object changes.
|
are only created when the content of the object changes.
|
||||||
|
|
||||||
A ``DELETE`` to a versioned object will be handled in one of two ways,
|
A ``DELETE`` to a versioned object will be handled in one of two ways,
|
||||||
depending on the value of a ``X-Versions-Mode`` header set on the container.
|
as described above.
|
||||||
The available modes are:
|
|
||||||
|
|
||||||
* ``stack``
|
|
||||||
|
|
||||||
Only remove the current version of the object. If any previous versions
|
|
||||||
exist in the archive container, the most recent one is copied over the
|
|
||||||
current version, and the copy in the archive container is deleted. As a
|
|
||||||
result, if you have 5 total versions of the object, you must delete the
|
|
||||||
object 5 times to completely remove the object. This is the default
|
|
||||||
behavior if ``X-Versions-Mode`` has not been set for the container.
|
|
||||||
|
|
||||||
* ``history``
|
|
||||||
|
|
||||||
Copy the current version of the object to the archive container, write
|
|
||||||
a zero-byte "delete marker" object that notes when the delete took place,
|
|
||||||
and delete the object from the versioned container. The object will no
|
|
||||||
longer appear in container listings for the versioned container and future
|
|
||||||
requests there will return 404 Not Found. However, the content will still
|
|
||||||
be recoverable from the archive container.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
While it is possible to switch between 'stack' and 'history' mode on a
|
|
||||||
container, it is not recommended.
|
|
||||||
|
|
||||||
To restore a previous version of an object, find the desired version in the
|
To restore a previous version of an object, find the desired version in the
|
||||||
archive container then issue a ``COPY`` with a ``Destination`` header
|
archive container then issue a ``COPY`` with a ``Destination`` header
|
||||||
indicating the original location. This will retain a copy of the current
|
indicating the original location. This will archive the current version similar
|
||||||
version similar to a ``PUT`` over the versioned object. Additionally, if the
|
to a ``PUT`` over the versioned object. If the the client additionally wishes
|
||||||
container is in ``stack`` mode and the client wishes to permanently delete the
|
to permanently delete what was the current version, it must find the
|
||||||
current version, it may issue a ``DELETE`` to the versioned object as
|
newly-created archive in the archive container and issue a separate ``DELETE``
|
||||||
described above.
|
to it.
|
||||||
|
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
How to Enable Object Versioning in a Swift Cluster
|
How to Enable Object Versioning in a Swift Cluster
|
||||||
@ -81,9 +85,10 @@ so this functionality was already available in previous releases and every
|
|||||||
attempt was made to maintain backwards compatibility. To allow operators to
|
attempt was made to maintain backwards compatibility. To allow operators to
|
||||||
perform a seamless upgrade, it is not required to add the middleware to the
|
perform a seamless upgrade, it is not required to add the middleware to the
|
||||||
proxy pipeline and the flag ``allow_versions`` in the container server
|
proxy pipeline and the flag ``allow_versions`` in the container server
|
||||||
configuration files are still valid, but only for ``stack`` mode. In future
|
configuration files are still valid, but only when using
|
||||||
releases, ``allow_versions`` will be deprecated in favor of adding this
|
``X-Versions-Location``. In future releases, ``allow_versions`` will be
|
||||||
middleware to the pipeline to enable or disable the feature.
|
deprecated in favor of adding this middleware to the pipeline to enable or
|
||||||
|
disable the feature.
|
||||||
|
|
||||||
In case the middleware is added to the proxy pipeline, you must also
|
In case the middleware is added to the proxy pipeline, you must also
|
||||||
set ``allow_versioned_writes`` to ``True`` in the middleware options
|
set ``allow_versioned_writes`` to ``True`` in the middleware options
|
||||||
@ -92,9 +97,9 @@ request.
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
You need to add the middleware to the proxy pipeline and set
|
You need to add the middleware to the proxy pipeline and set
|
||||||
``allow_versioned_writes = True`` to use the ``history`` mode. Setting
|
``allow_versioned_writes = True`` to use ``X-History-Location``. Setting
|
||||||
``allow_versions = True`` in the container server is not sufficient to
|
``allow_versions = True`` in the container server is not sufficient to
|
||||||
enable ``history`` mode.
|
enable the use of ``X-History-Location``.
|
||||||
|
|
||||||
|
|
||||||
Upgrade considerations:
|
Upgrade considerations:
|
||||||
@ -103,25 +108,25 @@ Upgrade considerations:
|
|||||||
If ``allow_versioned_writes`` is set in the filter configuration, you can leave
|
If ``allow_versioned_writes`` is set in the filter configuration, you can leave
|
||||||
the ``allow_versions`` flag in the container server configuration files
|
the ``allow_versions`` flag in the container server configuration files
|
||||||
untouched. If you decide to disable or remove the ``allow_versions`` flag, you
|
untouched. If you decide to disable or remove the ``allow_versions`` flag, you
|
||||||
must re-set any existing containers that had the 'X-Versions-Location' flag
|
must re-set any existing containers that had the ``X-Versions-Location`` flag
|
||||||
configured so that it can now be tracked by the versioned_writes middleware.
|
configured so that it can now be tracked by the versioned_writes middleware.
|
||||||
|
|
||||||
Clients should not use the 'history' mode until all proxies in the cluster
|
Clients should not use the ``X-History-Location`` header until all proxies in
|
||||||
have been upgraded to a version of Swift that supports it. Attempting to use
|
the cluster have been upgraded to a version of Swift that supports it.
|
||||||
the 'history' mode during a rolling upgrade may result in some requests being
|
Attempting to use ``X-History-Location`` during a rolling upgrade may result
|
||||||
served by proxies running old code (which necessarily uses the 'stack' mode),
|
in some requests being served by proxies running old code, leading to data
|
||||||
leading to data loss.
|
loss.
|
||||||
|
|
||||||
--------------------------------------------
|
----------------------------------------------------
|
||||||
Examples Using ``curl`` with ``stack`` Mode
|
Examples Using ``curl`` with ``X-Versions-Location``
|
||||||
--------------------------------------------
|
----------------------------------------------------
|
||||||
|
|
||||||
First, create a container with the ``X-Versions-Location`` header or add the
|
First, create a container with the ``X-Versions-Location`` header or add the
|
||||||
header to an existing container. Also make sure the container referenced by
|
header to an existing container. Also make sure the container referenced by
|
||||||
the ``X-Versions-Location`` exists. In this example, the name of that
|
the ``X-Versions-Location`` exists. In this example, the name of that
|
||||||
container is "versions"::
|
container is "versions"::
|
||||||
|
|
||||||
curl -i -XPUT -H "X-Auth-Token: <token>" -H "X-Versions-Mode: stack" \
|
curl -i -XPUT -H "X-Auth-Token: <token>" \
|
||||||
-H "X-Versions-Location: versions" http://<storage_url>/container
|
-H "X-Versions-Location: versions" http://<storage_url>/container
|
||||||
curl -i -XPUT -H "X-Auth-Token: <token>" http://<storage_url>/versions
|
curl -i -XPUT -H "X-Auth-Token: <token>" http://<storage_url>/versions
|
||||||
|
|
||||||
@ -150,16 +155,16 @@ http://<storage_url>/versions?prefix=008myobject/
|
|||||||
curl -i -XGET -H "X-Auth-Token: <token>" \
|
curl -i -XGET -H "X-Auth-Token: <token>" \
|
||||||
http://<storage_url>/container/myobject
|
http://<storage_url>/container/myobject
|
||||||
|
|
||||||
----------------------------------------------
|
---------------------------------------------------
|
||||||
Examples Using ``curl`` with ``history`` Mode
|
Examples Using ``curl`` with ``X-History-Location``
|
||||||
----------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
As above, create a container with the ``X-Versions-Location`` header and ensure
|
As above, create a container with the ``X-History-Location`` header and ensure
|
||||||
that the container referenced by the ``X-Versions-Location`` exists. In this
|
that the container referenced by the ``X-History-Location`` exists. In this
|
||||||
example, the name of that container is "versions"::
|
example, the name of that container is "versions"::
|
||||||
|
|
||||||
curl -i -XPUT -H "X-Auth-Token: <token>" -H "X-Versions-Mode: history" \
|
curl -i -XPUT -H "X-Auth-Token: <token>" \
|
||||||
-H "X-Versions-Location: versions" http://<storage_url>/container
|
-H "X-History-Location: versions" http://<storage_url>/container
|
||||||
curl -i -XPUT -H "X-Auth-Token: <token>" http://<storage_url>/versions
|
curl -i -XPUT -H "X-Auth-Token: <token>" http://<storage_url>/versions
|
||||||
|
|
||||||
Create an object (the first version)::
|
Create an object (the first version)::
|
||||||
@ -238,12 +243,11 @@ from swift.common.exceptions import (
|
|||||||
ListingIterNotFound, ListingIterError)
|
ListingIterNotFound, ListingIterError)
|
||||||
|
|
||||||
|
|
||||||
VERSIONING_MODES = ('stack', 'history')
|
|
||||||
DELETE_MARKER_CONTENT_TYPE = 'application/x-deleted;swift_versions_deleted=1'
|
DELETE_MARKER_CONTENT_TYPE = 'application/x-deleted;swift_versions_deleted=1'
|
||||||
VERSIONS_LOC_CLIENT = 'x-versions-location'
|
CLIENT_VERSIONS_LOC = 'x-versions-location'
|
||||||
VERSIONS_LOC_SYSMETA = get_sys_meta_prefix('container') + 'versions-location'
|
CLIENT_HISTORY_LOC = 'x-history-location'
|
||||||
VERSIONS_MODE_CLIENT = 'x-versions-mode'
|
SYSMETA_VERSIONS_LOC = get_sys_meta_prefix('container') + 'versions-location'
|
||||||
VERSIONS_MODE_SYSMETA = get_sys_meta_prefix('container') + 'versions-mode'
|
SYSMETA_VERSIONS_MODE = get_sys_meta_prefix('container') + 'versions-mode'
|
||||||
|
|
||||||
|
|
||||||
class VersionedWritesContext(WSGIContext):
|
class VersionedWritesContext(WSGIContext):
|
||||||
@ -646,17 +650,18 @@ class VersionedWritesContext(WSGIContext):
|
|||||||
self._response_headers = []
|
self._response_headers = []
|
||||||
mode = location = ''
|
mode = location = ''
|
||||||
for key, val in self._response_headers:
|
for key, val in self._response_headers:
|
||||||
if key.lower() == VERSIONS_LOC_SYSMETA:
|
if key.lower() == SYSMETA_VERSIONS_LOC:
|
||||||
location = val
|
location = val
|
||||||
elif key.lower() == VERSIONS_MODE_SYSMETA:
|
elif key.lower() == SYSMETA_VERSIONS_MODE:
|
||||||
mode = val
|
mode = val
|
||||||
|
|
||||||
if location:
|
if location:
|
||||||
|
if mode == 'history':
|
||||||
self._response_headers.extend([
|
self._response_headers.extend([
|
||||||
(VERSIONS_LOC_CLIENT.title(), location)])
|
(CLIENT_HISTORY_LOC.title(), location)])
|
||||||
if mode:
|
else:
|
||||||
self._response_headers.extend([
|
self._response_headers.extend([
|
||||||
(VERSIONS_MODE_CLIENT.title(), mode)])
|
(CLIENT_VERSIONS_LOC.title(), location)])
|
||||||
|
|
||||||
start_response(self._response_status,
|
start_response(self._response_status,
|
||||||
self._response_headers,
|
self._response_headers,
|
||||||
@ -672,61 +677,70 @@ class VersionedWritesMiddleware(object):
|
|||||||
self.logger = get_logger(conf, log_route='versioned_writes')
|
self.logger = get_logger(conf, log_route='versioned_writes')
|
||||||
|
|
||||||
def container_request(self, req, start_response, enabled):
|
def container_request(self, req, start_response, enabled):
|
||||||
# set version location header as sysmeta
|
if CLIENT_VERSIONS_LOC in req.headers and \
|
||||||
if VERSIONS_LOC_CLIENT in req.headers:
|
CLIENT_HISTORY_LOC in req.headers:
|
||||||
val = req.headers.get(VERSIONS_LOC_CLIENT)
|
if not req.headers[CLIENT_HISTORY_LOC]:
|
||||||
if val:
|
# defer to versions location entirely
|
||||||
|
del req.headers[CLIENT_HISTORY_LOC]
|
||||||
|
elif req.headers[CLIENT_VERSIONS_LOC]:
|
||||||
|
raise HTTPBadRequest(
|
||||||
|
request=req, content_type='text/plain',
|
||||||
|
body='Only one of %s or %s may be specified' % (
|
||||||
|
CLIENT_VERSIONS_LOC, CLIENT_HISTORY_LOC))
|
||||||
|
else:
|
||||||
|
# history location is present and versions location is
|
||||||
|
# present but empty -- clean it up
|
||||||
|
del req.headers[CLIENT_VERSIONS_LOC]
|
||||||
|
|
||||||
|
if CLIENT_VERSIONS_LOC in req.headers or \
|
||||||
|
CLIENT_HISTORY_LOC in req.headers:
|
||||||
|
if CLIENT_VERSIONS_LOC in req.headers:
|
||||||
|
val = req.headers[CLIENT_VERSIONS_LOC]
|
||||||
|
mode = 'stack'
|
||||||
|
else:
|
||||||
|
val = req.headers[CLIENT_HISTORY_LOC]
|
||||||
|
mode = 'history'
|
||||||
|
|
||||||
|
if not val:
|
||||||
|
# empty value is the same as X-Remove-Versions-Location
|
||||||
|
req.headers['X-Remove-Versions-Location'] = 'x'
|
||||||
|
elif not config_true_value(enabled) and \
|
||||||
|
req.method in ('PUT', 'POST'):
|
||||||
# differently from previous version, we are actually
|
# differently from previous version, we are actually
|
||||||
# returning an error if user tries to set versions location
|
# returning an error if user tries to set versions location
|
||||||
# while feature is explicitly disabled.
|
# while feature is explicitly disabled.
|
||||||
if not config_true_value(enabled) and \
|
|
||||||
req.method in ('PUT', 'POST'):
|
|
||||||
raise HTTPPreconditionFailed(
|
raise HTTPPreconditionFailed(
|
||||||
request=req, content_type='text/plain',
|
request=req, content_type='text/plain',
|
||||||
body='Versioned Writes is disabled')
|
body='Versioned Writes is disabled')
|
||||||
|
else:
|
||||||
|
# OK, we received a value, have versioning enabled, and aren't
|
||||||
|
# trying to set two modes at once. Validate the value and
|
||||||
|
# translate to sysmeta.
|
||||||
location = check_container_format(req, val)
|
location = check_container_format(req, val)
|
||||||
req.headers[VERSIONS_LOC_SYSMETA] = location
|
req.headers[SYSMETA_VERSIONS_LOC] = location
|
||||||
|
req.headers[SYSMETA_VERSIONS_MODE] = mode
|
||||||
|
|
||||||
# reset original header to maintain sanity
|
# reset original header on container server to maintain sanity
|
||||||
# now only sysmeta is source of Versions Location
|
# now only sysmeta is source of Versions Location
|
||||||
req.headers[VERSIONS_LOC_CLIENT] = ''
|
req.headers[CLIENT_VERSIONS_LOC] = ''
|
||||||
|
|
||||||
# if both headers are in the same request
|
# if both add and remove headers are in the same request
|
||||||
# adding location takes precedence over removing
|
# adding location takes precedence over removing
|
||||||
if 'X-Remove-Versions-Location' in req.headers:
|
for header in ['X-Remove-Versions-Location',
|
||||||
del req.headers['X-Remove-Versions-Location']
|
'X-Remove-History-Location']:
|
||||||
else:
|
if header in req.headers:
|
||||||
# empty value is the same as X-Remove-Versions-Location
|
del req.headers[header]
|
||||||
req.headers['X-Remove-Versions-Location'] = 'x'
|
|
||||||
|
|
||||||
# handle removing versions container
|
if any(req.headers.get(header) for header in [
|
||||||
val = req.headers.get('X-Remove-Versions-Location')
|
'X-Remove-Versions-Location',
|
||||||
if val:
|
'X-Remove-History-Location']):
|
||||||
req.headers.update({VERSIONS_LOC_SYSMETA: '',
|
req.headers.update({CLIENT_VERSIONS_LOC: '',
|
||||||
VERSIONS_LOC_CLIENT: ''})
|
SYSMETA_VERSIONS_LOC: '',
|
||||||
del req.headers['X-Remove-Versions-Location']
|
SYSMETA_VERSIONS_MODE: ''})
|
||||||
|
for header in ['X-Remove-Versions-Location',
|
||||||
# handle versioning mode
|
'X-Remove-History-Location']:
|
||||||
if VERSIONS_MODE_CLIENT in req.headers:
|
if header in req.headers:
|
||||||
val = req.headers.pop(VERSIONS_MODE_CLIENT)
|
del req.headers[header]
|
||||||
if val:
|
|
||||||
if not config_true_value(enabled) and \
|
|
||||||
req.method in ('PUT', 'POST'):
|
|
||||||
raise HTTPPreconditionFailed(
|
|
||||||
request=req, content_type='text/plain',
|
|
||||||
body='Versioned Writes is disabled')
|
|
||||||
if val not in VERSIONING_MODES:
|
|
||||||
raise HTTPBadRequest(
|
|
||||||
request=req, content_type='text/plain',
|
|
||||||
body='X-Versions-Mode must be one of %s' % ', '.join(
|
|
||||||
VERSIONING_MODES))
|
|
||||||
req.headers[VERSIONS_MODE_SYSMETA] = val
|
|
||||||
else:
|
|
||||||
req.headers['X-Remove-Versions-Mode'] = 'x'
|
|
||||||
|
|
||||||
if req.headers.pop('X-Remove-Versions-Mode', None):
|
|
||||||
req.headers.update({VERSIONS_MODE_SYSMETA: ''})
|
|
||||||
|
|
||||||
# send request and translate sysmeta headers from response
|
# send request and translate sysmeta headers from response
|
||||||
vw_ctx = VersionedWritesContext(self.app, self.logger)
|
vw_ctx = VersionedWritesContext(self.app, self.logger)
|
||||||
@ -826,8 +840,8 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
if config_true_value(conf.get('allow_versioned_writes')):
|
if config_true_value(conf.get('allow_versioned_writes')):
|
||||||
register_swift_info('versioned_writes',
|
register_swift_info('versioned_writes', allowed_flags=(
|
||||||
allowed_versions_mode=VERSIONING_MODES)
|
CLIENT_VERSIONS_LOC, CLIENT_HISTORY_LOC))
|
||||||
|
|
||||||
def obj_versions_filter(app):
|
def obj_versions_filter(app):
|
||||||
return VersionedWritesMiddleware(app, conf)
|
return VersionedWritesMiddleware(app, conf)
|
||||||
|
@ -123,15 +123,18 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
|||||||
self.assertEqual('PUT', method)
|
self.assertEqual('PUT', method)
|
||||||
self.assertEqual('/v1/a/c', path)
|
self.assertEqual('/v1/a/c', path)
|
||||||
self.assertIn('x-container-sysmeta-versions-location', req_headers)
|
self.assertIn('x-container-sysmeta-versions-location', req_headers)
|
||||||
self.assertNotIn('x-container-sysmeta-versions-mode', req_headers)
|
self.assertEqual(req.headers['x-container-sysmeta-versions-location'],
|
||||||
|
'ver_cont')
|
||||||
|
self.assertIn('x-container-sysmeta-versions-mode', req_headers)
|
||||||
|
self.assertEqual(req.headers['x-container-sysmeta-versions-mode'],
|
||||||
|
'stack')
|
||||||
self.assertEqual(len(self.authorized), 1)
|
self.assertEqual(len(self.authorized), 1)
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
self.assertRequestEqual(req, self.authorized[0])
|
||||||
|
|
||||||
def test_put_container_history(self):
|
def test_put_container_history_header(self):
|
||||||
self.app.register('PUT', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
self.app.register('PUT', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
||||||
req = Request.blank('/v1/a/c',
|
req = Request.blank('/v1/a/c',
|
||||||
headers={'X-Versions-Location': 'ver_cont',
|
headers={'X-History-Location': 'ver_cont'},
|
||||||
'X-Versions-Mode': 'history'},
|
|
||||||
environ={'REQUEST_METHOD': 'PUT'})
|
environ={'REQUEST_METHOD': 'PUT'})
|
||||||
status, headers, body = self.call_vw(req)
|
status, headers, body = self.call_vw(req)
|
||||||
self.assertEqual(status, '200 OK')
|
self.assertEqual(status, '200 OK')
|
||||||
@ -150,17 +153,29 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
|||||||
self.assertEqual(len(self.authorized), 1)
|
self.assertEqual(len(self.authorized), 1)
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
self.assertRequestEqual(req, self.authorized[0])
|
||||||
|
|
||||||
|
def test_put_container_both_headers(self):
|
||||||
|
req = Request.blank('/v1/a/c',
|
||||||
|
headers={'X-Versions-Location': 'ver_cont',
|
||||||
|
'X-History-Location': 'ver_cont'},
|
||||||
|
environ={'REQUEST_METHOD': 'PUT'})
|
||||||
|
status, headers, body = self.call_vw(req)
|
||||||
|
self.assertEqual(status, '400 Bad Request')
|
||||||
|
self.assertFalse(self.app.calls)
|
||||||
|
|
||||||
def test_container_allow_versioned_writes_false(self):
|
def test_container_allow_versioned_writes_false(self):
|
||||||
self.vw.conf = {'allow_versioned_writes': 'false'}
|
self.vw.conf = {'allow_versioned_writes': 'false'}
|
||||||
|
|
||||||
# PUT/POST container must fail as 412 when allow_versioned_writes
|
# PUT/POST container must fail as 412 when allow_versioned_writes
|
||||||
# set to false
|
# set to false
|
||||||
for method in ('PUT', 'POST'):
|
for method in ('PUT', 'POST'):
|
||||||
|
for header in ('X-Versions-Location', 'X-History-Location'):
|
||||||
req = Request.blank('/v1/a/c',
|
req = Request.blank('/v1/a/c',
|
||||||
headers={'X-Versions-Location': 'ver_cont'},
|
headers={header: 'ver_cont'},
|
||||||
environ={'REQUEST_METHOD': method})
|
environ={'REQUEST_METHOD': method})
|
||||||
status, headers, body = self.call_vw(req)
|
status, headers, body = self.call_vw(req)
|
||||||
self.assertEqual(status, "412 Precondition Failed")
|
self.assertEqual(status, "412 Precondition Failed",
|
||||||
|
'Got %s instead of 412 when %sing '
|
||||||
|
'with %s header' % (status, method, header))
|
||||||
|
|
||||||
# GET performs as normal
|
# GET performs as normal
|
||||||
self.app.register('GET', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
self.app.register('GET', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
||||||
@ -172,117 +187,34 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
|||||||
status, headers, body = self.call_vw(req)
|
status, headers, body = self.call_vw(req)
|
||||||
self.assertEqual(status, '200 OK')
|
self.assertEqual(status, '200 OK')
|
||||||
|
|
||||||
def test_remove_versions_location(self):
|
def _test_removal(self, headers):
|
||||||
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
self.app.register('POST', '/v1/a/c', swob.HTTPNoContent, {}, 'passed')
|
||||||
req = Request.blank('/v1/a/c',
|
req = Request.blank('/v1/a/c',
|
||||||
headers={'X-Remove-Versions-Location': 'x'},
|
headers=headers,
|
||||||
environ={'REQUEST_METHOD': 'POST'})
|
environ={'REQUEST_METHOD': 'POST'})
|
||||||
status, headers, body = self.call_vw(req)
|
status, headers, body = self.call_vw(req)
|
||||||
self.assertEqual(status, '200 OK')
|
self.assertEqual(status, '204 No Content')
|
||||||
|
|
||||||
# check for sysmeta header
|
# check for sysmeta header
|
||||||
calls = self.app.calls_with_headers
|
calls = self.app.calls_with_headers
|
||||||
method, path, req_headers = calls[0]
|
method, path, req_headers = calls[0]
|
||||||
self.assertEqual('POST', method)
|
self.assertEqual('POST', method)
|
||||||
self.assertEqual('/v1/a/c', path)
|
self.assertEqual('/v1/a/c', path)
|
||||||
self.assertIn('x-container-sysmeta-versions-location', req_headers)
|
for header in ['x-container-sysmeta-versions-location',
|
||||||
self.assertEqual('',
|
'x-container-sysmeta-versions-mode',
|
||||||
req_headers['x-container-sysmeta-versions-location'])
|
'x-versions-location']:
|
||||||
self.assertIn('x-versions-location', req_headers)
|
self.assertIn(header, req_headers)
|
||||||
self.assertEqual('', req_headers['x-versions-location'])
|
self.assertEqual('', req_headers[header])
|
||||||
self.assertEqual(len(self.authorized), 1)
|
self.assertEqual(len(self.authorized), 1)
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
self.assertRequestEqual(req, self.authorized[0])
|
||||||
|
|
||||||
|
def test_remove_headers(self):
|
||||||
|
self._test_removal({'X-Remove-Versions-Location': 'x'})
|
||||||
|
self._test_removal({'X-Remove-History-Location': 'x'})
|
||||||
|
|
||||||
def test_empty_versions_location(self):
|
def test_empty_versions_location(self):
|
||||||
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
self._test_removal({'X-Versions-Location': ''})
|
||||||
req = Request.blank('/v1/a/c',
|
self._test_removal({'X-History-Location': ''})
|
||||||
headers={'X-Versions-Location': ''},
|
|
||||||
environ={'REQUEST_METHOD': 'POST'})
|
|
||||||
status, headers, body = self.call_vw(req)
|
|
||||||
self.assertEqual(status, '200 OK')
|
|
||||||
|
|
||||||
# check for sysmeta header
|
|
||||||
calls = self.app.calls_with_headers
|
|
||||||
method, path, req_headers = calls[0]
|
|
||||||
self.assertEqual('POST', method)
|
|
||||||
self.assertEqual('/v1/a/c', path)
|
|
||||||
self.assertIn('x-container-sysmeta-versions-location', req_headers)
|
|
||||||
self.assertEqual('',
|
|
||||||
req_headers['x-container-sysmeta-versions-location'])
|
|
||||||
self.assertIn('x-versions-location', req_headers)
|
|
||||||
self.assertEqual('', req_headers['x-versions-location'])
|
|
||||||
self.assertEqual(len(self.authorized), 1)
|
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
|
||||||
|
|
||||||
def test_post_versions_mode(self):
|
|
||||||
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
|
||||||
req = Request.blank('/v1/a/c',
|
|
||||||
headers={'X-Versions-Mode': 'stack'},
|
|
||||||
environ={'REQUEST_METHOD': 'POST'})
|
|
||||||
status, headers, body = self.call_vw(req)
|
|
||||||
self.assertEqual(status, '200 OK')
|
|
||||||
|
|
||||||
# check for sysmeta header
|
|
||||||
calls = self.app.calls_with_headers
|
|
||||||
method, path, req_headers = calls[0]
|
|
||||||
self.assertEqual('POST', method)
|
|
||||||
self.assertEqual('/v1/a/c', path)
|
|
||||||
self.assertIn('x-container-sysmeta-versions-mode', req_headers)
|
|
||||||
self.assertEqual('stack',
|
|
||||||
req_headers['x-container-sysmeta-versions-mode'])
|
|
||||||
self.assertNotIn('x-versions-mode', req_headers)
|
|
||||||
self.assertEqual(len(self.authorized), 1)
|
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
|
||||||
|
|
||||||
def test_remove_versions_mode(self):
|
|
||||||
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
|
||||||
req = Request.blank('/v1/a/c',
|
|
||||||
headers={'X-Remove-Versions-Mode': 'x'},
|
|
||||||
environ={'REQUEST_METHOD': 'POST'})
|
|
||||||
status, headers, body = self.call_vw(req)
|
|
||||||
self.assertEqual(status, '200 OK')
|
|
||||||
|
|
||||||
# check for sysmeta header
|
|
||||||
calls = self.app.calls_with_headers
|
|
||||||
method, path, req_headers = calls[0]
|
|
||||||
self.assertEqual('POST', method)
|
|
||||||
self.assertEqual('/v1/a/c', path)
|
|
||||||
self.assertIn('x-container-sysmeta-versions-mode', req_headers)
|
|
||||||
self.assertEqual('',
|
|
||||||
req_headers['x-container-sysmeta-versions-mode'])
|
|
||||||
self.assertNotIn('x-versions-mode', req_headers)
|
|
||||||
self.assertEqual(len(self.authorized), 1)
|
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
|
||||||
|
|
||||||
def test_empty_versions_mode(self):
|
|
||||||
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
|
||||||
req = Request.blank('/v1/a/c',
|
|
||||||
headers={'X-Versions-Mode': ''},
|
|
||||||
environ={'REQUEST_METHOD': 'POST'})
|
|
||||||
status, headers, body = self.call_vw(req)
|
|
||||||
self.assertEqual(status, '200 OK')
|
|
||||||
|
|
||||||
# check for sysmeta header
|
|
||||||
calls = self.app.calls_with_headers
|
|
||||||
method, path, req_headers = calls[0]
|
|
||||||
self.assertEqual('POST', method)
|
|
||||||
self.assertEqual('/v1/a/c', path)
|
|
||||||
self.assertIn('x-container-sysmeta-versions-mode', req_headers)
|
|
||||||
self.assertEqual('',
|
|
||||||
req_headers['x-container-sysmeta-versions-mode'])
|
|
||||||
self.assertNotIn('x-versions-mode', req_headers)
|
|
||||||
self.assertEqual(len(self.authorized), 1)
|
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
|
||||||
|
|
||||||
def test_bad_versions_mode(self):
|
|
||||||
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
|
||||||
req = Request.blank('/v1/a/c',
|
|
||||||
headers={'X-Versions-Mode': 'foo'},
|
|
||||||
environ={'REQUEST_METHOD': 'POST'})
|
|
||||||
status, headers, body = self.call_vw(req)
|
|
||||||
self.assertEqual(status, '400 Bad Request')
|
|
||||||
self.assertEqual(len(self.authorized), 0)
|
|
||||||
self.assertEqual('X-Versions-Mode must be one of stack, history', body)
|
|
||||||
|
|
||||||
def test_remove_add_versions_precedence(self):
|
def test_remove_add_versions_precedence(self):
|
||||||
self.app.register(
|
self.app.register(
|
||||||
@ -308,6 +240,43 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
|||||||
self.assertEqual(len(self.authorized), 1)
|
self.assertEqual(len(self.authorized), 1)
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
self.assertRequestEqual(req, self.authorized[0])
|
||||||
|
|
||||||
|
def _test_blank_add_versions_precedence(self, blank_header, add_header):
|
||||||
|
self.app.register(
|
||||||
|
'POST', '/v1/a/c', swob.HTTPOk,
|
||||||
|
{'x-container-sysmeta-versions-location': 'ver_cont'},
|
||||||
|
'passed')
|
||||||
|
req = Request.blank('/v1/a/c',
|
||||||
|
headers={blank_header: '',
|
||||||
|
add_header: 'ver_cont'},
|
||||||
|
environ={'REQUEST_METHOD': 'POST'})
|
||||||
|
|
||||||
|
status, headers, body = self.call_vw(req)
|
||||||
|
self.assertEqual(status, '200 OK')
|
||||||
|
|
||||||
|
# check for sysmeta header
|
||||||
|
calls = self.app.calls_with_headers
|
||||||
|
method, path, req_headers = calls[-1]
|
||||||
|
self.assertEqual('POST', method)
|
||||||
|
self.assertEqual('/v1/a/c', path)
|
||||||
|
self.assertIn('x-container-sysmeta-versions-location', req_headers)
|
||||||
|
self.assertEqual('ver_cont',
|
||||||
|
req_headers['x-container-sysmeta-versions-location'])
|
||||||
|
self.assertIn('x-container-sysmeta-versions-mode', req_headers)
|
||||||
|
self.assertEqual('history' if add_header == 'X-History-Location'
|
||||||
|
else 'stack',
|
||||||
|
req_headers['x-container-sysmeta-versions-mode'])
|
||||||
|
self.assertNotIn('x-remove-versions-location', req_headers)
|
||||||
|
self.assertIn('x-versions-location', req_headers)
|
||||||
|
self.assertEqual('', req_headers['x-versions-location'])
|
||||||
|
self.assertEqual(len(self.authorized), 1)
|
||||||
|
self.assertRequestEqual(req, self.authorized[0])
|
||||||
|
|
||||||
|
def test_blank_add_versions_precedence(self):
|
||||||
|
self._test_blank_add_versions_precedence(
|
||||||
|
'X-Versions-Location', 'X-History-Location')
|
||||||
|
self._test_blank_add_versions_precedence(
|
||||||
|
'X-History-Location', 'X-Versions-Location')
|
||||||
|
|
||||||
def test_get_container(self):
|
def test_get_container(self):
|
||||||
self.app.register(
|
self.app.register(
|
||||||
'GET', '/v1/a/c', swob.HTTPOk,
|
'GET', '/v1/a/c', swob.HTTPOk,
|
||||||
@ -319,7 +288,6 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
|||||||
status, headers, body = self.call_vw(req)
|
status, headers, body = self.call_vw(req)
|
||||||
self.assertEqual(status, '200 OK')
|
self.assertEqual(status, '200 OK')
|
||||||
self.assertIn(('X-Versions-Location', 'ver_cont'), headers)
|
self.assertIn(('X-Versions-Location', 'ver_cont'), headers)
|
||||||
self.assertIn(('X-Versions-Mode', 'stack'), headers)
|
|
||||||
self.assertEqual(len(self.authorized), 1)
|
self.assertEqual(len(self.authorized), 1)
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
self.assertRequestEqual(req, self.authorized[0])
|
||||||
|
|
||||||
@ -333,8 +301,7 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
|||||||
environ={'REQUEST_METHOD': 'HEAD'})
|
environ={'REQUEST_METHOD': 'HEAD'})
|
||||||
status, headers, body = self.call_vw(req)
|
status, headers, body = self.call_vw(req)
|
||||||
self.assertEqual(status, '200 OK')
|
self.assertEqual(status, '200 OK')
|
||||||
self.assertIn(('X-Versions-Location', 'other_ver_cont'), headers)
|
self.assertIn(('X-History-Location', 'other_ver_cont'), headers)
|
||||||
self.assertIn(('X-Versions-Mode', 'history'), headers)
|
|
||||||
self.assertEqual(len(self.authorized), 1)
|
self.assertEqual(len(self.authorized), 1)
|
||||||
self.assertRequestEqual(req, self.authorized[0])
|
self.assertRequestEqual(req, self.authorized[0])
|
||||||
|
|
||||||
@ -850,17 +817,6 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
|||||||
self.assertTrue(path.startswith('/v1/a/ver_cont/001o/3'))
|
self.assertTrue(path.startswith('/v1/a/ver_cont/001o/3'))
|
||||||
self.assertNotIn('x-if-delete-at', [h.lower() for h in req_headers])
|
self.assertNotIn('x-if-delete-at', [h.lower() for h in req_headers])
|
||||||
|
|
||||||
def test_post_bad_mode(self):
|
|
||||||
req = Request.blank(
|
|
||||||
'/v1/a/c',
|
|
||||||
environ={'REQUEST_METHOD': 'POST',
|
|
||||||
'CONTENT_LENGTH': '0',
|
|
||||||
'HTTP_X_VERSIONS_MODE': 'bad-mode'})
|
|
||||||
status, headers, body = self.call_vw(req)
|
|
||||||
self.assertEqual(status, '400 Bad Request')
|
|
||||||
self.assertEqual('X-Versions-Mode must be one of stack, history', body)
|
|
||||||
self.assertFalse(self.app.calls_with_headers)
|
|
||||||
|
|
||||||
@mock.patch('swift.common.middleware.versioned_writes.time.time',
|
@mock.patch('swift.common.middleware.versioned_writes.time.time',
|
||||||
return_value=1234)
|
return_value=1234)
|
||||||
def test_history_delete_marker_no_object_success(self, mock_time):
|
def test_history_delete_marker_no_object_success(self, mock_time):
|
||||||
@ -1526,8 +1482,8 @@ class TestSwiftInfo(unittest.TestCase):
|
|||||||
swift_info = utils.get_swift_info()
|
swift_info = utils.get_swift_info()
|
||||||
self.assertIn('versioned_writes', swift_info)
|
self.assertIn('versioned_writes', swift_info)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
swift_info['versioned_writes'].get('allowed_versions_mode'),
|
swift_info['versioned_writes'].get('allowed_flags'),
|
||||||
('stack', 'history'))
|
('x-versions-location', 'x-history-location'))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user