Add functional tests for Storage Policy
* additional container tests * refactor test cross policy copy * make functional tests cleanup better In-process functional tests only define a single ring and will skip some of the multi-storage policy tests, but have been updated to reload_policies with the patched swift.conf. DocImpact Implements: blueprint storage-policies Change-Id: If17bc7b9737558d3b9a54eeb6ff3e6b51463f002
This commit is contained in:
parent
b02f0db126
commit
c11ac01252
@ -35,7 +35,7 @@ class ContainerController(Controller):
|
|||||||
# Ensure these are all lowercase
|
# Ensure these are all lowercase
|
||||||
pass_through_headers = ['x-container-read', 'x-container-write',
|
pass_through_headers = ['x-container-read', 'x-container-write',
|
||||||
'x-container-sync-key', 'x-container-sync-to',
|
'x-container-sync-key', 'x-container-sync-to',
|
||||||
'x-versions-location', POLICY_INDEX.lower()]
|
'x-versions-location']
|
||||||
|
|
||||||
def __init__(self, app, account_name, container_name, **kwargs):
|
def __init__(self, app, account_name, container_name, **kwargs):
|
||||||
Controller.__init__(self, app)
|
Controller.__init__(self, app)
|
||||||
|
@ -21,6 +21,7 @@ import locale
|
|||||||
import eventlet
|
import eventlet
|
||||||
import eventlet.debug
|
import eventlet.debug
|
||||||
import functools
|
import functools
|
||||||
|
import random
|
||||||
from time import time, sleep
|
from time import time, sleep
|
||||||
from httplib import HTTPException
|
from httplib import HTTPException
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
@ -37,7 +38,7 @@ from test.functional.swift_test_client import Connection, ResponseError
|
|||||||
# on file systems that don't support extended attributes.
|
# on file systems that don't support extended attributes.
|
||||||
from test.unit import debug_logger, FakeMemcache
|
from test.unit import debug_logger, FakeMemcache
|
||||||
|
|
||||||
from swift.common import constraints, utils, ring
|
from swift.common import constraints, utils, ring, storage_policy
|
||||||
from swift.common.wsgi import monkey_patch_mimetools
|
from swift.common.wsgi import monkey_patch_mimetools
|
||||||
from swift.common.middleware import catch_errors, gatekeeper, healthcheck, \
|
from swift.common.middleware import catch_errors, gatekeeper, healthcheck, \
|
||||||
proxy_logging, container_sync, bulk, tempurl, slo, dlo, ratelimit, \
|
proxy_logging, container_sync, bulk, tempurl, slo, dlo, ratelimit, \
|
||||||
@ -151,6 +152,8 @@ def in_process_setup(the_object_server=object_server):
|
|||||||
orig_swift_conf_name = utils.SWIFT_CONF_FILE
|
orig_swift_conf_name = utils.SWIFT_CONF_FILE
|
||||||
utils.SWIFT_CONF_FILE = swift_conf
|
utils.SWIFT_CONF_FILE = swift_conf
|
||||||
constraints.reload_constraints()
|
constraints.reload_constraints()
|
||||||
|
storage_policy.SWIFT_CONF_FILE = swift_conf
|
||||||
|
storage_policy.reload_storage_policies()
|
||||||
global config
|
global config
|
||||||
if constraints.SWIFT_CONSTRAINTS_LOADED:
|
if constraints.SWIFT_CONSTRAINTS_LOADED:
|
||||||
# Use the swift constraints that are loaded for the test framework
|
# Use the swift constraints that are loaded for the test framework
|
||||||
@ -344,7 +347,7 @@ def get_cluster_info():
|
|||||||
# test.conf data
|
# test.conf data
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
eff_constraints.update(cluster_info['swift'])
|
eff_constraints.update(cluster_info.get('swift', {}))
|
||||||
|
|
||||||
# Finally, we'll allow any constraint present in the swift-constraints
|
# Finally, we'll allow any constraint present in the swift-constraints
|
||||||
# section of test.conf to override everything. Note that only those
|
# section of test.conf to override everything. Note that only those
|
||||||
@ -620,6 +623,18 @@ def load_constraint(name):
|
|||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
def get_storage_policy_from_cluster_info(info):
|
||||||
|
policies = info['swift'].get('policies', {})
|
||||||
|
default_policy = []
|
||||||
|
non_default_policies = []
|
||||||
|
for p in policies:
|
||||||
|
if p.get('default', {}):
|
||||||
|
default_policy.append(p)
|
||||||
|
else:
|
||||||
|
non_default_policies.append(p)
|
||||||
|
return default_policy, non_default_policies
|
||||||
|
|
||||||
|
|
||||||
def reset_acl():
|
def reset_acl():
|
||||||
def post(url, token, parsed, conn):
|
def post(url, token, parsed, conn):
|
||||||
conn.request('POST', parsed.path, '', {
|
conn.request('POST', parsed.path, '', {
|
||||||
@ -650,3 +665,65 @@ def requires_acls(f):
|
|||||||
reset_acl()
|
reset_acl()
|
||||||
return rv
|
return rv
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionalStoragePolicyCollection(object):
|
||||||
|
|
||||||
|
def __init__(self, policies):
|
||||||
|
self._all = policies
|
||||||
|
self.default = None
|
||||||
|
for p in self:
|
||||||
|
if p.get('default', False):
|
||||||
|
assert self.default is None, 'Found multiple default ' \
|
||||||
|
'policies %r and %r' % (self.default, p)
|
||||||
|
self.default = p
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_info(cls, info=None):
|
||||||
|
if not (info or cluster_info):
|
||||||
|
get_cluster_info()
|
||||||
|
info = info or cluster_info
|
||||||
|
try:
|
||||||
|
policy_info = info['swift']['policies']
|
||||||
|
except KeyError:
|
||||||
|
raise AssertionError('Did not find any policy info in %r' % info)
|
||||||
|
policies = cls(policy_info)
|
||||||
|
assert policies.default, \
|
||||||
|
'Did not find default policy in %r' % policy_info
|
||||||
|
return policies
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._all)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._all)
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return self._all[index]
|
||||||
|
|
||||||
|
def filter(self, **kwargs):
|
||||||
|
return self.__class__([p for p in self if all(
|
||||||
|
p.get(k) == v for k, v in kwargs.items())])
|
||||||
|
|
||||||
|
def exclude(self, **kwargs):
|
||||||
|
return self.__class__([p for p in self if all(
|
||||||
|
p.get(k) != v for k, v in kwargs.items())])
|
||||||
|
|
||||||
|
def select(self):
|
||||||
|
return random.choice(self)
|
||||||
|
|
||||||
|
|
||||||
|
def requires_policies(f):
|
||||||
|
@functools.wraps(f)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
if skip:
|
||||||
|
raise SkipTest
|
||||||
|
try:
|
||||||
|
self.policies = FunctionalStoragePolicyCollection.from_info()
|
||||||
|
except AssertionError:
|
||||||
|
raise SkipTest("Unable to determine available policies")
|
||||||
|
if len(self.policies) < 2:
|
||||||
|
raise SkipTest("Multiple policies not enabled")
|
||||||
|
return f(self, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
@ -186,7 +186,7 @@ class Connection(object):
|
|||||||
"""
|
"""
|
||||||
status = self.make_request('GET', '/info',
|
status = self.make_request('GET', '/info',
|
||||||
cfg={'absolute_path': True})
|
cfg={'absolute_path': True})
|
||||||
if status == 404:
|
if status // 100 == 4:
|
||||||
return {}
|
return {}
|
||||||
if not 200 <= status <= 299:
|
if not 200 <= status <= 299:
|
||||||
raise ResponseError(self.response, 'GET', '/info')
|
raise ResponseError(self.response, 'GET', '/info')
|
||||||
|
@ -36,6 +36,36 @@ class TestAccount(unittest.TestCase):
|
|||||||
self.max_meta_overall_size = load_constraint('max_meta_overall_size')
|
self.max_meta_overall_size = load_constraint('max_meta_overall_size')
|
||||||
self.max_meta_value_length = load_constraint('max_meta_value_length')
|
self.max_meta_value_length = load_constraint('max_meta_value_length')
|
||||||
|
|
||||||
|
def head(url, token, parsed, conn):
|
||||||
|
conn.request('HEAD', parsed.path, '', {'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(head)
|
||||||
|
self.existing_metadata = set([
|
||||||
|
k for k, v in resp.getheaders() if
|
||||||
|
k.lower().startswith('x-account-meta')])
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
def head(url, token, parsed, conn):
|
||||||
|
conn.request('HEAD', parsed.path, '', {'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(head)
|
||||||
|
resp.read()
|
||||||
|
new_metadata = set(
|
||||||
|
[k for k, v in resp.getheaders() if
|
||||||
|
k.lower().startswith('x-account-meta')])
|
||||||
|
|
||||||
|
def clear_meta(url, token, parsed, conn, remove_metadata_keys):
|
||||||
|
headers = {'X-Auth-Token': token}
|
||||||
|
headers.update((k, '') for k in remove_metadata_keys)
|
||||||
|
conn.request('POST', parsed.path, '', headers)
|
||||||
|
return check_response(conn)
|
||||||
|
extra_metadata = list(self.existing_metadata ^ new_metadata)
|
||||||
|
for i in range(0, len(extra_metadata), 90):
|
||||||
|
batch = extra_metadata[i:i + 90]
|
||||||
|
resp = retry(clear_meta, batch)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status // 100, 2)
|
||||||
|
|
||||||
def test_metadata(self):
|
def test_metadata(self):
|
||||||
if tf.skip:
|
if tf.skip:
|
||||||
raise SkipTest
|
raise SkipTest
|
||||||
|
@ -21,7 +21,7 @@ from nose import SkipTest
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from test.functional import check_response, retry, requires_acls, \
|
from test.functional import check_response, retry, requires_acls, \
|
||||||
load_constraint
|
load_constraint, requires_policies
|
||||||
import test.functional as tf
|
import test.functional as tf
|
||||||
|
|
||||||
|
|
||||||
@ -31,6 +31,8 @@ class TestContainer(unittest.TestCase):
|
|||||||
if tf.skip:
|
if tf.skip:
|
||||||
raise SkipTest
|
raise SkipTest
|
||||||
self.name = uuid4().hex
|
self.name = uuid4().hex
|
||||||
|
# this container isn't created by default, but will be cleaned up
|
||||||
|
self.container = uuid4().hex
|
||||||
|
|
||||||
def put(url, token, parsed, conn):
|
def put(url, token, parsed, conn):
|
||||||
conn.request('PUT', parsed.path + '/' + self.name, '',
|
conn.request('PUT', parsed.path + '/' + self.name, '',
|
||||||
@ -50,38 +52,47 @@ class TestContainer(unittest.TestCase):
|
|||||||
if tf.skip:
|
if tf.skip:
|
||||||
raise SkipTest
|
raise SkipTest
|
||||||
|
|
||||||
def get(url, token, parsed, conn):
|
def get(url, token, parsed, conn, container):
|
||||||
conn.request('GET', parsed.path + '/' + self.name + '?format=json',
|
conn.request(
|
||||||
'', {'X-Auth-Token': token})
|
'GET', parsed.path + '/' + container + '?format=json', '',
|
||||||
return check_response(conn)
|
|
||||||
|
|
||||||
def delete(url, token, parsed, conn, obj):
|
|
||||||
conn.request('DELETE',
|
|
||||||
'/'.join([parsed.path, self.name, obj['name']]), '',
|
|
||||||
{'X-Auth-Token': token})
|
{'X-Auth-Token': token})
|
||||||
return check_response(conn)
|
return check_response(conn)
|
||||||
|
|
||||||
|
def delete(url, token, parsed, conn, container, obj):
|
||||||
|
conn.request(
|
||||||
|
'DELETE', '/'.join([parsed.path, container, obj['name']]), '',
|
||||||
|
{'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
|
||||||
|
for container in (self.name, self.container):
|
||||||
while True:
|
while True:
|
||||||
resp = retry(get)
|
resp = retry(get, container)
|
||||||
body = resp.read()
|
body = resp.read()
|
||||||
|
if resp.status == 404:
|
||||||
|
break
|
||||||
self.assert_(resp.status // 100 == 2, resp.status)
|
self.assert_(resp.status // 100 == 2, resp.status)
|
||||||
objs = json.loads(body)
|
objs = json.loads(body)
|
||||||
if not objs:
|
if not objs:
|
||||||
break
|
break
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
resp = retry(delete, obj)
|
resp = retry(delete, container, obj)
|
||||||
resp.read()
|
resp.read()
|
||||||
self.assertEqual(resp.status, 204)
|
self.assertEqual(resp.status, 204)
|
||||||
|
|
||||||
def delete(url, token, parsed, conn):
|
def delete(url, token, parsed, conn, container):
|
||||||
conn.request('DELETE', parsed.path + '/' + self.name, '',
|
conn.request('DELETE', parsed.path + '/' + container, '',
|
||||||
{'X-Auth-Token': token})
|
{'X-Auth-Token': token})
|
||||||
return check_response(conn)
|
return check_response(conn)
|
||||||
|
|
||||||
resp = retry(delete)
|
resp = retry(delete, self.name)
|
||||||
resp.read()
|
resp.read()
|
||||||
self.assertEqual(resp.status, 204)
|
self.assertEqual(resp.status, 204)
|
||||||
|
|
||||||
|
# container may have not been created
|
||||||
|
resp = retry(delete, self.container)
|
||||||
|
resp.read()
|
||||||
|
self.assert_(resp.status in (204, 404))
|
||||||
|
|
||||||
def test_multi_metadata(self):
|
def test_multi_metadata(self):
|
||||||
if tf.skip:
|
if tf.skip:
|
||||||
raise SkipTest
|
raise SkipTest
|
||||||
@ -1342,6 +1353,163 @@ class TestContainer(unittest.TestCase):
|
|||||||
self.assertEqual(resp.read(), 'Invalid UTF8 or contains NULL')
|
self.assertEqual(resp.read(), 'Invalid UTF8 or contains NULL')
|
||||||
self.assertEqual(resp.status, 412)
|
self.assertEqual(resp.status, 412)
|
||||||
|
|
||||||
|
def test_create_container_gets_default_policy_by_default(self):
|
||||||
|
try:
|
||||||
|
default_policy = \
|
||||||
|
tf.FunctionalStoragePolicyCollection.from_info().default
|
||||||
|
except AssertionError:
|
||||||
|
raise SkipTest()
|
||||||
|
|
||||||
|
def put(url, token, parsed, conn):
|
||||||
|
conn.request('PUT', parsed.path + '/' + self.container, '',
|
||||||
|
{'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(put)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status // 100, 2)
|
||||||
|
|
||||||
|
def head(url, token, parsed, conn):
|
||||||
|
conn.request('HEAD', parsed.path + '/' + self.container, '',
|
||||||
|
{'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(head)
|
||||||
|
resp.read()
|
||||||
|
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
||||||
|
self.assertEquals(headers.get('x-storage-policy'),
|
||||||
|
default_policy['name'])
|
||||||
|
|
||||||
|
def test_error_invalid_storage_policy_name(self):
|
||||||
|
def put(url, token, parsed, conn, headers):
|
||||||
|
new_headers = dict({'X-Auth-Token': token}, **headers)
|
||||||
|
conn.request('PUT', parsed.path + '/' + self.container, '',
|
||||||
|
new_headers)
|
||||||
|
return check_response(conn)
|
||||||
|
|
||||||
|
# create
|
||||||
|
resp = retry(put, {'X-Storage-Policy': uuid4().hex})
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 400)
|
||||||
|
|
||||||
|
@requires_policies
|
||||||
|
def test_create_non_default_storage_policy_container(self):
|
||||||
|
policy = self.policies.exclude(default=True).select()
|
||||||
|
|
||||||
|
def put(url, token, parsed, conn, headers=None):
|
||||||
|
base_headers = {'X-Auth-Token': token}
|
||||||
|
if headers:
|
||||||
|
base_headers.update(headers)
|
||||||
|
conn.request('PUT', parsed.path + '/' + self.container, '',
|
||||||
|
base_headers)
|
||||||
|
return check_response(conn)
|
||||||
|
headers = {'X-Storage-Policy': policy['name']}
|
||||||
|
resp = retry(put, headers=headers)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 201)
|
||||||
|
|
||||||
|
def head(url, token, parsed, conn):
|
||||||
|
conn.request('HEAD', parsed.path + '/' + self.container, '',
|
||||||
|
{'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(head)
|
||||||
|
resp.read()
|
||||||
|
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
||||||
|
self.assertEquals(headers.get('x-storage-policy'),
|
||||||
|
policy['name'])
|
||||||
|
|
||||||
|
# and test recreate with-out specifiying Storage Policy
|
||||||
|
resp = retry(put)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 202)
|
||||||
|
# should still be original storage policy
|
||||||
|
resp = retry(head)
|
||||||
|
resp.read()
|
||||||
|
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
||||||
|
self.assertEquals(headers.get('x-storage-policy'),
|
||||||
|
policy['name'])
|
||||||
|
|
||||||
|
# delete it
|
||||||
|
def delete(url, token, parsed, conn):
|
||||||
|
conn.request('DELETE', parsed.path + '/' + self.container, '',
|
||||||
|
{'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(delete)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 204)
|
||||||
|
|
||||||
|
# verify no policy header
|
||||||
|
resp = retry(head)
|
||||||
|
resp.read()
|
||||||
|
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
||||||
|
self.assertEquals(headers.get('x-storage-policy'), None)
|
||||||
|
|
||||||
|
@requires_policies
|
||||||
|
def test_conflict_change_storage_policy_with_put(self):
|
||||||
|
def put(url, token, parsed, conn, headers):
|
||||||
|
new_headers = dict({'X-Auth-Token': token}, **headers)
|
||||||
|
conn.request('PUT', parsed.path + '/' + self.container, '',
|
||||||
|
new_headers)
|
||||||
|
return check_response(conn)
|
||||||
|
|
||||||
|
# create
|
||||||
|
policy = self.policies.select()
|
||||||
|
resp = retry(put, {'X-Storage-Policy': policy['name']})
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 201)
|
||||||
|
|
||||||
|
# can't change it
|
||||||
|
other_policy = self.policies.exclude(name=policy['name']).select()
|
||||||
|
resp = retry(put, {'X-Storage-Policy': other_policy['name']})
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 409)
|
||||||
|
|
||||||
|
def head(url, token, parsed, conn):
|
||||||
|
conn.request('HEAD', parsed.path + '/' + self.container, '',
|
||||||
|
{'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
# still original policy
|
||||||
|
resp = retry(head)
|
||||||
|
resp.read()
|
||||||
|
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
||||||
|
self.assertEquals(headers.get('x-storage-policy'),
|
||||||
|
policy['name'])
|
||||||
|
|
||||||
|
@requires_policies
|
||||||
|
def test_noop_change_storage_policy_with_post(self):
|
||||||
|
def put(url, token, parsed, conn, headers):
|
||||||
|
new_headers = dict({'X-Auth-Token': token}, **headers)
|
||||||
|
conn.request('PUT', parsed.path + '/' + self.container, '',
|
||||||
|
new_headers)
|
||||||
|
return check_response(conn)
|
||||||
|
|
||||||
|
# create
|
||||||
|
policy = self.policies.select()
|
||||||
|
resp = retry(put, {'X-Storage-Policy': policy['name']})
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 201)
|
||||||
|
|
||||||
|
def post(url, token, parsed, conn, headers):
|
||||||
|
new_headers = dict({'X-Auth-Token': token}, **headers)
|
||||||
|
conn.request('POST', parsed.path + '/' + self.container, '',
|
||||||
|
new_headers)
|
||||||
|
return check_response(conn)
|
||||||
|
# attempt update
|
||||||
|
for header in ('X-Storage-Policy', 'X-Storage-Policy-Index'):
|
||||||
|
other_policy = self.policies.exclude(name=policy['name']).select()
|
||||||
|
resp = retry(post, {header: other_policy['name']})
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 204)
|
||||||
|
|
||||||
|
def head(url, token, parsed, conn):
|
||||||
|
conn.request('HEAD', parsed.path + '/' + self.container, '',
|
||||||
|
{'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
# still original policy
|
||||||
|
resp = retry(head)
|
||||||
|
resp.read()
|
||||||
|
headers = dict((k.lower(), v) for k, v in resp.getheaders())
|
||||||
|
self.assertEquals(headers.get('x-storage-policy'),
|
||||||
|
policy['name'])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -21,7 +21,8 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from swift.common.utils import json
|
from swift.common.utils import json
|
||||||
|
|
||||||
from test.functional import check_response, retry, requires_acls
|
from test.functional import check_response, retry, requires_acls, \
|
||||||
|
requires_policies
|
||||||
import test.functional as tf
|
import test.functional as tf
|
||||||
|
|
||||||
|
|
||||||
@ -32,13 +33,9 @@ class TestObject(unittest.TestCase):
|
|||||||
raise SkipTest
|
raise SkipTest
|
||||||
self.container = uuid4().hex
|
self.container = uuid4().hex
|
||||||
|
|
||||||
def put(url, token, parsed, conn):
|
self.containers = []
|
||||||
conn.request('PUT', parsed.path + '/' + self.container, '',
|
self._create_container(self.container)
|
||||||
{'X-Auth-Token': token})
|
|
||||||
return check_response(conn)
|
|
||||||
resp = retry(put)
|
|
||||||
resp.read()
|
|
||||||
self.assertEqual(resp.status, 201)
|
|
||||||
self.obj = uuid4().hex
|
self.obj = uuid4().hex
|
||||||
|
|
||||||
def put(url, token, parsed, conn):
|
def put(url, token, parsed, conn):
|
||||||
@ -50,40 +47,65 @@ class TestObject(unittest.TestCase):
|
|||||||
resp.read()
|
resp.read()
|
||||||
self.assertEqual(resp.status, 201)
|
self.assertEqual(resp.status, 201)
|
||||||
|
|
||||||
|
def _create_container(self, name=None, headers=None):
|
||||||
|
if not name:
|
||||||
|
name = uuid4().hex
|
||||||
|
self.containers.append(name)
|
||||||
|
headers = headers or {}
|
||||||
|
|
||||||
|
def put(url, token, parsed, conn, name):
|
||||||
|
new_headers = dict({'X-Auth-Token': token}, **headers)
|
||||||
|
conn.request('PUT', parsed.path + '/' + name, '',
|
||||||
|
new_headers)
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(put, name)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 201)
|
||||||
|
return name
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
if tf.skip:
|
if tf.skip:
|
||||||
raise SkipTest
|
raise SkipTest
|
||||||
|
|
||||||
def delete(url, token, parsed, conn, obj):
|
|
||||||
conn.request('DELETE',
|
|
||||||
'%s/%s/%s' % (parsed.path, self.container, obj),
|
|
||||||
'', {'X-Auth-Token': token})
|
|
||||||
return check_response(conn)
|
|
||||||
|
|
||||||
# get list of objects in container
|
# get list of objects in container
|
||||||
def list(url, token, parsed, conn):
|
def get(url, token, parsed, conn, container):
|
||||||
conn.request('GET',
|
conn.request(
|
||||||
'%s/%s' % (parsed.path, self.container),
|
'GET', parsed.path + '/' + container + '?format=json', '',
|
||||||
'', {'X-Auth-Token': token})
|
{'X-Auth-Token': token})
|
||||||
return check_response(conn)
|
return check_response(conn)
|
||||||
resp = retry(list)
|
|
||||||
object_listing = resp.read()
|
|
||||||
self.assertEqual(resp.status, 200)
|
|
||||||
|
|
||||||
# iterate over object listing and delete all objects
|
# delete an object
|
||||||
for obj in object_listing.splitlines():
|
def delete(url, token, parsed, conn, container, obj):
|
||||||
resp = retry(delete, obj)
|
conn.request(
|
||||||
|
'DELETE', '/'.join([parsed.path, container, obj['name']]), '',
|
||||||
|
{'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
|
||||||
|
for container in self.containers:
|
||||||
|
while True:
|
||||||
|
resp = retry(get, container)
|
||||||
|
body = resp.read()
|
||||||
|
if resp.status == 404:
|
||||||
|
break
|
||||||
|
self.assert_(resp.status // 100 == 2, resp.status)
|
||||||
|
objs = json.loads(body)
|
||||||
|
if not objs:
|
||||||
|
break
|
||||||
|
for obj in objs:
|
||||||
|
resp = retry(delete, container, obj)
|
||||||
resp.read()
|
resp.read()
|
||||||
self.assertEqual(resp.status, 204)
|
self.assertEqual(resp.status, 204)
|
||||||
|
|
||||||
# delete the container
|
# delete the container
|
||||||
def delete(url, token, parsed, conn):
|
def delete(url, token, parsed, conn, name):
|
||||||
conn.request('DELETE', parsed.path + '/' + self.container, '',
|
conn.request('DELETE', parsed.path + '/' + name, '',
|
||||||
{'X-Auth-Token': token})
|
{'X-Auth-Token': token})
|
||||||
return check_response(conn)
|
return check_response(conn)
|
||||||
resp = retry(delete)
|
|
||||||
|
for container in self.containers:
|
||||||
|
resp = retry(delete, container)
|
||||||
resp.read()
|
resp.read()
|
||||||
self.assertEqual(resp.status, 204)
|
self.assert_(resp.status in (204, 404))
|
||||||
|
|
||||||
def test_if_none_match(self):
|
def test_if_none_match(self):
|
||||||
def put(url, token, parsed, conn):
|
def put(url, token, parsed, conn):
|
||||||
@ -996,6 +1018,64 @@ class TestObject(unittest.TestCase):
|
|||||||
self.assertEquals(headers.get('access-control-allow-origin'),
|
self.assertEquals(headers.get('access-control-allow-origin'),
|
||||||
'http://m.com')
|
'http://m.com')
|
||||||
|
|
||||||
|
@requires_policies
|
||||||
|
def test_cross_policy_copy(self):
|
||||||
|
# create container in first policy
|
||||||
|
policy = self.policies.select()
|
||||||
|
container = self._create_container(
|
||||||
|
headers={'X-Storage-Policy': policy['name']})
|
||||||
|
obj = uuid4().hex
|
||||||
|
|
||||||
|
# create a container in second policy
|
||||||
|
other_policy = self.policies.exclude(name=policy['name']).select()
|
||||||
|
other_container = self._create_container(
|
||||||
|
headers={'X-Storage-Policy': other_policy['name']})
|
||||||
|
other_obj = uuid4().hex
|
||||||
|
|
||||||
|
def put_obj(url, token, parsed, conn, container, obj):
|
||||||
|
# to keep track of things, use the original path as the body
|
||||||
|
content = '%s/%s' % (container, obj)
|
||||||
|
path = '%s/%s' % (parsed.path, content)
|
||||||
|
conn.request('PUT', path, content, {'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
|
||||||
|
# create objects
|
||||||
|
for c, o in zip((container, other_container), (obj, other_obj)):
|
||||||
|
resp = retry(put_obj, c, o)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 201)
|
||||||
|
|
||||||
|
def put_copy_from(url, token, parsed, conn, container, obj, source):
|
||||||
|
dest_path = '%s/%s/%s' % (parsed.path, container, obj)
|
||||||
|
conn.request('PUT', dest_path, '',
|
||||||
|
{'X-Auth-Token': token,
|
||||||
|
'Content-Length': '0',
|
||||||
|
'X-Copy-From': source})
|
||||||
|
return check_response(conn)
|
||||||
|
|
||||||
|
copy_requests = (
|
||||||
|
(container, other_obj, '%s/%s' % (other_container, other_obj)),
|
||||||
|
(other_container, obj, '%s/%s' % (container, obj)),
|
||||||
|
)
|
||||||
|
|
||||||
|
# copy objects
|
||||||
|
for c, o, source in copy_requests:
|
||||||
|
resp = retry(put_copy_from, c, o, source)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 201)
|
||||||
|
|
||||||
|
def get_obj(url, token, parsed, conn, container, obj):
|
||||||
|
path = '%s/%s/%s' % (parsed.path, container, obj)
|
||||||
|
conn.request('GET', path, '', {'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
|
||||||
|
# validate contents, contents should be source
|
||||||
|
validate_requests = copy_requests
|
||||||
|
for c, o, body in validate_requests:
|
||||||
|
resp = retry(get_obj, c, o)
|
||||||
|
self.assertEqual(resp.status, 200)
|
||||||
|
self.assertEqual(body, resp.read())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -28,6 +28,8 @@ import uuid
|
|||||||
import eventlet
|
import eventlet
|
||||||
from nose import SkipTest
|
from nose import SkipTest
|
||||||
|
|
||||||
|
from swift.common.storage_policy import POLICY
|
||||||
|
|
||||||
from test.functional import normalized_urls, load_constraint, cluster_info
|
from test.functional import normalized_urls, load_constraint, cluster_info
|
||||||
import test.functional as tf
|
import test.functional as tf
|
||||||
from test.functional.swift_test_client import Account, Connection, File, \
|
from test.functional.swift_test_client import Account, Connection, File, \
|
||||||
@ -2077,6 +2079,61 @@ class TestObjectVersioningEnv(object):
|
|||||||
cls.versioning_enabled = 'versions' in container_info
|
cls.versioning_enabled = 'versions' in container_info
|
||||||
|
|
||||||
|
|
||||||
|
class TestCrossPolicyObjectVersioningEnv(object):
|
||||||
|
# tri-state: None initially, then True/False
|
||||||
|
versioning_enabled = None
|
||||||
|
multiple_policies_enabled = None
|
||||||
|
policies = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUp(cls):
|
||||||
|
cls.conn = Connection(tf.config)
|
||||||
|
cls.conn.authenticate()
|
||||||
|
|
||||||
|
if cls.multiple_policies_enabled is None:
|
||||||
|
try:
|
||||||
|
cls.policies = tf.FunctionalStoragePolicyCollection.from_info()
|
||||||
|
except AssertionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if cls.policies and len(cls.policies) > 1:
|
||||||
|
cls.multiple_policies_enabled = True
|
||||||
|
else:
|
||||||
|
cls.multiple_policies_enabled = False
|
||||||
|
# We have to lie here that versioning is enabled. We actually
|
||||||
|
# don't know, but it does not matter. We know these tests cannot
|
||||||
|
# run without multiple policies present. If multiple policies are
|
||||||
|
# present, we won't be setting this field to any value, so it
|
||||||
|
# should all still work.
|
||||||
|
cls.versioning_enabled = True
|
||||||
|
return
|
||||||
|
|
||||||
|
policy = cls.policies.select()
|
||||||
|
version_policy = cls.policies.exclude(name=policy['name']).select()
|
||||||
|
|
||||||
|
cls.account = Account(cls.conn, tf.config.get('account',
|
||||||
|
tf.config['username']))
|
||||||
|
|
||||||
|
# avoid getting a prefix that stops halfway through an encoded
|
||||||
|
# character
|
||||||
|
prefix = Utils.create_name().decode("utf-8")[:10].encode("utf-8")
|
||||||
|
|
||||||
|
cls.versions_container = cls.account.container(prefix + "-versions")
|
||||||
|
if not cls.versions_container.create(
|
||||||
|
{POLICY: policy['name']}):
|
||||||
|
raise ResponseError(cls.conn.response)
|
||||||
|
|
||||||
|
cls.container = cls.account.container(prefix + "-objs")
|
||||||
|
if not cls.container.create(
|
||||||
|
hdrs={'X-Versions-Location': cls.versions_container.name,
|
||||||
|
POLICY: version_policy['name']}):
|
||||||
|
raise ResponseError(cls.conn.response)
|
||||||
|
|
||||||
|
container_info = cls.container.info()
|
||||||
|
# if versioning is off, then X-Versions-Location won't persist
|
||||||
|
cls.versioning_enabled = 'versions' in container_info
|
||||||
|
|
||||||
|
|
||||||
class TestObjectVersioning(Base):
|
class TestObjectVersioning(Base):
|
||||||
env = TestObjectVersioningEnv
|
env = TestObjectVersioningEnv
|
||||||
set_up = False
|
set_up = False
|
||||||
@ -2127,6 +2184,21 @@ class TestObjectVersioningUTF8(Base2, TestObjectVersioning):
|
|||||||
set_up = False
|
set_up = False
|
||||||
|
|
||||||
|
|
||||||
|
class TestCrossPolicyObjectVersioning(TestObjectVersioning):
|
||||||
|
env = TestCrossPolicyObjectVersioningEnv
|
||||||
|
set_up = False
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCrossPolicyObjectVersioning, self).setUp()
|
||||||
|
if self.env.multiple_policies_enabled is False:
|
||||||
|
raise SkipTest('Cross policy test requires multiple policies')
|
||||||
|
elif self.env.multiple_policies_enabled is not True:
|
||||||
|
# just some sanity checking
|
||||||
|
raise Exception("Expected multiple_policies_enabled "
|
||||||
|
"to be True/False, got %r" % (
|
||||||
|
self.env.versioning_enabled,))
|
||||||
|
|
||||||
|
|
||||||
class TestTempurlEnv(object):
|
class TestTempurlEnv(object):
|
||||||
tempurl_enabled = None # tri-state: None initially, then True/False
|
tempurl_enabled = None # tri-state: None initially, then True/False
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user