Merge "Use separate headers for versioned_writes' stack and history modes"

This commit is contained in:
Jenkins 2016-09-22 20:05:02 +00:00 committed by Gerrit Code Review
commit f1c3ceb48d
5 changed files with 281 additions and 293 deletions

View File

@ -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:

View File

@ -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

View File

@ -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::

View File

@ -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)

View File

@ -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__':