Use separate headers for versioned_writes' stack and history modes
Now, instead of saying X-Versions-Location: <container> X-Versions-Mode: history clients should just say X-History-Location: <container> Since we've never had a release featuring a user-settable X-Versions-Mode header, support may be dropped and that is now ignored. Change-Id: Icfd0f481d4e40dd5375c737190aea7ee8dbc3bf9
This commit is contained in:
parent
b4c1517ddd
commit
60a2fe0ba8
@ -621,6 +621,27 @@ X-Fresh-Metadata:
|
||||
in: header
|
||||
required: false
|
||||
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:
|
||||
description: |
|
||||
If set to true , Object Storage queries all
|
||||
@ -683,9 +704,17 @@ X-Remove-Container-name:
|
||||
in: header
|
||||
required: false
|
||||
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:
|
||||
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
|
||||
required: false
|
||||
type: string
|
||||
@ -756,10 +785,11 @@ X-Trans-Id-Extra:
|
||||
X-Versions-Location:
|
||||
description: |
|
||||
The URL-encoded UTF-8 representation of the container that stores
|
||||
previous versions of objects. If not set, versioning is disabled
|
||||
for this container. For more information about object versioning,
|
||||
see `Object versioning <http://docs.openstack.org/developer/
|
||||
swift/api/object_versioning.html>`_.
|
||||
previous versions of objects. If neither this nor ``X-History-Location``
|
||||
is set, versioning is disabled for this container. ``X-Versions-Location``
|
||||
and ``X-History-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
|
||||
@ -773,26 +803,6 @@ X-Versions-Location_resp:
|
||||
in: header
|
||||
required: false
|
||||
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
|
||||
account:
|
||||
|
@ -83,7 +83,7 @@ Response Parameters
|
||||
- X-Container-Sync-Key: X-Container-Sync-Key_resp
|
||||
- X-Container-Sync-To: X-Container-Sync-To_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-Trans-Id: X-Trans-Id
|
||||
- Content_Type: Content-Type_listing_resp
|
||||
@ -197,7 +197,7 @@ Request
|
||||
- X-Container-Sync-To: X-Container-Sync-To
|
||||
- X-Container-Sync-Key: X-Container-Sync-Key
|
||||
- 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-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
|
||||
@ -331,8 +331,9 @@ Request
|
||||
- X-Container-Sync-To: X-Container-Sync-To
|
||||
- X-Container-Sync-Key: X-Container-Sync-Key
|
||||
- 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-History-Location: X-Remove-History-Location
|
||||
- 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-Max-Age: X-Container-Meta-Access-Control-Max-Age
|
||||
@ -436,7 +437,7 @@ Response Parameters
|
||||
- X-Trans-Id: X-Trans-Id
|
||||
- Content-Type: Content-Type_cud_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
|
||||
|
||||
|
||||
|
@ -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
|
||||
``[filter:versioned_writes]`` section of the proxy-server configuration file.
|
||||
|
||||
The ``X-Versions-Location`` header defines the
|
||||
container that holds the non-current versions of your objects. You
|
||||
must UTF-8-encode and then URL-encode the container name before you
|
||||
include it in the ``X-Versions-Location`` header. This header enables
|
||||
object versioning for all objects in the container. With a comparable
|
||||
``archive`` container in place, changes to objects in the ``current``
|
||||
container automatically create non-current versions in the ``archive``
|
||||
container.
|
||||
To enable object versioning for a container, you must specify an "archive
|
||||
container" that will retain non-current versions via either the
|
||||
``X-Versions-Location`` or ``X-History-Location`` header. These two headers
|
||||
enable two distinct modes of operation. Either mode may be used within a
|
||||
cluster, but only one mode may be active for any given container. You must
|
||||
UTF-8-encode and then URL-encode the container name before you include it in
|
||||
the header.
|
||||
|
||||
The ``X-Versions-Mode`` header defines the behavior of ``DELETE`` requests to
|
||||
objects in the versioned container. In the default ``stack`` mode, deleting an
|
||||
object will restore the most-recent version from the ``archive`` container,
|
||||
overwriting the curent version. Alternatively you may specify ``history``
|
||||
mode, where deleting an object will copy the current version to the
|
||||
``archive`` then remove it from the ``current`` container.
|
||||
For both modes, **PUT** requests will archive any pre-existing objects before
|
||||
writing new data, and **GET** requests will serve the current version. **COPY**
|
||||
requests behave like a **GET** followed by a **PUT**; that is, if the copy
|
||||
*source* is in a versioned container then the current version will be copied,
|
||||
and if the copy *destination* is in a versioned container then any pre-existing
|
||||
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:
|
||||
|
||||
.. 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::
|
||||
|
||||
@ -169,14 +176,14 @@ Example Using ``stack`` Mode
|
||||
on it. If want to completely remove an object and you have five
|
||||
versions of it, you must **DELETE** it five times.
|
||||
|
||||
Example Using ``history`` Mode
|
||||
------------------------------
|
||||
Example Using ``X-History-Location``
|
||||
------------------------------------
|
||||
|
||||
#. Create the ``current`` container:
|
||||
|
||||
.. 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::
|
||||
|
||||
@ -266,7 +273,7 @@ Example Using ``history`` Mode
|
||||
#. Issue a **DELETE** request to a versioned object to copy the
|
||||
current version of the object to the archive container then delete it from
|
||||
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::
|
||||
|
||||
|
@ -15,21 +15,48 @@
|
||||
|
||||
"""
|
||||
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
|
||||
``X-Versions-Location`` header on the container, and its value is the
|
||||
container where the versions are stored.
|
||||
to tell swift to version all objects in the container. The value of the flag is
|
||||
the container where the versions are stored (commonly referred to as the
|
||||
"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::
|
||||
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.
|
||||
|
||||
.. note::
|
||||
Enabling versioning on an archive container is not recommended.
|
||||
|
||||
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
|
||||
new object and the data in the ``PUT`` request is saved as the data for the
|
||||
versioned object. The new object name (for the previous version) is
|
||||
``<archive_container>/<length><object_name>/<timestamp>``, where ``length``
|
||||
is the 3-character zero-padded hexadecimal length of the ``<object_name>`` and
|
||||
``<timestamp>`` is the timestamp of when the previous version was created.
|
||||
new object in the archive container and the data in the ``PUT`` request is
|
||||
saved as the data for the versioned object. The new object name (for the
|
||||
previous version) is ``<archive_container>/<length><object_name>/<timestamp>``,
|
||||
where ``length`` is the 3-character zero-padded hexadecimal length of the
|
||||
``<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
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
as described above.
|
||||
|
||||
To restore a previous version of an object, find the desired version in the
|
||||
archive container then issue a ``COPY`` with a ``Destination`` header
|
||||
indicating the original location. This will retain a copy of the current
|
||||
version similar to a ``PUT`` over the versioned object. Additionally, if the
|
||||
container is in ``stack`` mode and the client wishes to permanently delete the
|
||||
current version, it may issue a ``DELETE`` to the versioned object as
|
||||
described above.
|
||||
indicating the original location. This will archive the current version similar
|
||||
to a ``PUT`` over the versioned object. If the the client additionally wishes
|
||||
to permanently delete what was the current version, it must find the
|
||||
newly-created archive in the archive container and issue a separate ``DELETE``
|
||||
to it.
|
||||
|
||||
--------------------------------------------------
|
||||
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
|
||||
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
|
||||
configuration files are still valid, but only for ``stack`` mode. In future
|
||||
releases, ``allow_versions`` will be deprecated in favor of adding this
|
||||
middleware to the pipeline to enable or disable the feature.
|
||||
configuration files are still valid, but only when using
|
||||
``X-Versions-Location``. In future releases, ``allow_versions`` will be
|
||||
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
|
||||
set ``allow_versioned_writes`` to ``True`` in the middleware options
|
||||
@ -92,9 +97,9 @@ request.
|
||||
|
||||
.. note::
|
||||
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
|
||||
enable ``history`` mode.
|
||||
enable the use of ``X-History-Location``.
|
||||
|
||||
|
||||
Upgrade considerations:
|
||||
@ -103,25 +108,25 @@ Upgrade considerations:
|
||||
If ``allow_versioned_writes`` is set in the filter configuration, you can leave
|
||||
the ``allow_versions`` flag in the container server configuration files
|
||||
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.
|
||||
|
||||
Clients should not use the 'history' mode until all proxies in the cluster
|
||||
have been upgraded to a version of Swift that supports it. Attempting to use
|
||||
the 'history' mode during a rolling upgrade may result in some requests being
|
||||
served by proxies running old code (which necessarily uses the 'stack' mode),
|
||||
leading to data loss.
|
||||
Clients should not use the ``X-History-Location`` header until all proxies in
|
||||
the cluster have been upgraded to a version of Swift that supports it.
|
||||
Attempting to use ``X-History-Location`` during a rolling upgrade may result
|
||||
in some requests being served by proxies running old code, leading to data
|
||||
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
|
||||
header to an existing container. Also make sure the container referenced by
|
||||
the ``X-Versions-Location`` exists. In this example, the name of that
|
||||
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
|
||||
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>" \
|
||||
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
|
||||
that the container referenced by the ``X-Versions-Location`` exists. In this
|
||||
As above, create a container with the ``X-History-Location`` header and ensure
|
||||
that the container referenced by the ``X-History-Location`` exists. In this
|
||||
example, the name of that container is "versions"::
|
||||
|
||||
curl -i -XPUT -H "X-Auth-Token: <token>" -H "X-Versions-Mode: history" \
|
||||
-H "X-Versions-Location: versions" http://<storage_url>/container
|
||||
curl -i -XPUT -H "X-Auth-Token: <token>" \
|
||||
-H "X-History-Location: versions" http://<storage_url>/container
|
||||
curl -i -XPUT -H "X-Auth-Token: <token>" http://<storage_url>/versions
|
||||
|
||||
Create an object (the first version)::
|
||||
@ -238,12 +243,11 @@ from swift.common.exceptions import (
|
||||
ListingIterNotFound, ListingIterError)
|
||||
|
||||
|
||||
VERSIONING_MODES = ('stack', 'history')
|
||||
DELETE_MARKER_CONTENT_TYPE = 'application/x-deleted;swift_versions_deleted=1'
|
||||
VERSIONS_LOC_CLIENT = 'x-versions-location'
|
||||
VERSIONS_LOC_SYSMETA = get_sys_meta_prefix('container') + 'versions-location'
|
||||
VERSIONS_MODE_CLIENT = 'x-versions-mode'
|
||||
VERSIONS_MODE_SYSMETA = get_sys_meta_prefix('container') + 'versions-mode'
|
||||
CLIENT_VERSIONS_LOC = 'x-versions-location'
|
||||
CLIENT_HISTORY_LOC = 'x-history-location'
|
||||
SYSMETA_VERSIONS_LOC = get_sys_meta_prefix('container') + 'versions-location'
|
||||
SYSMETA_VERSIONS_MODE = get_sys_meta_prefix('container') + 'versions-mode'
|
||||
|
||||
|
||||
class VersionedWritesContext(WSGIContext):
|
||||
@ -646,17 +650,18 @@ class VersionedWritesContext(WSGIContext):
|
||||
self._response_headers = []
|
||||
mode = location = ''
|
||||
for key, val in self._response_headers:
|
||||
if key.lower() == VERSIONS_LOC_SYSMETA:
|
||||
if key.lower() == SYSMETA_VERSIONS_LOC:
|
||||
location = val
|
||||
elif key.lower() == VERSIONS_MODE_SYSMETA:
|
||||
elif key.lower() == SYSMETA_VERSIONS_MODE:
|
||||
mode = val
|
||||
|
||||
if location:
|
||||
self._response_headers.extend([
|
||||
(VERSIONS_LOC_CLIENT.title(), location)])
|
||||
if mode:
|
||||
self._response_headers.extend([
|
||||
(VERSIONS_MODE_CLIENT.title(), mode)])
|
||||
if mode == 'history':
|
||||
self._response_headers.extend([
|
||||
(CLIENT_HISTORY_LOC.title(), location)])
|
||||
else:
|
||||
self._response_headers.extend([
|
||||
(CLIENT_VERSIONS_LOC.title(), location)])
|
||||
|
||||
start_response(self._response_status,
|
||||
self._response_headers,
|
||||
@ -672,61 +677,70 @@ class VersionedWritesMiddleware(object):
|
||||
self.logger = get_logger(conf, log_route='versioned_writes')
|
||||
|
||||
def container_request(self, req, start_response, enabled):
|
||||
# set version location header as sysmeta
|
||||
if VERSIONS_LOC_CLIENT in req.headers:
|
||||
val = req.headers.get(VERSIONS_LOC_CLIENT)
|
||||
if val:
|
||||
if CLIENT_VERSIONS_LOC in req.headers and \
|
||||
CLIENT_HISTORY_LOC in req.headers:
|
||||
if not req.headers[CLIENT_HISTORY_LOC]:
|
||||
# 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
|
||||
# returning an error if user tries to set versions location
|
||||
# while feature is explicitly disabled.
|
||||
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')
|
||||
|
||||
raise HTTPPreconditionFailed(
|
||||
request=req, content_type='text/plain',
|
||||
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)
|
||||
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
|
||||
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
|
||||
if 'X-Remove-Versions-Location' in req.headers:
|
||||
del req.headers['X-Remove-Versions-Location']
|
||||
else:
|
||||
# empty value is the same as X-Remove-Versions-Location
|
||||
req.headers['X-Remove-Versions-Location'] = 'x'
|
||||
for header in ['X-Remove-Versions-Location',
|
||||
'X-Remove-History-Location']:
|
||||
if header in req.headers:
|
||||
del req.headers[header]
|
||||
|
||||
# handle removing versions container
|
||||
val = req.headers.get('X-Remove-Versions-Location')
|
||||
if val:
|
||||
req.headers.update({VERSIONS_LOC_SYSMETA: '',
|
||||
VERSIONS_LOC_CLIENT: ''})
|
||||
del req.headers['X-Remove-Versions-Location']
|
||||
|
||||
# handle versioning mode
|
||||
if VERSIONS_MODE_CLIENT in req.headers:
|
||||
val = req.headers.pop(VERSIONS_MODE_CLIENT)
|
||||
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: ''})
|
||||
if any(req.headers.get(header) for header in [
|
||||
'X-Remove-Versions-Location',
|
||||
'X-Remove-History-Location']):
|
||||
req.headers.update({CLIENT_VERSIONS_LOC: '',
|
||||
SYSMETA_VERSIONS_LOC: '',
|
||||
SYSMETA_VERSIONS_MODE: ''})
|
||||
for header in ['X-Remove-Versions-Location',
|
||||
'X-Remove-History-Location']:
|
||||
if header in req.headers:
|
||||
del req.headers[header]
|
||||
|
||||
# send request and translate sysmeta headers from response
|
||||
vw_ctx = VersionedWritesContext(self.app, self.logger)
|
||||
@ -826,8 +840,8 @@ def filter_factory(global_conf, **local_conf):
|
||||
conf = global_conf.copy()
|
||||
conf.update(local_conf)
|
||||
if config_true_value(conf.get('allow_versioned_writes')):
|
||||
register_swift_info('versioned_writes',
|
||||
allowed_versions_mode=VERSIONING_MODES)
|
||||
register_swift_info('versioned_writes', allowed_flags=(
|
||||
CLIENT_VERSIONS_LOC, CLIENT_HISTORY_LOC))
|
||||
|
||||
def obj_versions_filter(app):
|
||||
return VersionedWritesMiddleware(app, conf)
|
||||
|
@ -123,15 +123,18 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
||||
self.assertEqual('PUT', method)
|
||||
self.assertEqual('/v1/a/c', path)
|
||||
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.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')
|
||||
req = Request.blank('/v1/a/c',
|
||||
headers={'X-Versions-Location': 'ver_cont',
|
||||
'X-Versions-Mode': 'history'},
|
||||
headers={'X-History-Location': 'ver_cont'},
|
||||
environ={'REQUEST_METHOD': 'PUT'})
|
||||
status, headers, body = self.call_vw(req)
|
||||
self.assertEqual(status, '200 OK')
|
||||
@ -150,17 +153,29 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
||||
self.assertEqual(len(self.authorized), 1)
|
||||
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):
|
||||
self.vw.conf = {'allow_versioned_writes': 'false'}
|
||||
|
||||
# PUT/POST container must fail as 412 when allow_versioned_writes
|
||||
# set to false
|
||||
for method in ('PUT', 'POST'):
|
||||
req = Request.blank('/v1/a/c',
|
||||
headers={'X-Versions-Location': 'ver_cont'},
|
||||
environ={'REQUEST_METHOD': method})
|
||||
status, headers, body = self.call_vw(req)
|
||||
self.assertEqual(status, "412 Precondition Failed")
|
||||
for header in ('X-Versions-Location', 'X-History-Location'):
|
||||
req = Request.blank('/v1/a/c',
|
||||
headers={header: 'ver_cont'},
|
||||
environ={'REQUEST_METHOD': method})
|
||||
status, headers, body = self.call_vw(req)
|
||||
self.assertEqual(status, "412 Precondition Failed",
|
||||
'Got %s instead of 412 when %sing '
|
||||
'with %s header' % (status, method, header))
|
||||
|
||||
# GET performs as normal
|
||||
self.app.register('GET', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
||||
@ -172,117 +187,34 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
||||
status, headers, body = self.call_vw(req)
|
||||
self.assertEqual(status, '200 OK')
|
||||
|
||||
def test_remove_versions_location(self):
|
||||
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
||||
def _test_removal(self, headers):
|
||||
self.app.register('POST', '/v1/a/c', swob.HTTPNoContent, {}, 'passed')
|
||||
req = Request.blank('/v1/a/c',
|
||||
headers={'X-Remove-Versions-Location': 'x'},
|
||||
headers=headers,
|
||||
environ={'REQUEST_METHOD': 'POST'})
|
||||
status, headers, body = self.call_vw(req)
|
||||
self.assertEqual(status, '200 OK')
|
||||
self.assertEqual(status, '204 No Content')
|
||||
|
||||
# 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'])
|
||||
for header in ['x-container-sysmeta-versions-location',
|
||||
'x-container-sysmeta-versions-mode',
|
||||
'x-versions-location']:
|
||||
self.assertIn(header, req_headers)
|
||||
self.assertEqual('', req_headers[header])
|
||||
self.assertEqual(len(self.authorized), 1)
|
||||
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):
|
||||
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
||||
req = Request.blank('/v1/a/c',
|
||||
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)
|
||||
self._test_removal({'X-Versions-Location': ''})
|
||||
self._test_removal({'X-History-Location': ''})
|
||||
|
||||
def test_remove_add_versions_precedence(self):
|
||||
self.app.register(
|
||||
@ -308,6 +240,43 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
||||
self.assertEqual(len(self.authorized), 1)
|
||||
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):
|
||||
self.app.register(
|
||||
'GET', '/v1/a/c', swob.HTTPOk,
|
||||
@ -319,7 +288,6 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
||||
status, headers, body = self.call_vw(req)
|
||||
self.assertEqual(status, '200 OK')
|
||||
self.assertIn(('X-Versions-Location', 'ver_cont'), headers)
|
||||
self.assertIn(('X-Versions-Mode', 'stack'), headers)
|
||||
self.assertEqual(len(self.authorized), 1)
|
||||
self.assertRequestEqual(req, self.authorized[0])
|
||||
|
||||
@ -333,8 +301,7 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
||||
environ={'REQUEST_METHOD': 'HEAD'})
|
||||
status, headers, body = self.call_vw(req)
|
||||
self.assertEqual(status, '200 OK')
|
||||
self.assertIn(('X-Versions-Location', 'other_ver_cont'), headers)
|
||||
self.assertIn(('X-Versions-Mode', 'history'), headers)
|
||||
self.assertIn(('X-History-Location', 'other_ver_cont'), headers)
|
||||
self.assertEqual(len(self.authorized), 1)
|
||||
self.assertRequestEqual(req, self.authorized[0])
|
||||
|
||||
@ -850,17 +817,6 @@ class VersionedWritesTestCase(VersionedWritesBaseTestCase):
|
||||
self.assertTrue(path.startswith('/v1/a/ver_cont/001o/3'))
|
||||
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',
|
||||
return_value=1234)
|
||||
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()
|
||||
self.assertIn('versioned_writes', swift_info)
|
||||
self.assertEqual(
|
||||
swift_info['versioned_writes'].get('allowed_versions_mode'),
|
||||
('stack', 'history'))
|
||||
swift_info['versioned_writes'].get('allowed_flags'),
|
||||
('x-versions-location', 'x-history-location'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
Loading…
Reference in New Issue
Block a user