dab7192e1e
The existing FakeSwift implementation already supports using registered GET responses for GET requests with a query param. It also supports using registered GET responses for HEAD requests (if they either both had the exact SAME matching query params, or both did not have ANY query params). But it did not support using registered GET responses w/o query params for HEAD requests with a query param, even though a GET with the same query param would work. This change makes it a little more consistent: any client or test that makes a GET request, should be able to make a similar HEAD request and expect consistent response headers and status. This test infra improvement is needed as we're going to be extending test_slo with a bunch of tests that assert consistent response headers for both GET and HEAD requests w/ new query params. Change-Id: Idb4020fdeee87a9164312dc9647ab0820b098ff8
3373 lines
141 KiB
Python
3373 lines
141 KiB
Python
# Copyright (c) 2019 OpenStack Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import functools
|
|
import json
|
|
import os
|
|
import time
|
|
import mock
|
|
import unittest
|
|
import six
|
|
from six.moves import urllib
|
|
from swift.common import swob, utils
|
|
from swift.common.middleware import versioned_writes, copy, symlink, \
|
|
listing_formats
|
|
from swift.common.swob import Request, wsgi_quote, str_to_wsgi
|
|
from swift.common.middleware.symlink import TGT_OBJ_SYSMETA_SYMLINK_HDR, \
|
|
ALLOW_RESERVED_NAMES, SYMLOOP_EXTEND
|
|
from swift.common.middleware.versioned_writes.object_versioning import \
|
|
SYSMETA_VERSIONS_CONT, SYSMETA_VERSIONS_ENABLED, \
|
|
SYSMETA_VERSIONS_SYMLINK, DELETE_MARKER_CONTENT_TYPE
|
|
from swift.common.request_helpers import get_reserved_name
|
|
from swift.common.storage_policy import StoragePolicy
|
|
from swift.common.utils import md5
|
|
from swift.proxy.controllers.base import get_cache_key
|
|
from test.unit import patch_policies, FakeMemcache, make_timestamp_iter
|
|
from test.unit.common.middleware.helpers import FakeSwift
|
|
|
|
|
|
def local_tz(func):
|
|
'''
|
|
Decorator to change the timezone when running a test.
|
|
|
|
This uses the Eastern Time Zone definition from the time module's docs.
|
|
Note that the timezone affects things like time.time() and time.mktime().
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
tz = os.environ.get('TZ', '')
|
|
try:
|
|
os.environ['TZ'] = 'EST+05EDT,M4.1.0,M10.5.0'
|
|
time.tzset()
|
|
return func(*args, **kwargs)
|
|
finally:
|
|
os.environ['TZ'] = tz
|
|
time.tzset()
|
|
return wrapper
|
|
|
|
|
|
class ObjectVersioningBaseTestCase(unittest.TestCase):
|
|
def setUp(self):
|
|
self.app = FakeSwift()
|
|
conf = {}
|
|
self.sym = symlink.filter_factory(conf)(self.app)
|
|
self.sym.logger = self.app.logger
|
|
self.ov = versioned_writes.object_versioning.\
|
|
ObjectVersioningMiddleware(self.sym, conf)
|
|
self.ov.logger = self.app.logger
|
|
self.cp = copy.filter_factory({})(self.ov)
|
|
self.lf = listing_formats.ListingFilter(self.cp, {}, self.app.logger)
|
|
|
|
self.ts = make_timestamp_iter()
|
|
cont_cache_version_on = {'sysmeta': {
|
|
'versions-container': self.build_container_name('c'),
|
|
'versions-enabled': 'true'}}
|
|
self.cache_version_on = FakeMemcache()
|
|
self.cache_version_on.set(get_cache_key('a'), {'status': 200})
|
|
self.cache_version_on.set(get_cache_key('a', 'c'),
|
|
cont_cache_version_on)
|
|
self.cache_version_on.set(
|
|
get_cache_key('a', self.build_container_name('c')),
|
|
{'status': 200})
|
|
|
|
self.cache_version_on_but_busted = FakeMemcache()
|
|
self.cache_version_on_but_busted.set(get_cache_key('a'),
|
|
{'status': 200})
|
|
self.cache_version_on_but_busted.set(get_cache_key('a', 'c'),
|
|
cont_cache_version_on)
|
|
self.cache_version_on_but_busted.set(
|
|
get_cache_key('a', self.build_container_name('c')),
|
|
{'status': 404})
|
|
|
|
cont_cache_version_off = {'sysmeta': {
|
|
'versions-container': self.build_container_name('c'),
|
|
'versions-enabled': 'false'}}
|
|
self.cache_version_off = FakeMemcache()
|
|
self.cache_version_off.set(get_cache_key('a'), {'status': 200})
|
|
self.cache_version_off.set(get_cache_key('a', 'c'),
|
|
cont_cache_version_off)
|
|
self.cache_version_off.set(
|
|
get_cache_key('a', self.build_container_name('c')),
|
|
{'status': 200})
|
|
|
|
self.cache_version_never_on = FakeMemcache()
|
|
self.cache_version_never_on.set(get_cache_key('a'), {'status': 200})
|
|
self.cache_version_never_on.set(get_cache_key('a', 'c'),
|
|
{'status': 200})
|
|
self.expected_unread_requests = {}
|
|
|
|
def tearDown(self):
|
|
self.assertEqual(self.app.unclosed_requests, {})
|
|
self.assertEqual(self.app.unread_requests,
|
|
self.expected_unread_requests)
|
|
|
|
def call_ov(self, req):
|
|
# authorized gets reset everytime
|
|
self.authorized = []
|
|
|
|
def authorize(req):
|
|
self.authorized.append(req)
|
|
|
|
if 'swift.authorize' not in req.environ:
|
|
req.environ['swift.authorize'] = authorize
|
|
|
|
req.headers.setdefault("User-Agent", "Marula Kruger")
|
|
|
|
status = [None]
|
|
headers = [None]
|
|
|
|
def start_response(s, h, ei=None):
|
|
status[0] = s
|
|
headers[0] = h
|
|
|
|
body_iter = self.lf(req.environ, start_response)
|
|
with utils.closing_if_possible(body_iter):
|
|
body = b''.join(body_iter)
|
|
|
|
return status[0], headers[0], body
|
|
|
|
def assertRequestEqual(self, req, other):
|
|
self.assertEqual(req.method, other.method)
|
|
self.assertEqual(req.path, other.path)
|
|
|
|
def str_to_wsgi(self, native_str):
|
|
if six.PY2 and isinstance(native_str, six.text_type):
|
|
native_str = native_str.encode('utf8')
|
|
return str_to_wsgi(native_str)
|
|
|
|
def build_container_name(self, cont):
|
|
return get_reserved_name('versions', cont)
|
|
|
|
def build_object_name(self, obj, version):
|
|
return get_reserved_name(obj, version)
|
|
|
|
def build_symlink_path(self, cont, obj, version):
|
|
cont = self.build_container_name(cont)
|
|
obj = self.build_object_name(obj, version)
|
|
return wsgi_quote(self.str_to_wsgi("%s/%s" % (cont, obj)))
|
|
|
|
def build_versions_path(self, acc='a', cont='c', obj=None, version=None):
|
|
cont = self.build_container_name(cont)
|
|
if not obj:
|
|
return self.str_to_wsgi("/v1/%s/%s" % (acc, cont))
|
|
obj = self.build_object_name(obj, version)
|
|
return self.str_to_wsgi("/v1/%s/%s/%s" % (acc, cont, obj))
|
|
|
|
|
|
class ObjectVersioningTestCase(ObjectVersioningBaseTestCase):
|
|
|
|
def test_put_container(self):
|
|
self.app.register('HEAD', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register('HEAD', '/v1/a/c', swob.HTTPOk, {}, '')
|
|
self.app.register('PUT', self.build_versions_path(), swob.HTTPOk, {},
|
|
'passed')
|
|
self.app.register('PUT', '/v1/a/c', swob.HTTPAccepted, {}, 'passed')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true'},
|
|
environ={'REQUEST_METHOD': 'PUT'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
|
|
# check for sysmeta header
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(4, len(calls))
|
|
method, path, headers = calls[3]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c', path)
|
|
self.assertIn(SYSMETA_VERSIONS_CONT, headers)
|
|
self.assertEqual(headers[SYSMETA_VERSIONS_CONT],
|
|
wsgi_quote(self.str_to_wsgi(
|
|
self.build_container_name('c'))))
|
|
self.assertIn(SYSMETA_VERSIONS_ENABLED, headers)
|
|
self.assertEqual(headers[SYSMETA_VERSIONS_ENABLED], 'True')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
@patch_policies([StoragePolicy(0, 'zero', True),
|
|
StoragePolicy(1, 'one', False)])
|
|
def test_same_policy_as_existing_container(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register('GET', '/v1/a/c', swob.HTTPOk, {
|
|
'x-backend-storage-policy-index': 1}, '')
|
|
self.app.register('PUT', self.build_versions_path(), swob.HTTPOk, {},
|
|
'passed')
|
|
self.app.register('POST', '/v1/a/c', swob.HTTPNoContent, {}, '')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true'},
|
|
environ={'REQUEST_METHOD': 'POST'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
|
|
# check for sysmeta header
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(4, len(calls))
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
# request to create versions container
|
|
method, path, headers = calls[2]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual(self.build_versions_path(), path)
|
|
self.assertIn('X-Storage-Policy', headers)
|
|
self.assertEqual('one', headers['X-Storage-Policy'])
|
|
|
|
# request to enable versioning on primary container
|
|
method, path, headers = calls[3]
|
|
self.assertEqual('POST', method)
|
|
self.assertEqual('/v1/a/c', path)
|
|
self.assertIn(SYSMETA_VERSIONS_CONT, headers)
|
|
self.assertEqual(headers[SYSMETA_VERSIONS_CONT],
|
|
wsgi_quote(self.str_to_wsgi(
|
|
self.build_container_name('c'))))
|
|
self.assertIn(SYSMETA_VERSIONS_ENABLED, headers)
|
|
self.assertEqual(headers[SYSMETA_VERSIONS_ENABLED], 'True')
|
|
|
|
@patch_policies([StoragePolicy(0, 'zero', True),
|
|
StoragePolicy(1, 'one', False, is_deprecated=True)])
|
|
def test_existing_container_has_deprecated_policy(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register('GET', '/v1/a/c', swob.HTTPOk, {
|
|
'x-backend-storage-policy-index': 1}, '')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true'},
|
|
environ={'REQUEST_METHOD': 'POST'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
self.assertEqual(body,
|
|
b'Cannot enable object versioning on a container '
|
|
b'that uses a deprecated storage policy.')
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(2, len(calls))
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
@patch_policies([StoragePolicy(0, 'zero', True),
|
|
StoragePolicy(1, 'one', False, is_deprecated=True)])
|
|
def test_existing_container_has_deprecated_policy_unauthed(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register('GET', '/v1/a/c', swob.HTTPOk, {
|
|
'x-backend-storage-policy-index': 1}, '')
|
|
|
|
def fake_authorize(req):
|
|
self.authorized.append(req)
|
|
return swob.HTTPForbidden()
|
|
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true'},
|
|
environ={'REQUEST_METHOD': 'POST',
|
|
'swift.authorize': fake_authorize})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '403 Forbidden')
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(2, len(calls))
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_same_policy_as_primary_container(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register('GET', '/v1/a/c', swob.HTTPNotFound, {}, '')
|
|
self.app.register('PUT', self.build_versions_path(), swob.HTTPOk,
|
|
{}, '')
|
|
self.app.register('PUT', '/v1/a/c', swob.HTTPOk, {}, '')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true',
|
|
'X-Storage-Policy': 'ec42'},
|
|
environ={'REQUEST_METHOD': 'PUT'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
|
|
# check for sysmeta header
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(4, len(calls))
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
# request to create versions container
|
|
method, path, headers = calls[2]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual(self.build_versions_path(), path)
|
|
self.assertIn('X-Storage-Policy', headers)
|
|
self.assertEqual('ec42', headers['X-Storage-Policy'])
|
|
|
|
# request to enable versioning on primary container
|
|
method, path, headers = calls[3]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c', path)
|
|
self.assertIn(SYSMETA_VERSIONS_CONT, headers)
|
|
self.assertEqual(headers[SYSMETA_VERSIONS_CONT],
|
|
wsgi_quote(self.str_to_wsgi(
|
|
self.build_container_name('c'))))
|
|
self.assertIn(SYSMETA_VERSIONS_ENABLED, headers)
|
|
self.assertEqual(headers[SYSMETA_VERSIONS_ENABLED], 'True')
|
|
self.assertIn('X-Storage-Policy', headers)
|
|
self.assertEqual('ec42', headers['X-Storage-Policy'])
|
|
|
|
def test_enable_versioning_failed_primary_container(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('GET', '/v1/a/c', swob.HTTPNotFound, {}, 'passed')
|
|
self.app.register('PUT', self.build_versions_path(),
|
|
swob.HTTPOk, {}, 'passed')
|
|
self.app.register('DELETE', self.build_versions_path(),
|
|
swob.HTTPNoContent, {}, '')
|
|
self.app.register('PUT', '/v1/a/c', swob.HTTPInternalServerError,
|
|
{}, '')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true'},
|
|
environ={'REQUEST_METHOD': 'PUT'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '500 Internal Error')
|
|
|
|
def test_enable_versioning_failed_versions_container(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register('GET', '/v1/a/c', swob.HTTPNotFound, {}, '')
|
|
self.app.register('PUT', self.build_versions_path(),
|
|
swob.HTTPInternalServerError, {}, '')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true'},
|
|
environ={'REQUEST_METHOD': 'PUT'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '500 Internal Error')
|
|
|
|
def test_enable_versioning_existing_container(self):
|
|
self.app.register('HEAD', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register('HEAD', self.build_versions_path(),
|
|
swob.HTTPOk, {}, '')
|
|
self.app.register('PUT', self.build_versions_path(),
|
|
swob.HTTPAccepted, {}, '')
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: False},
|
|
'passed')
|
|
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true'},
|
|
environ={'REQUEST_METHOD': 'POST'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
|
|
# check for sysmeta header
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(5, len(calls))
|
|
method, path, req_headers = calls[-1]
|
|
self.assertEqual('POST', method)
|
|
self.assertEqual('/v1/a/c', path)
|
|
self.assertIn(SYSMETA_VERSIONS_ENABLED, req_headers)
|
|
self.assertEqual(req_headers[SYSMETA_VERSIONS_ENABLED],
|
|
'True')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_put_container_with_legacy_versioning(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{'x-container-sysmeta-versions-location': 'ver_cont'},
|
|
'')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true'},
|
|
environ={'REQUEST_METHOD': 'POST'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
|
|
def test_put_container_with_super_legacy_versioning(self):
|
|
# x-versions-location was used before versioned writes
|
|
# was pulled out to middleware
|
|
self.app.register('HEAD', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register(
|
|
'HEAD', '/v1/a/c', swob.HTTPOk,
|
|
{'x-versions-location': 'ver_cont'},
|
|
'')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'true'},
|
|
environ={'REQUEST_METHOD': 'POST'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
|
|
def test_get_container(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, 'passed')
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True}, b'[]')
|
|
req = Request.blank(
|
|
'/v1/a/c',
|
|
environ={'REQUEST_METHOD': 'GET'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_get_reserved_container_passthrough(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('GET', '/v1/a/%s' % get_reserved_name('foo'),
|
|
swob.HTTPOk, {}, b'[]')
|
|
req = Request.blank('/v1/a/%s' % get_reserved_name('foo'))
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_head_container(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, 'passed')
|
|
self.app.register(
|
|
'HEAD', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True}, None)
|
|
req = Request.blank(
|
|
'/v1/a/c',
|
|
environ={'REQUEST_METHOD': 'HEAD'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_delete_container_success(self):
|
|
self.app.register(
|
|
'DELETE', '/v1/a/c', swob.HTTPNoContent, {}, '')
|
|
self.app.register(
|
|
'DELETE', self.build_versions_path(),
|
|
swob.HTTPNoContent, {}, '')
|
|
self.app.register('HEAD', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register(
|
|
'HEAD', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True}, '')
|
|
self.app.register(
|
|
'HEAD', self.build_versions_path(), swob.HTTPOk,
|
|
{'x-container-object-count': 0}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c', environ={'REQUEST_METHOD': 'DELETE'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', '/v1/a'),
|
|
('HEAD', '/v1/a/c'),
|
|
('HEAD', self.build_versions_path()),
|
|
('HEAD', self.build_versions_path()), # get_container_info
|
|
('DELETE', self.build_versions_path()),
|
|
('DELETE', '/v1/a/c'),
|
|
])
|
|
|
|
def test_delete_container_fail_object_count(self):
|
|
self.app.register('HEAD', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register(
|
|
'HEAD', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: False}, '')
|
|
self.app.register(
|
|
'HEAD',
|
|
self.build_versions_path(),
|
|
swob.HTTPOk,
|
|
{'x-container-object-count': 1}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c', environ={'REQUEST_METHOD': 'DELETE'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '409 Conflict')
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', '/v1/a'),
|
|
('HEAD', '/v1/a/c'),
|
|
('HEAD', self.build_versions_path()),
|
|
('HEAD', self.build_versions_path()), # get_container_info
|
|
])
|
|
|
|
def test_delete_container_fail_delete_versions_cont(self):
|
|
# N.B.: Notice lack of a call to DELETE /v1/a/c
|
|
# Since deleting versions container failed, swift should
|
|
# not delete primary container
|
|
self.app.register(
|
|
'DELETE', self.build_versions_path(),
|
|
swob.HTTPServerError, {}, '')
|
|
self.app.register('HEAD', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register(
|
|
'HEAD', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: False}, '')
|
|
self.app.register(
|
|
'HEAD', self.build_versions_path(), swob.HTTPOk,
|
|
{'x-container-object-count': 0}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c', environ={'REQUEST_METHOD': 'DELETE'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '500 Internal Error')
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', '/v1/a'),
|
|
('HEAD', '/v1/a/c'),
|
|
('HEAD', self.build_versions_path()),
|
|
('HEAD', self.build_versions_path()), # get_container_info
|
|
('DELETE', self.build_versions_path()),
|
|
])
|
|
|
|
def test_get(self):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk, {
|
|
'Content-Location': self.build_versions_path(
|
|
obj='o', version='9999998765.99999')},
|
|
'body')
|
|
req = Request.blank(
|
|
'/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertIn(('X-Object-Version-Id', '0000001234.00000'), headers)
|
|
self.assertIn(
|
|
('Content-Location', '/v1/a/c/o?version-id=0000001234.00000'),
|
|
headers)
|
|
|
|
def test_get_symlink(self):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o?symlink=get', swob.HTTPOk, {
|
|
'X-Symlink-Target': '%s/%s' % (
|
|
self.build_container_name('c'),
|
|
self.build_object_name('o', '9999998765.99999'),
|
|
),
|
|
'X-Symlink-Target-Etag': 'versioned-obj-etag',
|
|
}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o?symlink=get',
|
|
environ={'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertIn(('X-Object-Version-Id', '0000001234.00000'), headers)
|
|
self.assertIn(
|
|
('X-Symlink-Target', 'c/o?version-id=0000001234.00000'),
|
|
headers)
|
|
self.assertEqual(body, b'')
|
|
# N.B. HEAD req already works with existing registered GET response
|
|
req = Request.blank(
|
|
'/v1/a/c/o?symlink=get', method='HEAD',
|
|
environ={'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertIn(('X-Object-Version-Id', '0000001234.00000'), headers)
|
|
self.assertIn(
|
|
('X-Symlink-Target', 'c/o?version-id=0000001234.00000'),
|
|
headers)
|
|
self.assertEqual(body, b'')
|
|
|
|
def test_put_object_no_versioning(self):
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
cache = FakeMemcache()
|
|
cache.set(get_cache_key('a'), {'status': 200})
|
|
cache.set(get_cache_key('a', 'c'), {'status': 200})
|
|
req = Request.blank(
|
|
'/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'PUT', 'swift.cache': cache,
|
|
'CONTENT_LENGTH': '100'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_PUT_overwrite(self, mock_time):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR: 'c-unique/whatever'}, '')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
put_body = 'stuff' * 100
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT', body=put_body,
|
|
headers={'Content-Type': 'text/plain',
|
|
'ETag': md5(
|
|
put_body.encode('utf8'),
|
|
usedforsecurity=False).hexdigest(),
|
|
'Content-Length': len(put_body)},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual(['OV', 'OV', 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT', self.build_versions_path(
|
|
obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertIn('X-Newest', calls[0].headers)
|
|
self.assertEqual('True', calls[0].headers['X-Newest'])
|
|
|
|
symlink_expected_headers = {
|
|
SYMLOOP_EXTEND: 'true',
|
|
ALLOW_RESERVED_NAMES: 'true',
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
put_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(put_body)),
|
|
}
|
|
symlink_put_headers = self.app._calls[-1].headers
|
|
for k, v in symlink_expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
|
|
def test_POST(self):
|
|
self.app.register(
|
|
'POST',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPAccepted, {}, '')
|
|
self.app.register(
|
|
'POST', '/v1/a/c/o', swob.HTTPTemporaryRedirect, {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
'Location': self.build_versions_path(
|
|
obj='o', version='9999998765.99999')}, '')
|
|
|
|
# TODO: in symlink middleware, swift.leave_relative_location
|
|
# is added by the middleware during the response
|
|
# adding to the client request here, need to understand how
|
|
# to modify the response environ.
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='POST',
|
|
headers={'Content-Type': 'text/jibberish01',
|
|
'X-Object-Meta-Foo': 'bar'},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.leave_relative_location': 'true',
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual([None, 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
self.assertEqual(self.app.calls, [
|
|
('POST', '/v1/a/c/o'),
|
|
('POST', self.build_versions_path(
|
|
obj='o', version='9999998765.99999')),
|
|
])
|
|
|
|
expected_hdrs = {
|
|
'content-type': 'text/jibberish01',
|
|
'x-object-meta-foo': 'bar',
|
|
}
|
|
version_obj_post_headers = self.app._calls[1].headers
|
|
for k, v in expected_hdrs.items():
|
|
self.assertEqual(version_obj_post_headers[k], v)
|
|
|
|
def test_POST_mismatched_location(self):
|
|
# This is a defensive chech, ideally a mistmached
|
|
# versions container should never happen.
|
|
self.app.register(
|
|
'POST', '/v1/a/c/o', swob.HTTPTemporaryRedirect, {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
'Location': self.build_versions_path(
|
|
cont='mismatched', obj='o', version='9999998765.99999')},
|
|
'')
|
|
|
|
# TODO: in symlink middleware, swift.leave_relative_location
|
|
# is added by the middleware during the response
|
|
# adding to the client request here, need to understand how
|
|
# to modify the response environ.
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='POST',
|
|
headers={'Content-Type': 'text/jibberish01',
|
|
'X-Object-Meta-Foo': 'bar'},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.leave_relative_location': 'true',
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '307 Temporary Redirect')
|
|
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual([None], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
self.assertEqual(self.app.calls, [
|
|
('POST', '/v1/a/c/o'),
|
|
])
|
|
|
|
def test_POST_regular_symlink(self):
|
|
self.app.register(
|
|
'POST', '/v1/a/c/o', swob.HTTPTemporaryRedirect, {
|
|
'Location': '/v1/a/t/o'}, '')
|
|
|
|
# TODO: in symlink middleware, swift.leave_relative_location
|
|
# is added by the middleware during the response
|
|
# adding to the client request here, need to understand how
|
|
# to modify the response environ.
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='POST',
|
|
headers={'Content-Type': 'text/jibberish01',
|
|
'X-Object-Meta-Foo': 'bar'},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.leave_relative_location': 'true',
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '307 Temporary Redirect')
|
|
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual([None], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
self.assertEqual(self.app.calls, [
|
|
('POST', '/v1/a/c/o'),
|
|
])
|
|
|
|
def test_denied_PUT_of_versioned_object(self):
|
|
authorize_call = []
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'last-modified': 'Thu, 1 Jan 1970 00:00:01 GMT'}, 'passed')
|
|
|
|
def fake_authorize(req):
|
|
# we should deny the object PUT
|
|
authorize_call.append(req)
|
|
return swob.HTTPForbidden()
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'PUT',
|
|
'swift.cache': self.cache_version_on,
|
|
'swift.authorize': fake_authorize,
|
|
'CONTENT_LENGTH': '0'})
|
|
# Save off a copy, as the middleware may modify the original
|
|
expected_req = Request(req.environ.copy())
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '403 Forbidden')
|
|
self.assertEqual(len(authorize_call), 1)
|
|
self.assertRequestEqual(expected_req, authorize_call[0])
|
|
|
|
self.assertEqual(self.app.calls, [])
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_PUT_overwrite_tombstone(self, mock_time):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPNotFound, {}, None)
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
put_body = 'stuff' * 100
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT', body=put_body,
|
|
headers={'Content-Type': 'text/plain',
|
|
'ETag': md5(
|
|
put_body.encode('utf8'),
|
|
usedforsecurity=False).hexdigest(),
|
|
'Content-Length': len(put_body)},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
# authorized twice because of pre-flight check on PUT
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual(['OV', 'OV', 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT', self.build_versions_path(
|
|
obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertIn('X-Newest', calls[0].headers)
|
|
self.assertEqual('True', calls[0].headers['X-Newest'])
|
|
|
|
expected_headers = {
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
put_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(put_body)),
|
|
}
|
|
symlink_put_headers = self.app._calls[-1].headers
|
|
for k, v in expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_PUT_overwrite_object_with_DLO(self, mock_time):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'last-modified': 'Thu, 1 Jan 1970 00:01:00 GMT'}, 'old version')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
put_body = ''
|
|
req = Request.blank('/v1/a/c/o', method='PUT', body=put_body,
|
|
headers={'Content-Type': 'text/plain',
|
|
'X-Object-Manifest': 'req/manifest'},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual(4, self.app.call_count)
|
|
self.assertEqual(['OV', 'OV', 'OV', 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
|
|
self.assertEqual([
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999')),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
], self.app.calls)
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertIn('X-Newest', calls[0].headers)
|
|
self.assertEqual('True', calls[0].headers['X-Newest'])
|
|
|
|
self.assertNotIn('x-object-manifest', calls[1].headers)
|
|
self.assertEqual('req/manifest',
|
|
calls[-2].headers['X-Object-Manifest'])
|
|
|
|
symlink_put_headers = calls[-1].headers
|
|
expected_headers = {
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
put_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(put_body)),
|
|
}
|
|
for k, v in expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
self.assertNotIn('x-object-manifest', symlink_put_headers)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_PUT_overwrite_DLO_with_object(self, mock_time):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'X-Object-Manifest': 'resp/manifest',
|
|
'last-modified': 'Thu, 1 Jan 1970 00:01:00 GMT'},
|
|
'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
put_body = 'stuff' * 100
|
|
req = Request.blank('/v1/a/c/o', method='PUT', body=put_body,
|
|
headers={'Content-Type': 'text/plain'},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual(4, self.app.call_count)
|
|
self.assertEqual(['OV', 'OV', 'OV', 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
self.assertEqual([
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999')),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
], self.app.calls)
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertIn('X-Newest', calls[0].headers)
|
|
self.assertEqual('True', calls[0].headers['X-Newest'])
|
|
|
|
self.assertEqual('resp/manifest',
|
|
calls[1].headers['X-Object-Manifest'])
|
|
self.assertNotIn(TGT_OBJ_SYSMETA_SYMLINK_HDR,
|
|
calls[1].headers)
|
|
|
|
self.assertNotIn('x-object-manifest', calls[2].headers)
|
|
self.assertNotIn(TGT_OBJ_SYSMETA_SYMLINK_HDR,
|
|
calls[2].headers)
|
|
|
|
symlink_put_headers = calls[-1].headers
|
|
expected_headers = {
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
put_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(put_body)),
|
|
}
|
|
for k, v in expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
self.assertNotIn('x-object-manifest', symlink_put_headers)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_PUT_overwrite_SLO_with_object(self, mock_time):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {
|
|
'X-Static-Large-Object': 'True',
|
|
# N.B. object-sever strips swift_bytes
|
|
'Content-Type': 'application/octet-stream',
|
|
'X-Object-Sysmeta-Container-Update-Override-Etag':
|
|
'656516af0f7474b857857dd2a327f3b9; '
|
|
'slo_etag=71e938d37c1d06dc634dd24660255a88',
|
|
'X-Object-Sysmeta-Slo-Etag': '71e938d37c1d06dc634dd24660255a88',
|
|
'X-Object-Sysmeta-Slo-Size': '10485760',
|
|
'last-modified': 'Thu, 1 Jan 1970 00:01:00 GMT',
|
|
}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
put_body = 'stuff' * 100
|
|
req = Request.blank('/v1/a/c/o', method='PUT', body=put_body,
|
|
headers={'Content-Type': 'text/plain'},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual(4, self.app.call_count)
|
|
self.assertEqual(['OV', 'OV', 'OV', 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
self.assertEqual([
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999')),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
], self.app.calls)
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertIn('X-Newest', calls[0].headers)
|
|
self.assertEqual('True', calls[0].headers['X-Newest'])
|
|
|
|
slo_headers = {
|
|
'X-Static-Large-Object': 'True',
|
|
'Content-Type': 'application/octet-stream; swift_bytes=10485760',
|
|
'X-Object-Sysmeta-Container-Update-Override-Etag':
|
|
'656516af0f7474b857857dd2a327f3b9; '
|
|
'slo_etag=71e938d37c1d06dc634dd24660255a88',
|
|
'X-Object-Sysmeta-Slo-Etag': '71e938d37c1d06dc634dd24660255a88',
|
|
'X-Object-Sysmeta-Slo-Size': '10485760',
|
|
}
|
|
archive_put = calls[1]
|
|
for key, value in slo_headers.items():
|
|
self.assertEqual(archive_put.headers[key], value)
|
|
|
|
client_put = calls[2]
|
|
for key in slo_headers:
|
|
if key == 'Content-Type':
|
|
self.assertEqual('text/plain', client_put.headers[key])
|
|
else:
|
|
self.assertNotIn(key, client_put.headers)
|
|
|
|
symlink_put_headers = calls[-1].headers
|
|
expected_headers = {
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
put_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(put_body)),
|
|
}
|
|
for k, v in expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
self.assertNotIn('x-object-manifest', symlink_put_headers)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_PUT_overwrite_object(self, mock_time):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'last-modified': 'Thu, 1 Jan 1970 00:01:00 GMT'}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
|
|
put_body = 'stuff' * 100
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT', body=put_body,
|
|
headers={'Content-Type': 'text/plain',
|
|
'ETag': md5(
|
|
put_body.encode('utf8'),
|
|
usedforsecurity=False).hexdigest(),
|
|
'Content-Length': len(put_body)},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
# authorized twice because of pre-flight check on PUT
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual(['OV', 'OV', 'OV', 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999')),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertIn('X-Newest', calls[0].headers)
|
|
self.assertEqual('True', calls[0].headers['X-Newest'])
|
|
|
|
expected_headers = {
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
put_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(put_body)),
|
|
}
|
|
symlink_put_headers = self.app._calls[-1].headers
|
|
for k, v in expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
|
|
def test_new_version_get_errors(self):
|
|
# GET on source fails, expect client error response,
|
|
# no PUT should happen
|
|
self.app.register('GET', '/v1/a/c/o',
|
|
swob.HTTPBadRequest, {}, None)
|
|
req = Request.blank('/v1/a/c/o', method='PUT',
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '100'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
self.assertEqual(1, self.app.call_count)
|
|
|
|
# GET on source fails, expect server error response
|
|
self.app.register('GET', '/v1/a/c/o',
|
|
swob.HTTPBadGateway, {}, None)
|
|
req = Request.blank('/v1/a/c/o', method='PUT',
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '100'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '503 Service Unavailable')
|
|
self.assertEqual(2, self.app.call_count)
|
|
|
|
def test_new_version_put_errors(self):
|
|
# PUT of version fails, expect client error response
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'last-modified': 'Thu, 1 Jan 1970 00:01:00 GMT'}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPUnauthorized, {}, None)
|
|
req = Request.blank('/v1/a/c/o', method='PUT',
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '100'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '401 Unauthorized')
|
|
self.assertEqual(2, self.app.call_count)
|
|
|
|
# PUT of version fails, expect server error response
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPBadGateway, {}, None)
|
|
req = Request.blank(
|
|
'/v1/a/c/o', headers={'Content-Type': 'text/plain'},
|
|
environ={'REQUEST_METHOD': 'PUT',
|
|
'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '100'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '503 Service Unavailable')
|
|
self.assertEqual(4, self.app.call_count)
|
|
|
|
# PUT fails because the reserved container is missing; server error
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPNotFound, {}, None)
|
|
req = Request.blank(
|
|
'/v1/a/c/o', headers={'Content-Type': 'text/plain'},
|
|
environ={'REQUEST_METHOD': 'PUT',
|
|
'swift.cache': self.cache_version_on_but_busted,
|
|
'CONTENT_LENGTH': '100'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '500 Internal Error')
|
|
self.assertIn(b'container does not exist', body)
|
|
self.assertIn(b're-enable object versioning', body)
|
|
|
|
|
|
class ObjectVersioningTestDisabled(ObjectVersioningBaseTestCase):
|
|
def test_get_container(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, 'passed')
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: 'c\x01versions',
|
|
SYSMETA_VERSIONS_ENABLED: False}, b'[]')
|
|
req = Request.blank(
|
|
'/v1/a/c',
|
|
environ={'REQUEST_METHOD': 'GET'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'False'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_head_container(self):
|
|
self.app.register('GET', '/v1/a', swob.HTTPOk, {}, 'passed')
|
|
self.app.register(
|
|
'HEAD', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: 'c\x01versions',
|
|
SYSMETA_VERSIONS_ENABLED: False}, None)
|
|
req = Request.blank(
|
|
'/v1/a/c',
|
|
environ={'REQUEST_METHOD': 'HEAD'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'False'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_disable_versioning(self):
|
|
self.app.register('POST', '/v1/a/c', swob.HTTPOk, {}, 'passed')
|
|
req = Request.blank('/v1/a/c',
|
|
headers={'X-Versions-Enabled': 'false'},
|
|
environ={'REQUEST_METHOD': 'POST',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_PUT_overwrite_null_marker_versioning_disabled(self, mock_time):
|
|
# During object PUT with a versioning disabled, if the most
|
|
# recent versioned object is a DELETE marker will a *null*
|
|
# version-id, then the DELETE marker should be removed.
|
|
listing_body = [{
|
|
"hash": "y",
|
|
"last_modified": "2014-11-21T14:23:02.206740",
|
|
"bytes": 0,
|
|
"name": self.build_object_name('o', '0000000001.00000'),
|
|
"content_type": "application/x-deleted;swift_versions_deleted=1"
|
|
}, {
|
|
"hash": "x",
|
|
"last_modified": "2014-11-21T14:14:27.409100",
|
|
"bytes": 3,
|
|
"name": self.build_object_name('o', '0000000002.00000'),
|
|
"content_type": "text/plain"
|
|
}]
|
|
prefix_listing_path = \
|
|
'/v1/a/c\x01versions?prefix=o---&marker='
|
|
self.app.register(
|
|
'GET', prefix_listing_path, swob.HTTPOk, {},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
self.app.register(
|
|
'HEAD',
|
|
self.build_versions_path(obj='o', version='0000000001.00000'),
|
|
swob.HTTPNoContent,
|
|
{'content-type': DELETE_MARKER_CONTENT_TYPE}, None)
|
|
self.app.register(
|
|
'DELETE',
|
|
self.build_versions_path(obj='o', version='0000000001.00000'),
|
|
swob.HTTPNoContent, {}, None)
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
put_body = 'stuff' * 100
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT', body=put_body,
|
|
headers={'Content-Type': 'text/plain',
|
|
'ETag': md5(
|
|
put_body.encode('utf8'),
|
|
usedforsecurity=False).hexdigest(),
|
|
'Content-Length': len(put_body)},
|
|
environ={'swift.cache': self.cache_version_off,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
|
|
# authorized twice because of pre-flight check on PUT
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
# TODO self.assertEqual(['OV', None, 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
|
|
obj_put_headers = self.app.calls_with_headers[-1].headers
|
|
self.assertNotIn(SYSMETA_VERSIONS_SYMLINK, obj_put_headers)
|
|
|
|
def test_put_object_versioning_disabled(self):
|
|
listing_body = [{
|
|
"hash": "x",
|
|
"last_modified": "2014-11-21T14:14:27.409100",
|
|
"bytes": 3,
|
|
"name": self.build_object_name('o', '0000000001.00000'),
|
|
"content_type": "text/plain"
|
|
}]
|
|
prefix_listing_path = \
|
|
'/v1/a/c\x01versions?prefix=o---&marker='
|
|
self.app.register(
|
|
'GET', prefix_listing_path, swob.HTTPOk, {},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
req = Request.blank(
|
|
'/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'PUT',
|
|
'swift.cache': self.cache_version_off,
|
|
'CONTENT_LENGTH': '100'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
obj_put_headers = self.app._calls[-1].headers
|
|
self.assertNotIn(SYSMETA_VERSIONS_SYMLINK, obj_put_headers)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_PUT_with_recent_versioned_marker_versioning_disabled(self,
|
|
mock_time):
|
|
# During object PUT with a versioning disabled, if the most
|
|
# recent versioned object is a DELETE marker will a non-null
|
|
# version-id, then the DELETE marker should not be removed.
|
|
listing_body = [{
|
|
"hash": "y",
|
|
"last_modified": "2014-11-21T14:23:02.206740",
|
|
"bytes": 0,
|
|
"name": self.build_object_name('o', '0000000001.00000'),
|
|
"content_type": "application/x-deleted;swift_versions_deleted=1"
|
|
}, {
|
|
"hash": "x",
|
|
"last_modified": "2014-11-21T14:14:27.409100",
|
|
"bytes": 3,
|
|
"name": self.build_object_name('o', '0000000002.00000'),
|
|
"content_type": "text/plain"
|
|
}]
|
|
prefix_listing_path = \
|
|
'/v1/a/c\x01versions?prefix=o---&marker='
|
|
self.app.register(
|
|
'GET', prefix_listing_path, swob.HTTPOk, {},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
self.app.register(
|
|
'HEAD',
|
|
self.build_versions_path(obj='o', version='0000000001.00000'),
|
|
swob.HTTPNoContent,
|
|
{'content-type': DELETE_MARKER_CONTENT_TYPE}, None)
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
put_body = 'stuff' * 100
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT', body=put_body,
|
|
headers={'Content-Type': 'text/plain',
|
|
'ETag': md5(
|
|
put_body.encode('utf8'),
|
|
usedforsecurity=False).hexdigest(),
|
|
'Content-Length': len(put_body)},
|
|
environ={'swift.cache': self.cache_version_off,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
|
|
# authorized twice because of pre-flight check on PUT
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
# TODO self.assertEqual(['OV', None, 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
|
|
obj_put_headers = self.app.calls_with_headers[-1].headers
|
|
self.assertNotIn(SYSMETA_VERSIONS_SYMLINK, obj_put_headers)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_delete_object_with_versioning_disabled(self, mock_time):
|
|
# When versioning is disabled, swift will simply issue the
|
|
# original request to the versioned container
|
|
self.app.register(
|
|
'DELETE', '/v1/a/c/o', swob.HTTPNoContent, {}, 'passed')
|
|
req = Request.blank(
|
|
'/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'DELETE',
|
|
'swift.cache': self.cache_version_off})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_POST_symlink(self):
|
|
self.app.register(
|
|
'POST',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPAccepted, {}, '')
|
|
self.app.register(
|
|
'POST', '/v1/a/c/o', swob.HTTPTemporaryRedirect, {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
'Location': self.build_versions_path(
|
|
obj='o', version='9999998765.99999')}, '')
|
|
|
|
# TODO: in symlink middleware, swift.leave_relative_location
|
|
# is added by the middleware during the response
|
|
# adding to the client request here, need to understand how
|
|
# to modify the response environ.
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='POST',
|
|
headers={'Content-Type': 'text/jibberish01',
|
|
'X-Object-Meta-Foo': 'bar'},
|
|
environ={'swift.cache': self.cache_version_off,
|
|
'swift.leave_relative_location': 'true',
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual([None, 'OV'], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
self.assertEqual(self.app.calls, [
|
|
('POST', '/v1/a/c/o'),
|
|
('POST',
|
|
self.build_versions_path(obj='o', version='9999998765.99999')),
|
|
])
|
|
|
|
expected_hdrs = {
|
|
'content-type': 'text/jibberish01',
|
|
'x-object-meta-foo': 'bar',
|
|
}
|
|
version_obj_post_headers = self.app._calls[1].headers
|
|
for k, v in expected_hdrs.items():
|
|
self.assertEqual(version_obj_post_headers[k], v)
|
|
|
|
def test_POST_unversioned_obj(self):
|
|
self.app.register(
|
|
'POST', '/v1/a/c/o', swob.HTTPAccepted, {}, '')
|
|
|
|
# TODO: in symlink middleware, swift.leave_relative_location
|
|
# is added by the middleware during the response
|
|
# adding to the client request here, need to understand how
|
|
# to modify the response environ.
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='POST',
|
|
headers={'Content-Type': 'text/jibberish01',
|
|
'X-Object-Meta-Foo': 'bar'},
|
|
environ={'swift.cache': self.cache_version_off,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual([None], self.app.swift_sources)
|
|
self.assertEqual({'fake_trans_id'}, set(self.app.txn_ids))
|
|
self.assertEqual(self.app.calls, [
|
|
('POST', '/v1/a/c/o'),
|
|
])
|
|
|
|
expected_hdrs = {
|
|
'content-type': 'text/jibberish01',
|
|
'x-object-meta-foo': 'bar',
|
|
}
|
|
version_obj_post_headers = self.app._calls[0].headers
|
|
for k, v in expected_hdrs.items():
|
|
self.assertEqual(version_obj_post_headers[k], v)
|
|
|
|
|
|
class ObjectVersioningTestDelete(ObjectVersioningBaseTestCase):
|
|
def test_delete_object_with_versioning_never_enabled(self):
|
|
# should be a straight DELETE, versioning middleware
|
|
# does not get involved.
|
|
self.app.register(
|
|
'DELETE', '/v1/a/c/o', swob.HTTPNoContent, {}, 'passed')
|
|
cache = FakeMemcache()
|
|
cache.set(get_cache_key('a'), {'status': 200})
|
|
cache.set(get_cache_key('a', 'c'), {'status': 200})
|
|
req = Request.blank(
|
|
'/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'DELETE',
|
|
'swift.cache': cache})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
called_method = \
|
|
[method for (method, path, rheaders) in self.app._calls]
|
|
self.assertNotIn('PUT', called_method)
|
|
self.assertNotIn('GET', called_method)
|
|
self.assertEqual(1, self.app.call_count)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_put_delete_marker_no_object_success(self, mock_time):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPNotFound,
|
|
{}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'DELETE', '/v1/a/c/o', swob.HTTPNotFound, {}, None)
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'DELETE',
|
|
'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '0'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertEqual(len(self.authorized), 2)
|
|
|
|
req.environ['REQUEST_METHOD'] = 'PUT'
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(['GET', 'PUT', 'DELETE'], [c.method for c in calls])
|
|
self.assertEqual('application/x-deleted;swift_versions_deleted=1',
|
|
calls[1].headers.get('Content-Type'))
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_delete_marker_over_object_success(self, mock_time):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'last-modified': 'Thu, 1 Jan 1970 00:01:00 GMT'}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'DELETE', '/v1/a/c/o', swob.HTTPNoContent, {}, None)
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'DELETE',
|
|
'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '0'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual(b'', body)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
|
|
req.environ['REQUEST_METHOD'] = 'PUT'
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(['GET', 'PUT', 'PUT', 'DELETE'],
|
|
[c.method for c in calls])
|
|
self.assertEqual(
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
calls[1].path)
|
|
self.assertEqual('application/x-deleted;swift_versions_deleted=1',
|
|
calls[2].headers.get('Content-Type'))
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_delete_marker_over_versioned_object_success(self, mock_time):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_SYMLINK: 'true'}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'DELETE', '/v1/a/c/o', swob.HTTPNoContent, {}, None)
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'DELETE',
|
|
'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '0'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual(b'', body)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
|
|
req.environ['REQUEST_METHOD'] = 'PUT'
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(['GET', 'PUT', 'DELETE'],
|
|
[c.method for c in calls])
|
|
self.assertEqual(
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
calls[1].path)
|
|
self.assertEqual('application/x-deleted;swift_versions_deleted=1',
|
|
calls[1].headers.get('Content-Type'))
|
|
|
|
def test_denied_DELETE_of_versioned_object(self):
|
|
authorize_call = []
|
|
|
|
def fake_authorize(req):
|
|
authorize_call.append((req.method, req.path))
|
|
return swob.HTTPForbidden()
|
|
|
|
req = Request.blank('/v1/a/c/o', method='DELETE', body='',
|
|
headers={'X-If-Delete-At': 1},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.authorize': fake_authorize,
|
|
'swift.trans_id': 'fake_trans_id'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '403 Forbidden')
|
|
self.assertEqual(len(authorize_call), 1)
|
|
self.assertEqual(('DELETE', '/v1/a/c/o'), authorize_call[0])
|
|
|
|
|
|
class ObjectVersioningTestCopy(ObjectVersioningBaseTestCase):
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_COPY_overwrite_tombstone(self, mock_time):
|
|
self.cache_version_on.set(get_cache_key('a', 'src_cont'),
|
|
{'status': 200})
|
|
src_body = 'stuff' * 100
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPNotFound, {}, None)
|
|
self.app.register(
|
|
'GET', '/v1/a/src_cont/src_obj', swob.HTTPOk, {}, src_body)
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
req = Request.blank(
|
|
'/v1/a/src_cont/src_obj',
|
|
environ={'REQUEST_METHOD': 'COPY',
|
|
'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '100'},
|
|
headers={'Destination': 'c/o'})
|
|
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(len(self.authorized), 3)
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/src_cont/src_obj'),
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
|
|
expected_headers = {
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
src_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(src_body)),
|
|
}
|
|
symlink_put_headers = self.app._calls[-1].headers
|
|
for k, v in expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_COPY_overwrite_object(self, mock_time):
|
|
self.cache_version_on.set(get_cache_key('a', 'src_cont'),
|
|
{'status': 200})
|
|
src_body = 'stuff' * 100
|
|
self.app.register(
|
|
'GET', '/v1/a/src_cont/src_obj', swob.HTTPOk, {}, src_body)
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'last-modified': 'Thu, 1 Jan 1970 00:01:00 GMT'}, 'old object')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
req = Request.blank(
|
|
'/v1/a/src_cont/src_obj',
|
|
environ={'REQUEST_METHOD': 'COPY',
|
|
'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '100'},
|
|
headers={'Destination': 'c/o'})
|
|
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(len(self.authorized), 3)
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/src_cont/src_obj'),
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999999939.99999')),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
|
|
expected_headers = {
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
src_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(src_body)),
|
|
}
|
|
symlink_put_headers = self.app._calls[-1].headers
|
|
for k, v in expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_COPY_overwrite_version_symlink(self, mock_time):
|
|
self.cache_version_on.set(get_cache_key('a', 'src_cont'),
|
|
{'status': 200})
|
|
src_body = 'stuff' * 100
|
|
self.app.register(
|
|
'GET', '/v1/a/src_cont/src_obj', swob.HTTPOk, {}, src_body)
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR: 'c-unique/whatever'}, '')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
req = Request.blank(
|
|
'/v1/a/src_cont/src_obj',
|
|
environ={'REQUEST_METHOD': 'COPY',
|
|
'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '100'},
|
|
headers={'Destination': 'c/o'})
|
|
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(len(self.authorized), 3)
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/src_cont/src_obj'),
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
|
|
expected_headers = {
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
src_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(src_body)),
|
|
}
|
|
symlink_put_headers = self.app._calls[-1].headers
|
|
for k, v in expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
|
|
@mock.patch('swift.common.middleware.versioned_writes.object_versioning.'
|
|
'time.time', return_value=1234)
|
|
def test_copy_new_version_different_account(self, mock_time):
|
|
self.cache_version_on.set(get_cache_key('src_acc'),
|
|
{'status': 200})
|
|
self.cache_version_on.set(get_cache_key('src_acc', 'src_cont'),
|
|
{'status': 200})
|
|
src_body = 'stuff' * 100
|
|
self.app.register(
|
|
'GET', '/v1/src_acc/src_cont/src_obj', swob.HTTPOk, {}, src_body)
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR: 'c-unique/whatever'}, '')
|
|
self.app.register(
|
|
'PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999'),
|
|
swob.HTTPCreated, {}, 'passed')
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPCreated, {}, 'passed')
|
|
req = Request.blank(
|
|
'/v1/src_acc/src_cont/src_obj',
|
|
environ={'REQUEST_METHOD': 'COPY',
|
|
'swift.cache': self.cache_version_on,
|
|
'CONTENT_LENGTH': '100'},
|
|
headers={'Destination': 'c/o',
|
|
'Destination-Account': 'a'})
|
|
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(len(self.authorized), 3)
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/src_acc/src_cont/src_obj'),
|
|
('GET', '/v1/a/c/o?symlink=get'),
|
|
('PUT',
|
|
self.build_versions_path(obj='o', version='9999998765.99999')),
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
|
|
expected_headers = {
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR:
|
|
self.build_symlink_path('c', 'o', '9999998765.99999'),
|
|
'x-object-sysmeta-symlink-target-etag': md5(
|
|
src_body.encode('utf8'), usedforsecurity=False).hexdigest(),
|
|
'x-object-sysmeta-symlink-target-bytes': str(len(src_body)),
|
|
}
|
|
symlink_put_headers = self.app._calls[-1].headers
|
|
for k, v in expected_headers.items():
|
|
self.assertEqual(symlink_put_headers[k], v)
|
|
|
|
def test_copy_object_versioning_disabled(self):
|
|
self.cache_version_off.set(get_cache_key('a', 'src_cont'),
|
|
{'status': 200})
|
|
listing_body = [{
|
|
"hash": "x",
|
|
"last_modified": "2014-11-21T14:14:27.409100",
|
|
"bytes": 3,
|
|
"name": self.build_object_name('o', '0000000001.00000'),
|
|
"content_type": "text/plain"
|
|
}]
|
|
prefix_listing_path = \
|
|
'/v1/a/c\x01versions?prefix=o---&marker='
|
|
self.app.register(
|
|
'GET', prefix_listing_path, swob.HTTPOk, {},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
src_body = 'stuff' * 100
|
|
self.app.register(
|
|
'GET', '/v1/a/src_cont/src_obj', swob.HTTPOk, {}, src_body)
|
|
self.app.register(
|
|
'PUT', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
req = Request.blank(
|
|
'/v1/a/src_cont/src_obj',
|
|
environ={'REQUEST_METHOD': 'COPY',
|
|
'swift.cache': self.cache_version_off,
|
|
'CONTENT_LENGTH': '100'},
|
|
headers={'Destination': 'c/o'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 2)
|
|
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/src_cont/src_obj'),
|
|
('PUT', '/v1/a/c/o'),
|
|
])
|
|
obj_put_headers = self.app._calls[-1].headers
|
|
self.assertNotIn(SYSMETA_VERSIONS_SYMLINK, obj_put_headers)
|
|
|
|
|
|
class ObjectVersioningTestVersionAPI(ObjectVersioningBaseTestCase):
|
|
|
|
def test_fail_non_versioned_container(self):
|
|
self.app.register('HEAD', '/v1/a', swob.HTTPOk, {}, '')
|
|
self.app.register('HEAD', '/v1/a/c', swob.HTTPOk, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='GET',
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
self.assertEqual(body, b'version-aware operations require'
|
|
b' that the container is versioned')
|
|
|
|
def test_PUT_version(self):
|
|
timestamp = next(self.ts)
|
|
version_path = '%s?symlink=get' % self.build_versions_path(
|
|
obj='o', version=(~timestamp).normal)
|
|
etag = md5(b'old-version-etag', usedforsecurity=False).hexdigest()
|
|
self.app.register('HEAD', version_path, swob.HTTPNoContent, {
|
|
'Content-Length': 10,
|
|
'Content-Type': 'application/old-version',
|
|
'ETag': etag,
|
|
}, '')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': timestamp.normal})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', version_path),
|
|
('PUT', '/v1/a/c/o?version-id=%s' % timestamp.normal),
|
|
])
|
|
obj_put_headers = self.app.calls_with_headers[-1].headers
|
|
symlink_expected_headers = {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR: self.build_symlink_path(
|
|
'c', 'o', (~timestamp).normal),
|
|
'x-object-sysmeta-symlink-target-etag': etag,
|
|
'x-object-sysmeta-symlink-target-bytes': '10',
|
|
}
|
|
for k, v in symlink_expected_headers.items():
|
|
self.assertEqual(obj_put_headers[k], v)
|
|
|
|
def test_PUT_version_with_body(self):
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT', body='foo',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '1'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
|
|
def test_PUT_version_not_found(self):
|
|
timestamp = next(self.ts)
|
|
version_path = '%s?symlink=get' % self.build_versions_path(
|
|
obj='o', version=(~timestamp).normal)
|
|
self.app.register('HEAD', version_path, swob.HTTPNotFound, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': timestamp.normal})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertIn(b'version does not exist', body)
|
|
|
|
def test_PUT_version_container_not_found(self):
|
|
timestamp = next(self.ts)
|
|
version_path = '%s?symlink=get' % self.build_versions_path(
|
|
obj='o', version=(~timestamp).normal)
|
|
self.app.register('HEAD', version_path, swob.HTTPNotFound, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT',
|
|
environ={'swift.cache': self.cache_version_on_but_busted},
|
|
params={'version-id': timestamp.normal})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '500 Internal Error')
|
|
self.assertIn(b'container does not exist', body)
|
|
self.assertIn(b're-enable object versioning', body)
|
|
|
|
def test_PUT_version_invalid(self):
|
|
invalid_versions = ('null', 'something', '-10')
|
|
for version_id in invalid_versions:
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': invalid_versions})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
|
|
def test_POST_error(self):
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='POST',
|
|
headers={'Content-Type': 'text/plain',
|
|
'X-Object-Meta-foo': 'bar'},
|
|
environ={'swift.cache': self.cache_version_on,
|
|
'swift.trans_id': 'fake_trans_id'},
|
|
params={'version-id': '1'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
|
|
def test_GET_and_HEAD(self):
|
|
self.app.register(
|
|
'GET',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPOk, {}, 'foobar')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='GET',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Object-Version-Id', '0000000060.00000'),
|
|
headers)
|
|
self.assertEqual(b'foobar', body)
|
|
# HEAD with same params find same registered GET
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='HEAD',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Object-Version-Id', '0000000060.00000'),
|
|
headers)
|
|
self.assertEqual(b'', body)
|
|
|
|
def test_GET_404(self):
|
|
self.app.register(
|
|
'GET',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPNotFound, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='GET',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertNotIn(('X-Object-Version-Id', '0000000060.00000'),
|
|
headers)
|
|
|
|
def test_HEAD(self):
|
|
self.app.register(
|
|
'HEAD',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPOk, {
|
|
'X-Object-Meta-Foo': 'bar'},
|
|
'')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='HEAD',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertIn(('X-Object-Version-Id', '0000000060.00000'),
|
|
headers)
|
|
self.assertIn(('X-Object-Meta-Foo', 'bar'), headers)
|
|
|
|
def test_GET_null_id(self):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk, {}, 'foobar')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='GET',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': 'null'})
|
|
# N.B. GET w/ query param found registered raw_path GET
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(1, len(self.authorized))
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual(1, len(self.app.calls))
|
|
self.assertIn(('X-Object-Version-Id', 'null'), headers)
|
|
self.assertEqual(b'foobar', body)
|
|
# and HEAD w/ same params finds same registered GET
|
|
req = Request.blank(
|
|
'/v1/a/c/o?version-id=null', method='HEAD',
|
|
environ={'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual(2, len(self.app.calls))
|
|
self.assertIn(('X-Object-Version-Id', 'null'), headers)
|
|
self.assertEqual(b'', body)
|
|
|
|
def test_GET_null_id_versioned_obj(self):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk, {
|
|
'Content-Location': self.build_versions_path(
|
|
obj='o', version='9999998765.99999')},
|
|
'')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='GET',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': 'null'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertEqual(1, len(self.authorized))
|
|
self.assertEqual(1, len(self.app.calls))
|
|
self.assertNotIn(('X-Object-Version-Id', '0000001234.00000'), headers)
|
|
# This will log a 499 but (at the moment, anyway)
|
|
# we don't have a good way to avoid it
|
|
self.expected_unread_requests[('GET', '/v1/a/c/o?version-id=null')] = 1
|
|
|
|
def test_GET_null_id_404(self):
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPNotFound, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='GET',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': 'null'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertEqual(1, len(self.authorized))
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual(1, len(self.app.calls))
|
|
self.assertNotIn(('X-Object-Version-Id', 'null'), headers)
|
|
# and HEAD w/ same params finds same registered GET
|
|
# we have test_HEAD_null_id, the following test is meant to illustrate
|
|
# that FakeSwift works for HEADs even if only GETs are registered.
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='HEAD',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': 'null'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertEqual(1, len(self.authorized))
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual(2, len(self.app.calls))
|
|
self.assertNotIn(('X-Object-Version-Id', 'null'), headers)
|
|
|
|
def test_HEAD_null_id(self):
|
|
self.app.register(
|
|
'HEAD', '/v1/a/c/o', swob.HTTPOk, {'X-Object-Meta-Foo': 'bar'}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='HEAD',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': 'null'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(1, len(self.authorized))
|
|
self.assertEqual(1, len(self.app.calls))
|
|
self.assertIn(('X-Object-Version-Id', 'null'), headers)
|
|
self.assertIn(('X-Object-Meta-Foo', 'bar'), headers)
|
|
# N.B. GET on explicitly registered HEAD raised KeyError
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='GET',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': 'null'})
|
|
with self.assertRaises(KeyError):
|
|
status, headers, body = self.call_ov(req)
|
|
|
|
def test_HEAD_delete_marker(self):
|
|
self.app.register(
|
|
'HEAD',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPOk, {
|
|
'content-type':
|
|
'application/x-deleted;swift_versions_deleted=1'},
|
|
'')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='HEAD',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
|
|
# a HEAD/GET of a delete-marker returns a 404
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertIn(('X-Object-Version-Id', '0000000060.00000'),
|
|
headers)
|
|
|
|
def test_DELETE_not_current_version(self):
|
|
# This tests when version-id does not point to the
|
|
# current version, in this case, there's no need to
|
|
# re-link symlink
|
|
self.app.register('HEAD', '/v1/a/c/o', swob.HTTPOk, {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR: self.build_symlink_path(
|
|
'c', 'o', '9999999940.99999')}, '')
|
|
self.app.register(
|
|
'DELETE',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPNoContent, {}, 'foobar')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='DELETE',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual('0000000059.00000',
|
|
dict(headers)['X-Object-Current-Version-Id'])
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', '/v1/a/c/o?symlink=get'),
|
|
('DELETE',
|
|
'%s?version-id=0000000060.00000' % self.build_versions_path(
|
|
obj='o', version='9999999939.99999')),
|
|
])
|
|
|
|
calls = self.app.calls_with_headers
|
|
self.assertIn('X-Newest', calls[0].headers)
|
|
self.assertEqual('True', calls[0].headers['X-Newest'])
|
|
|
|
def test_DELETE_current_version(self):
|
|
self.app.register('HEAD', '/v1/a/c/o', swob.HTTPOk, {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR: self.build_symlink_path(
|
|
'c', 'o', '9999999939.99999')}, '')
|
|
self.app.register(
|
|
'DELETE',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPNoContent, {}, '')
|
|
self.app.register(
|
|
'DELETE', '/v1/a/c/o', swob.HTTPNoContent, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='DELETE',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual('null',
|
|
dict(headers)['X-Object-Current-Version-Id'])
|
|
self.assertEqual('0000000060.00000',
|
|
dict(headers)['X-Object-Version-Id'])
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', '/v1/a/c/o?symlink=get'),
|
|
('DELETE', '/v1/a/c/o'),
|
|
('DELETE',
|
|
self.build_versions_path(obj='o', version='9999999939.99999')),
|
|
])
|
|
|
|
def test_DELETE_current_version_is_delete_marker(self):
|
|
self.app.register('HEAD', '/v1/a/c/o', swob.HTTPNotFound, {}, '')
|
|
self.app.register(
|
|
'DELETE',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPNoContent, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='DELETE',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual('null',
|
|
dict(headers)['X-Object-Current-Version-Id'])
|
|
self.assertEqual('0000000060.00000',
|
|
dict(headers)['X-Object-Version-Id'])
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', '/v1/a/c/o?symlink=get'),
|
|
('DELETE',
|
|
'%s?version-id=0000000060.00000' % self.build_versions_path(
|
|
obj='o', version='9999999939.99999')),
|
|
])
|
|
|
|
def test_DELETE_current_obj_is_unversioned(self):
|
|
self.app.register('HEAD', '/v1/a/c/o', swob.HTTPOk, {}, '')
|
|
self.app.register(
|
|
'DELETE',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPNoContent, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='DELETE',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual('null',
|
|
dict(headers)['X-Object-Current-Version-Id'])
|
|
self.assertEqual('0000000060.00000',
|
|
dict(headers)['X-Object-Version-Id'])
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', '/v1/a/c/o?symlink=get'),
|
|
('DELETE',
|
|
'%s?version-id=0000000060.00000' % self.build_versions_path(
|
|
obj='o', version='9999999939.99999')),
|
|
])
|
|
|
|
def test_DELETE_null_version(self):
|
|
self.app.register(
|
|
'DELETE', '/v1/a/c/o', swob.HTTPNoContent, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='DELETE',
|
|
environ={'swift.cache': self.cache_version_on},
|
|
params={'version-id': 'null'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual(self.app.calls, [
|
|
('DELETE', '/v1/a/c/o?version-id=null'),
|
|
])
|
|
|
|
|
|
class ObjectVersioningVersionAPIWhileDisabled(ObjectVersioningBaseTestCase):
|
|
|
|
def test_PUT_version_versioning_disbaled(self):
|
|
timestamp = next(self.ts)
|
|
version_path = '%s?symlink=get' % self.build_versions_path(
|
|
obj='o', version=(~timestamp).normal)
|
|
etag = md5(b'old-version-etag', usedforsecurity=False).hexdigest()
|
|
self.app.register('HEAD', version_path, swob.HTTPNoContent, {
|
|
'Content-Length': 10,
|
|
'Content-Type': 'application/old-version',
|
|
'ETag': etag,
|
|
}, '')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='PUT',
|
|
environ={'swift.cache': self.cache_version_off},
|
|
params={'version-id': timestamp.normal})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', version_path),
|
|
('PUT', '/v1/a/c/o?version-id=%s' % timestamp.normal),
|
|
])
|
|
obj_put_headers = self.app.calls_with_headers[-1].headers
|
|
symlink_expected_headers = {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR: self.build_symlink_path(
|
|
'c', 'o', (~timestamp).normal),
|
|
'x-object-sysmeta-symlink-target-etag': etag,
|
|
'x-object-sysmeta-symlink-target-bytes': '10',
|
|
}
|
|
for k, v in symlink_expected_headers.items():
|
|
self.assertEqual(obj_put_headers[k], v)
|
|
|
|
def test_POST_error_versioning_disabled(self):
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='POST',
|
|
headers={'Content-Type': 'text/plain',
|
|
'X-Object-Meta-foo': 'bar'},
|
|
environ={'swift.cache': self.cache_version_off,
|
|
'swift.trans_id': 'fake_trans_id'},
|
|
params={'version-id': '1'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
|
|
def test_DELETE_current_version(self):
|
|
self.app.register('HEAD', '/v1/a/c/o', swob.HTTPOk, {
|
|
SYSMETA_VERSIONS_SYMLINK: 'true',
|
|
TGT_OBJ_SYSMETA_SYMLINK_HDR: self.build_symlink_path(
|
|
'c', 'o', '9999999939.99999')}, '')
|
|
self.app.register(
|
|
'DELETE',
|
|
self.build_versions_path(obj='o', version='9999999939.99999'),
|
|
swob.HTTPNoContent, {}, '')
|
|
self.app.register(
|
|
'DELETE', '/v1/a/c/o', swob.HTTPNoContent, {}, '')
|
|
|
|
# request with versioning disabled
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='DELETE',
|
|
environ={'swift.cache': self.cache_version_off},
|
|
params={'version-id': '0000000060.00000'})
|
|
status, headers, body = self.call_ov(req)
|
|
|
|
self.assertEqual(status, '204 No Content')
|
|
self.assertEqual(self.app.calls, [
|
|
('HEAD', '/v1/a/c/o?symlink=get'),
|
|
('DELETE', '/v1/a/c/o'),
|
|
('DELETE',
|
|
self.build_versions_path(obj='o', version='9999999939.99999')),
|
|
])
|
|
|
|
|
|
class ObjectVersioningTestContainerOperations(ObjectVersioningBaseTestCase):
|
|
def test_container_listing_translation(self):
|
|
listing_body = [{
|
|
'bytes': 0,
|
|
'name': 'my-normal-obj',
|
|
'hash': 'd41d8cd98f00b204e9800998ecf8427e; '
|
|
'symlink_target=%s; '
|
|
'symlink_target_etag=e55cedc11adb39c404b7365f7d6291fa; '
|
|
'symlink_target_bytes=9' % self.build_symlink_path(
|
|
'c', 'my-normal-obj', '9999999989.99999'),
|
|
'last_modified': '2019-07-26T15:09:54.518990',
|
|
'content_type': 'application/foo',
|
|
}, {
|
|
'bytes': 8,
|
|
'name': 'my-old-object',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '2019-07-26T15:54:38.326800',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 0,
|
|
'name': 'my-slo-manifest',
|
|
'hash': '387d1ab7d89eda2162bcf8e502667c86; '
|
|
'slo_etag=71e938d37c1d06dc634dd24660255a88; '
|
|
'symlink_target=%s; '
|
|
'symlink_target_etag=387d1ab7d89eda2162bcf8e502667c86; '
|
|
# N.B. symlink_target_bytes is set to the slo_size
|
|
'symlink_target_bytes=10485760' % self.build_symlink_path(
|
|
'c', 'my-slo-manifest', '9999999979.99999'),
|
|
'last_modified': '2019-07-26T15:00:28.499260',
|
|
'content_type': 'application/baz',
|
|
}, {
|
|
'bytes': 0,
|
|
'name': 'unexpected-symlink',
|
|
'hash': 'd41d8cd98f00b204e9800998ecf8427e; '
|
|
'symlink_target=tgt_container/tgt_obj; '
|
|
'symlink_target_etag=e55cedc11adb39c404b7365f7d6291fa; '
|
|
'symlink_target_bytes=9',
|
|
'last_modified': '2019-07-26T15:09:54.518990',
|
|
'content_type': 'application/symlink',
|
|
}]
|
|
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c',
|
|
environ={'REQUEST_METHOD': 'GET'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
expected = [{
|
|
'bytes': 9,
|
|
'name': 'my-normal-obj',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '2019-07-26T15:09:54.518990',
|
|
'content_type': 'application/foo',
|
|
'symlink_path':
|
|
'/v1/a/c/my-normal-obj?version-id=0000000010.00000',
|
|
'version_symlink': True,
|
|
}, {
|
|
'bytes': 8,
|
|
'name': 'my-old-object',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '2019-07-26T15:54:38.326800',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 10485760,
|
|
'name': 'my-slo-manifest',
|
|
# since we don't have slo middleware in test pipeline, we expect
|
|
# slo_etag to stay in the hash key
|
|
'hash': '387d1ab7d89eda2162bcf8e502667c86; '
|
|
'slo_etag=71e938d37c1d06dc634dd24660255a88',
|
|
'last_modified': '2019-07-26T15:00:28.499260',
|
|
'content_type': 'application/baz',
|
|
'symlink_path':
|
|
'/v1/a/c/my-slo-manifest?version-id=0000000020.00000',
|
|
'version_symlink': True,
|
|
}, {
|
|
'bytes': 0,
|
|
'name': 'unexpected-symlink',
|
|
'hash': 'd41d8cd98f00b204e9800998ecf8427e',
|
|
'last_modified': '2019-07-26T15:09:54.518990',
|
|
'symlink_bytes': 9,
|
|
'symlink_path': '/v1/a/tgt_container/tgt_obj',
|
|
'symlink_etag': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'content_type': 'application/symlink',
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
def test_listing_translation_utf8(self):
|
|
listing_body = [{
|
|
'bytes': 0,
|
|
'name': u'\N{SNOWMAN}-obj',
|
|
'hash': 'd41d8cd98f00b204e9800998ecf8427e; '
|
|
'symlink_target=%s; '
|
|
'symlink_target_etag=e55cedc11adb39c404b7365f7d6291fa; '
|
|
'symlink_target_bytes=9' % self.build_symlink_path(
|
|
u'\N{COMET}-container', u'\N{CLOUD}-target',
|
|
'9999999989.99999'),
|
|
'last_modified': '2019-07-26T15:09:54.518990',
|
|
'content_type': 'application/snowman',
|
|
}]
|
|
self.app.register(
|
|
'GET', '/v1/a/\xe2\x98\x83-test', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: wsgi_quote(
|
|
self.str_to_wsgi(self.build_container_name(
|
|
u'\N{COMET}-container'))),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/\xe2\x98\x83-test',
|
|
environ={'REQUEST_METHOD': 'GET'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
expected = [{
|
|
'bytes': 9,
|
|
'name': u'\N{SNOWMAN}-obj',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '2019-07-26T15:09:54.518990',
|
|
'symlink_path':
|
|
'/v1/a/%E2%98%83-test/%E2%98%81-target?'
|
|
'version-id=0000000010.00000',
|
|
'content_type': 'application/snowman',
|
|
'version_symlink': True,
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
def test_list_versions(self):
|
|
listing_body = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 0,
|
|
'name': 'obj',
|
|
'hash': 'd41d8cd98f00b204e9800998ecf8427e; '
|
|
'symlink_target=%s; '
|
|
'symlink_target_etag=e55cedc11adb39c404b7365f7d6291fa; '
|
|
'symlink_target_bytes=9' %
|
|
self.build_symlink_path('c', 'obj', '9999999979.99999'),
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
|
|
versions_listing_body = [{
|
|
'bytes': 9,
|
|
'name': self.build_object_name('obj', '9999999979.99999'),
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}, {
|
|
'bytes': 8,
|
|
'name': self.build_object_name('obj', '9999999989.99999'),
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb35d5f2',
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
self.app.register(
|
|
'GET', self.build_versions_path(), swob.HTTPOk, {},
|
|
json.dumps(versions_listing_body).encode('utf8'))
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
expected = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'version_id': 'null',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
'is_latest': True,
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'version_id': '0000000020.00000',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': True,
|
|
}, {
|
|
'bytes': 8,
|
|
'name': 'obj',
|
|
'version_id': '0000000010.00000',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb35d5f2',
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': False,
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
# Can be explicitly JSON
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&format=json',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on,
|
|
'HTTP_ACCEPT': 'application/json'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
|
|
# But everything else is unacceptable
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&format=txt',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '406 Not Acceptable')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on,
|
|
'HTTP_ACCEPT': 'text/plain'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '406 Not Acceptable')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&format=xml',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '406 Not Acceptable')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on,
|
|
'HTTP_ACCEPT': 'text/xml'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '406 Not Acceptable')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on,
|
|
'HTTP_ACCEPT': 'application/xml'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '406 Not Acceptable')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&format=asdf',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '406 Not Acceptable')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on,
|
|
'HTTP_ACCEPT': 'foo/bar'})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '406 Not Acceptable')
|
|
|
|
def test_list_versions_marker_missing_marker(self):
|
|
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True}, '{}')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&version_marker=1',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
self.assertEqual(body, b'version_marker param requires marker')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&marker=obj&version_marker=id',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
self.assertEqual(body, b'invalid version_marker param')
|
|
|
|
def test_list_versions_marker(self):
|
|
listing_body = [{
|
|
'bytes': 8,
|
|
'name': 'non-versioned-obj',
|
|
'hash': 'etag',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 0,
|
|
'name': 'obj',
|
|
'hash': 'd41d8cd98f00b204e9800998ecf8427e; '
|
|
'symlink_target=%s; '
|
|
'symlink_target_etag=e55cedc11adb39c404b7365f7d6291fa; '
|
|
'symlink_target_bytes=9' %
|
|
self.build_symlink_path('c', 'obj', '9999999969.99999'),
|
|
'last_modified': '1970-01-01T00:00:30.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
|
|
versions_listing_body = [{
|
|
'bytes': 9,
|
|
'name': self.build_object_name('obj', '9999999969.99999'),
|
|
'hash': 'etagv3',
|
|
'last_modified': '1970-01-01T00:00:30.000000',
|
|
'content_type': 'text/plain',
|
|
}, {
|
|
'bytes': 10,
|
|
'name': self.build_object_name('obj', '9999999979.99999'),
|
|
'hash': 'etagv2',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}, {
|
|
'bytes': 8,
|
|
'name': self.build_object_name('obj', '9999999989.99999'),
|
|
'hash': 'etagv1',
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
|
|
expected = [{
|
|
'bytes': 8,
|
|
'name': 'non-versioned-obj',
|
|
'hash': 'etag',
|
|
'version_id': 'null',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'version_id': '0000000030.00000',
|
|
'hash': 'etagv3',
|
|
'last_modified': '1970-01-01T00:00:30.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': True,
|
|
}, {
|
|
'bytes': 10,
|
|
'name': 'obj',
|
|
'version_id': '0000000020.00000',
|
|
'hash': 'etagv2',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': False,
|
|
}, {
|
|
'bytes': 8,
|
|
'name': 'obj',
|
|
'version_id': '0000000010.00000',
|
|
'hash': 'etagv1',
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': False,
|
|
}]
|
|
|
|
self.app.register(
|
|
'GET', self.build_versions_path(), swob.HTTPOk, {},
|
|
json.dumps(versions_listing_body).encode('utf8'))
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body[1:]).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&marker=obj',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual(expected[1:], json.loads(body))
|
|
|
|
# version_marker
|
|
self.app.register(
|
|
'GET',
|
|
'%s?marker=%s' % (
|
|
self.build_versions_path(),
|
|
self.build_object_name('obj', '9999999989.99999')),
|
|
swob.HTTPOk, {},
|
|
json.dumps(versions_listing_body[2:]).encode('utf8'))
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body[1:]).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&marker=obj&version_marker=0000000010.00000',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertEqual(expected[3:], json.loads(body))
|
|
|
|
def test_list_versions_invalid_delimiter(self):
|
|
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True}, '{}')
|
|
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&delimiter=1',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
self.assertEqual(body, b'invalid delimiter param')
|
|
|
|
def test_list_versions_delete_markers(self):
|
|
listing_body = []
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
versions_listing_body = [{
|
|
'name': self.build_object_name('obj', '9999999979.99999'),
|
|
'bytes': 0,
|
|
'hash': utils.MD5_OF_EMPTY_STRING,
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': DELETE_MARKER_CONTENT_TYPE,
|
|
}, {
|
|
'name': self.build_object_name('obj', '9999999989.99999'),
|
|
'bytes': 0,
|
|
'hash': utils.MD5_OF_EMPTY_STRING,
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': DELETE_MARKER_CONTENT_TYPE,
|
|
}]
|
|
self.app.register(
|
|
'GET', self.build_versions_path(), swob.HTTPOk, {},
|
|
json.dumps(versions_listing_body).encode('utf8'))
|
|
req = Request.blank('/v1/a/c?versions', method='GET',
|
|
environ={'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
expected = [{
|
|
'name': 'obj',
|
|
'bytes': 0,
|
|
'version_id': '0000000020.00000',
|
|
'hash': utils.MD5_OF_EMPTY_STRING,
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': DELETE_MARKER_CONTENT_TYPE,
|
|
'is_latest': True,
|
|
}, {
|
|
'name': 'obj',
|
|
'bytes': 0,
|
|
'version_id': '0000000010.00000',
|
|
'hash': utils.MD5_OF_EMPTY_STRING,
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': DELETE_MARKER_CONTENT_TYPE,
|
|
'is_latest': False,
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
def test_list_versions_unversioned(self):
|
|
listing_body = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
# How did this get here??? Who knows -- maybe another container
|
|
# replica *does* know about versioning being enabled
|
|
'bytes': 0,
|
|
'name': 'obj',
|
|
'hash': 'd41d8cd98f00b204e9800998ecf8427e; '
|
|
'symlink_target=%s; '
|
|
'symlink_target_etag=e55cedc11adb39c404b7365f7d6291fa; '
|
|
'symlink_target_bytes=9' %
|
|
self.build_symlink_path('c', 'obj', '9999999979.99999'),
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
|
|
self.app.register(
|
|
'GET', self.build_versions_path(), swob.HTTPNotFound, {}, None)
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk, {},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_off})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertNotIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
expected = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'version_id': 'null',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
'is_latest': True,
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'version_id': '0000000020.00000',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': True,
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
def test_list_versions_delimiter(self):
|
|
listing_body = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 0,
|
|
'name': 'obj',
|
|
'hash': 'd41d8cd98f00b204e9800998ecf8427e; '
|
|
'symlink_target=%s; '
|
|
'symlink_target_etag=e55cedc11adb39c404b7365f7d6291fa; '
|
|
'symlink_target_bytes=9' %
|
|
self.build_symlink_path('c', 'obj', '9999999979.99999'),
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}, {
|
|
'subdir': 'subdir/'
|
|
}]
|
|
|
|
versions_listing_body = [{
|
|
'bytes': 9,
|
|
'name': self.build_object_name('obj', '9999999979.99999'),
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}, {
|
|
'bytes': 8,
|
|
'name': self.build_object_name('obj', '9999999989.99999'),
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb35d5f2',
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': 'text/plain',
|
|
}, {
|
|
'subdir': get_reserved_name('subdir/')
|
|
}]
|
|
|
|
self.app.register(
|
|
'GET', self.build_versions_path(), swob.HTTPOk, {},
|
|
json.dumps(versions_listing_body).encode('utf8'))
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c?versions&delimiter=/',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
expected = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'version_id': 'null',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
'is_latest': True,
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'version_id': '0000000020.00000',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': True,
|
|
}, {
|
|
'bytes': 8,
|
|
'name': 'obj',
|
|
'version_id': '0000000010.00000',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb35d5f2',
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': False,
|
|
}, {
|
|
'subdir': 'subdir/'
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
def test_list_versions_empty_primary(self):
|
|
versions_listing_body = [{
|
|
'bytes': 8,
|
|
'name': self.build_object_name('obj', '9999999979.99999'),
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}, {
|
|
'bytes': 8,
|
|
'name': self.build_object_name('obj', '9999999989.99999'),
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb35d5f2',
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
self.app.register(
|
|
'GET', self.build_versions_path(), swob.HTTPOk, {},
|
|
json.dumps(versions_listing_body).encode('utf8'))
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
'{}')
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
expected = [{
|
|
'bytes': 8,
|
|
'name': 'obj',
|
|
'version_id': '0000000020.00000',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': False,
|
|
}, {
|
|
'bytes': 8,
|
|
'name': 'obj',
|
|
'version_id': '0000000010.00000',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb35d5f2',
|
|
'last_modified': '1970-01-01T00:00:10.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': False,
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
def test_list_versions_error_versions_container(self):
|
|
listing_body = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
|
|
self.app.register(
|
|
'GET', self.build_versions_path(),
|
|
swob.HTTPInternalServerError, {}, '')
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '500 Internal Error')
|
|
|
|
def test_list_versions_empty_versions_container(self):
|
|
listing_body = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
|
|
self.app.register(
|
|
'GET', self.build_versions_path(), swob.HTTPOk, {}, '{}')
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
expected = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'version_id': 'null',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
'is_latest': True,
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'version_id': 'null',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': True,
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
def test_list_versions_404_versions_container(self):
|
|
listing_body = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
|
|
self.app.register(
|
|
'GET', self.build_versions_path(), swob.HTTPNotFound, {}, '')
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
expected = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'version_id': 'null',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
'is_latest': True,
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'version_id': 'null',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': True,
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
def test_list_versions_never_enabled(self):
|
|
listing_body = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
}]
|
|
|
|
self.app.register(
|
|
'GET', self.build_versions_path(), swob.HTTPNotFound, {}, '')
|
|
self.app.register(
|
|
'GET', '/v1/a/c', swob.HTTPOk, {},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_never_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertNotIn('x-versions-enabled', [h.lower() for h, _ in headers])
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
expected = [{
|
|
'bytes': 8,
|
|
'name': 'my-other-object',
|
|
'version_id': 'null',
|
|
'hash': 'ebdd8d46ecb4a07f6c433d67eb05d5f3',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
'content_type': 'application/bar',
|
|
'is_latest': True,
|
|
}, {
|
|
'bytes': 9,
|
|
'name': 'obj',
|
|
'version_id': 'null',
|
|
'hash': 'e55cedc11adb39c404b7365f7d6291fa',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
'content_type': 'text/plain',
|
|
'is_latest': True,
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/c?format=json'),
|
|
('HEAD', self.build_versions_path()),
|
|
])
|
|
|
|
# if it's in cache, we won't even get the HEAD
|
|
self.app._calls = []
|
|
self.cache_version_never_on.set(
|
|
get_cache_key('a', self.build_container_name('c')),
|
|
{'status': 404})
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': self.cache_version_never_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertNotIn('x-versions-enabled', [h.lower() for h, _ in headers])
|
|
self.assertEqual(expected, json.loads(body))
|
|
self.assertEqual(self.app.calls, [('GET', '/v1/a/c?format=json')])
|
|
|
|
def test_bytes_count(self):
|
|
self.app.register(
|
|
'HEAD', self.build_versions_path(), swob.HTTPOk,
|
|
{'X-Container-Bytes-Used': '17',
|
|
'X-Container-Object-Count': '3'}, '')
|
|
self.app.register(
|
|
'HEAD', '/v1/a/c', swob.HTTPOk,
|
|
{SYSMETA_VERSIONS_CONT: self.build_container_name('c'),
|
|
SYSMETA_VERSIONS_ENABLED: True,
|
|
'X-Container-Bytes-Used': '8',
|
|
'X-Container-Object-Count': '1'}, '')
|
|
req = Request.blank(
|
|
'/v1/a/c?versions',
|
|
environ={'REQUEST_METHOD': 'HEAD',
|
|
'swift.cache': self.cache_version_on})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertIn(('X-Versions-Enabled', 'True'), headers)
|
|
self.assertIn(('X-Container-Bytes-Used', '25'), headers)
|
|
self.assertIn(('X-Container-Object-Count', '1'), headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
|
|
class ObjectVersioningTestAccountOperations(ObjectVersioningBaseTestCase):
|
|
|
|
def test_list_containers(self):
|
|
listing_body = [{
|
|
'bytes': 10,
|
|
'count': 2,
|
|
'name': 'regular-cont',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
}, {
|
|
'bytes': 0,
|
|
'count': 3,
|
|
'name': 'versioned-cont',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
}]
|
|
|
|
versions_listing_body = [{
|
|
'bytes': 24,
|
|
'count': 3,
|
|
'name': self.build_container_name('versioned-cont'),
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
}]
|
|
|
|
cache = FakeMemcache()
|
|
|
|
self.app.register(
|
|
'GET', '/v1/a', swob.HTTPOk, {},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
|
|
params = {
|
|
'format': 'json',
|
|
'prefix': self.str_to_wsgi(get_reserved_name('versions')),
|
|
}
|
|
path = '/v1/a?%s' % urllib.parse.urlencode(params)
|
|
|
|
self.app.register(
|
|
'GET', path, swob.HTTPOk, {},
|
|
json.dumps(versions_listing_body).encode('utf8'))
|
|
|
|
req = Request.blank(
|
|
'/v1/a',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': cache})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
expected = [{
|
|
'bytes': 10,
|
|
'count': 2,
|
|
'name': 'regular-cont',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
}, {
|
|
'bytes': 24,
|
|
'count': 3,
|
|
'name': 'versioned-cont',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
req.query_string = 'limit=1'
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(1, len(json.loads(body)))
|
|
|
|
req.query_string = 'limit=foo'
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(2, len(json.loads(body)))
|
|
|
|
req.query_string = 'limit=100000000000000000000000'
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '412 Precondition Failed')
|
|
|
|
def test_list_containers_prefix(self):
|
|
listing_body = [{
|
|
'bytes': 0,
|
|
'count': 1,
|
|
'name': 'versioned-cont',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
}]
|
|
|
|
versions_listing_body = [{
|
|
'bytes': 24,
|
|
'count': 3,
|
|
'name': self.build_container_name('versioned-cont'),
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
}]
|
|
|
|
cache = FakeMemcache()
|
|
|
|
path = '/v1/a?%s' % urllib.parse.urlencode({
|
|
'format': 'json', 'prefix': 'versioned-'})
|
|
|
|
self.app.register(
|
|
'GET', path, swob.HTTPOk, {},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
|
|
path = '/v1/a?%s' % urllib.parse.urlencode({
|
|
'format': 'json', 'prefix': self.str_to_wsgi(
|
|
self.build_container_name('versioned-'))})
|
|
|
|
self.app.register(
|
|
'GET', path, swob.HTTPOk, {},
|
|
json.dumps(versions_listing_body).encode('utf8'))
|
|
|
|
req = Request.blank(
|
|
'/v1/a?prefix=versioned-',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': cache})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
expected = [{
|
|
'bytes': 24,
|
|
'count': 1,
|
|
'name': 'versioned-cont',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
def test_list_orphan_hidden_containers(self):
|
|
|
|
listing_body = [{
|
|
'bytes': 10,
|
|
'count': 2,
|
|
'name': 'alpha',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
}, {
|
|
'bytes': 6,
|
|
'count': 3,
|
|
'name': 'bravo',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
}, {
|
|
'bytes': 0,
|
|
'count': 5,
|
|
'name': 'charlie',
|
|
'last_modified': '1970-01-01T00:00:30.000000',
|
|
}, {
|
|
'bytes': 0,
|
|
'count': 8,
|
|
'name': 'zulu',
|
|
'last_modified': '1970-01-01T00:00:40.000000',
|
|
}]
|
|
|
|
versions_listing_body1 = [{
|
|
'bytes': 24,
|
|
'count': 8,
|
|
'name': self.build_container_name('bravo'),
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
}, {
|
|
'bytes': 123,
|
|
'count': 23,
|
|
'name': self.build_container_name('charlie'),
|
|
'last_modified': '1970-01-01T00:00:30.000000',
|
|
}, {
|
|
'bytes': 13,
|
|
'count': 30,
|
|
'name': self.build_container_name('kilo'),
|
|
'last_modified': '1970-01-01T00:00:35.000000',
|
|
}, {
|
|
'bytes': 83,
|
|
'count': 13,
|
|
'name': self.build_container_name('zulu'),
|
|
'last_modified': '1970-01-01T00:00:40.000000',
|
|
}]
|
|
|
|
cache = FakeMemcache()
|
|
|
|
self.app.register(
|
|
'GET', '/v1/a', swob.HTTPOk, {},
|
|
json.dumps(listing_body).encode('utf8'))
|
|
|
|
params = {
|
|
'format': 'json',
|
|
'prefix': self.str_to_wsgi(get_reserved_name('versions')),
|
|
}
|
|
path = '/v1/a?%s' % urllib.parse.urlencode(params)
|
|
|
|
self.app.register(
|
|
'GET', path, swob.HTTPOk, {},
|
|
json.dumps(versions_listing_body1).encode('utf8'))
|
|
|
|
req = Request.blank(
|
|
'/v1/a',
|
|
environ={'REQUEST_METHOD': 'GET',
|
|
'swift.cache': cache})
|
|
status, headers, body = self.call_ov(req)
|
|
self.assertEqual(status, '200 OK')
|
|
expected = [{
|
|
'bytes': 10,
|
|
'count': 2,
|
|
'name': 'alpha',
|
|
'last_modified': '1970-01-01T00:00:05.000000',
|
|
}, {
|
|
'bytes': 30,
|
|
'count': 3,
|
|
'name': 'bravo',
|
|
'last_modified': '1970-01-01T00:00:20.000000',
|
|
}, {
|
|
'bytes': 123,
|
|
'count': 5,
|
|
'name': 'charlie',
|
|
'last_modified': '1970-01-01T00:00:30.000000',
|
|
}, {
|
|
'bytes': 13,
|
|
'count': 0,
|
|
'name': 'kilo',
|
|
'last_modified': '1970-01-01T00:00:35.000000',
|
|
}, {
|
|
'bytes': 83,
|
|
'count': 8,
|
|
'name': 'zulu',
|
|
'last_modified': '1970-01-01T00:00:40.000000',
|
|
}]
|
|
self.assertEqual(expected, json.loads(body))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|