Merge "account to account copy implementation"
This commit is contained in:
commit
8329095f11
@ -248,6 +248,31 @@ def check_utf8(string):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_path_header(req, name, length, error_msg):
|
||||||
|
"""
|
||||||
|
Validate that the value of path-like header is
|
||||||
|
well formatted. We assume the caller ensures that
|
||||||
|
specific header is present in req.headers.
|
||||||
|
|
||||||
|
:param req: HTTP request object
|
||||||
|
:param name: header name
|
||||||
|
:param length: length of path segment check
|
||||||
|
:param error_msg: error message for client
|
||||||
|
:returns: A tuple with path parts according to length
|
||||||
|
:raise: HTTPPreconditionFailed if header value
|
||||||
|
is not well formatted.
|
||||||
|
"""
|
||||||
|
src_header = unquote(req.headers.get(name))
|
||||||
|
if not src_header.startswith('/'):
|
||||||
|
src_header = '/' + src_header
|
||||||
|
try:
|
||||||
|
return utils.split_path(src_header, length, length, True)
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPPreconditionFailed(
|
||||||
|
request=req,
|
||||||
|
body=error_msg)
|
||||||
|
|
||||||
|
|
||||||
def check_copy_from_header(req):
|
def check_copy_from_header(req):
|
||||||
"""
|
"""
|
||||||
Validate that the value from x-copy-from header is
|
Validate that the value from x-copy-from header is
|
||||||
@ -259,13 +284,42 @@ def check_copy_from_header(req):
|
|||||||
:raise: HTTPPreconditionFailed if x-copy-from value
|
:raise: HTTPPreconditionFailed if x-copy-from value
|
||||||
is not well formatted.
|
is not well formatted.
|
||||||
"""
|
"""
|
||||||
src_header = unquote(req.headers.get('X-Copy-From'))
|
return check_path_header(req, 'X-Copy-From', 2,
|
||||||
if not src_header.startswith('/'):
|
'X-Copy-From header must be of the form '
|
||||||
src_header = '/' + src_header
|
'<container name>/<object name>')
|
||||||
try:
|
|
||||||
return utils.split_path(src_header, 2, 2, True)
|
|
||||||
except ValueError:
|
def check_destination_header(req):
|
||||||
|
"""
|
||||||
|
Validate that the value from destination header is
|
||||||
|
well formatted. We assume the caller ensures that
|
||||||
|
destination header is present in req.headers.
|
||||||
|
|
||||||
|
:param req: HTTP request object
|
||||||
|
:returns: A tuple with container name and object name
|
||||||
|
:raise: HTTPPreconditionFailed if destination value
|
||||||
|
is not well formatted.
|
||||||
|
"""
|
||||||
|
return check_path_header(req, 'Destination', 2,
|
||||||
|
'Destination header must be of the form '
|
||||||
|
'<container name>/<object name>')
|
||||||
|
|
||||||
|
|
||||||
|
def check_account_format(req, account):
|
||||||
|
"""
|
||||||
|
Validate that the header contains valid account name.
|
||||||
|
We assume the caller ensures that
|
||||||
|
destination header is present in req.headers.
|
||||||
|
|
||||||
|
:param req: HTTP request object
|
||||||
|
:returns: A properly encoded account name
|
||||||
|
:raise: HTTPPreconditionFailed if account header
|
||||||
|
is not well formatted.
|
||||||
|
"""
|
||||||
|
if isinstance(account, unicode):
|
||||||
|
account = account.encode('utf-8')
|
||||||
|
if '/' in account:
|
||||||
raise HTTPPreconditionFailed(
|
raise HTTPPreconditionFailed(
|
||||||
request=req,
|
request=req,
|
||||||
body='X-Copy-From header must be of the form'
|
body='Account name cannot contain slashes')
|
||||||
'<container name>/<object name>')
|
return account
|
||||||
|
@ -41,7 +41,8 @@ from swift.common.utils import (
|
|||||||
normalize_delete_at_timestamp, public, quorum_size, get_expirer_container)
|
normalize_delete_at_timestamp, public, quorum_size, get_expirer_container)
|
||||||
from swift.common.bufferedhttp import http_connect
|
from swift.common.bufferedhttp import http_connect
|
||||||
from swift.common.constraints import check_metadata, check_object_creation, \
|
from swift.common.constraints import check_metadata, check_object_creation, \
|
||||||
check_copy_from_header
|
check_copy_from_header, check_destination_header, \
|
||||||
|
check_account_format
|
||||||
from swift.common import constraints
|
from swift.common import constraints
|
||||||
from swift.common.exceptions import ChunkReadTimeout, \
|
from swift.common.exceptions import ChunkReadTimeout, \
|
||||||
ChunkWriteTimeout, ConnectionTimeout, ListingIterNotFound, \
|
ChunkWriteTimeout, ConnectionTimeout, ListingIterNotFound, \
|
||||||
@ -600,12 +601,15 @@ class ObjectController(Controller):
|
|||||||
if req.environ.get('swift.orig_req_method', req.method) != 'POST':
|
if req.environ.get('swift.orig_req_method', req.method) != 'POST':
|
||||||
req.environ.setdefault('swift.log_info', []).append(
|
req.environ.setdefault('swift.log_info', []).append(
|
||||||
'x-copy-from:%s' % source_header)
|
'x-copy-from:%s' % source_header)
|
||||||
src_container_name, src_obj_name = check_copy_from_header(req)
|
|
||||||
ver, acct, _rest = req.split_path(2, 3, True)
|
ver, acct, _rest = req.split_path(2, 3, True)
|
||||||
if isinstance(acct, unicode):
|
src_account_name = req.headers.get('X-Copy-From-Account', None)
|
||||||
acct = acct.encode('utf-8')
|
if src_account_name:
|
||||||
source_header = '/%s/%s/%s/%s' % (ver, acct,
|
src_account_name = check_account_format(req, src_account_name)
|
||||||
src_container_name, src_obj_name)
|
else:
|
||||||
|
src_account_name = acct
|
||||||
|
src_container_name, src_obj_name = check_copy_from_header(req)
|
||||||
|
source_header = '/%s/%s/%s/%s' % (ver, src_account_name,
|
||||||
|
src_container_name, src_obj_name)
|
||||||
source_req = req.copy_get()
|
source_req = req.copy_get()
|
||||||
|
|
||||||
# make sure the source request uses it's container_info
|
# make sure the source request uses it's container_info
|
||||||
@ -614,8 +618,10 @@ class ObjectController(Controller):
|
|||||||
source_req.headers['X-Newest'] = 'true'
|
source_req.headers['X-Newest'] = 'true'
|
||||||
orig_obj_name = self.object_name
|
orig_obj_name = self.object_name
|
||||||
orig_container_name = self.container_name
|
orig_container_name = self.container_name
|
||||||
|
orig_account_name = self.account_name
|
||||||
self.object_name = src_obj_name
|
self.object_name = src_obj_name
|
||||||
self.container_name = src_container_name
|
self.container_name = src_container_name
|
||||||
|
self.account_name = src_account_name
|
||||||
sink_req = Request.blank(req.path_info,
|
sink_req = Request.blank(req.path_info,
|
||||||
environ=req.environ, headers=req.headers)
|
environ=req.environ, headers=req.headers)
|
||||||
source_resp = self.GET(source_req)
|
source_resp = self.GET(source_req)
|
||||||
@ -633,6 +639,7 @@ class ObjectController(Controller):
|
|||||||
return source_resp
|
return source_resp
|
||||||
self.object_name = orig_obj_name
|
self.object_name = orig_obj_name
|
||||||
self.container_name = orig_container_name
|
self.container_name = orig_container_name
|
||||||
|
self.account_name = orig_account_name
|
||||||
data_source = iter(source_resp.app_iter)
|
data_source = iter(source_resp.app_iter)
|
||||||
sink_req.content_length = source_resp.content_length
|
sink_req.content_length = source_resp.content_length
|
||||||
if sink_req.content_length is None:
|
if sink_req.content_length is None:
|
||||||
@ -647,6 +654,8 @@ class ObjectController(Controller):
|
|||||||
|
|
||||||
# we no longer need the X-Copy-From header
|
# we no longer need the X-Copy-From header
|
||||||
del sink_req.headers['X-Copy-From']
|
del sink_req.headers['X-Copy-From']
|
||||||
|
if 'X-Copy-From-Account' in sink_req.headers:
|
||||||
|
del sink_req.headers['X-Copy-From-Account']
|
||||||
if not content_type_manually_set:
|
if not content_type_manually_set:
|
||||||
sink_req.headers['Content-Type'] = \
|
sink_req.headers['Content-Type'] = \
|
||||||
source_resp.headers['Content-Type']
|
source_resp.headers['Content-Type']
|
||||||
@ -771,8 +780,9 @@ class ObjectController(Controller):
|
|||||||
resp = self.best_response(req, statuses, reasons, bodies,
|
resp = self.best_response(req, statuses, reasons, bodies,
|
||||||
_('Object PUT'), etag=etag)
|
_('Object PUT'), etag=etag)
|
||||||
if source_header:
|
if source_header:
|
||||||
resp.headers['X-Copied-From'] = quote(
|
acct, path = source_header.split('/', 3)[2:4]
|
||||||
source_header.split('/', 3)[3])
|
resp.headers['X-Copied-From-Account'] = quote(acct)
|
||||||
|
resp.headers['X-Copied-From'] = quote(path)
|
||||||
if 'last-modified' in source_resp.headers:
|
if 'last-modified' in source_resp.headers:
|
||||||
resp.headers['X-Copied-From-Last-Modified'] = \
|
resp.headers['X-Copied-From-Last-Modified'] = \
|
||||||
source_resp.headers['last-modified']
|
source_resp.headers['last-modified']
|
||||||
@ -893,27 +903,25 @@ class ObjectController(Controller):
|
|||||||
@delay_denial
|
@delay_denial
|
||||||
def COPY(self, req):
|
def COPY(self, req):
|
||||||
"""HTTP COPY request handler."""
|
"""HTTP COPY request handler."""
|
||||||
dest = req.headers.get('Destination')
|
if not req.headers.get('Destination'):
|
||||||
if not dest:
|
|
||||||
return HTTPPreconditionFailed(request=req,
|
return HTTPPreconditionFailed(request=req,
|
||||||
body='Destination header required')
|
body='Destination header required')
|
||||||
dest = unquote(dest)
|
dest_account = self.account_name
|
||||||
if not dest.startswith('/'):
|
if 'Destination-Account' in req.headers:
|
||||||
dest = '/' + dest
|
dest_account = req.headers.get('Destination-Account')
|
||||||
try:
|
dest_account = check_account_format(req, dest_account)
|
||||||
_junk, dest_container, dest_object = dest.split('/', 2)
|
req.headers['X-Copy-From-Account'] = self.account_name
|
||||||
except ValueError:
|
self.account_name = dest_account
|
||||||
return HTTPPreconditionFailed(
|
del req.headers['Destination-Account']
|
||||||
request=req,
|
dest_container, dest_object = check_destination_header(req)
|
||||||
body='Destination header must be of the form '
|
source = '/%s/%s' % (self.container_name, self.object_name)
|
||||||
'<container name>/<object name>')
|
|
||||||
source = '/' + self.container_name + '/' + self.object_name
|
|
||||||
self.container_name = dest_container
|
self.container_name = dest_container
|
||||||
self.object_name = dest_object
|
self.object_name = dest_object
|
||||||
# re-write the existing request as a PUT instead of creating a new one
|
# re-write the existing request as a PUT instead of creating a new one
|
||||||
# since this one is already attached to the posthooklogger
|
# since this one is already attached to the posthooklogger
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
req.path_info = '/v1/' + self.account_name + dest
|
req.path_info = '/v1/%s/%s/%s' % \
|
||||||
|
(dest_account, dest_container, dest_object)
|
||||||
req.headers['Content-Length'] = 0
|
req.headers['Content-Length'] = 0
|
||||||
req.headers['X-Copy-From'] = quote(source)
|
req.headers['X-Copy-From'] = quote(source)
|
||||||
del req.headers['Destination']
|
del req.headers['Destination']
|
||||||
|
@ -357,7 +357,8 @@ class Application(object):
|
|||||||
# controller's method indicates it'd like to gather more
|
# controller's method indicates it'd like to gather more
|
||||||
# information and try again later.
|
# information and try again later.
|
||||||
resp = req.environ['swift.authorize'](req)
|
resp = req.environ['swift.authorize'](req)
|
||||||
if not resp:
|
if not resp and not req.headers.get('X-Copy-From-Account') \
|
||||||
|
and not req.headers.get('Destination-Account'):
|
||||||
# No resp means authorized, no delayed recheck required.
|
# No resp means authorized, no delayed recheck required.
|
||||||
del req.environ['swift.authorize']
|
del req.environ['swift.authorize']
|
||||||
else:
|
else:
|
||||||
|
@ -174,8 +174,10 @@ class Connection(object):
|
|||||||
# unicode and this would cause troubles when doing
|
# unicode and this would cause troubles when doing
|
||||||
# no_safe_quote query.
|
# no_safe_quote query.
|
||||||
self.storage_url = str('/%s/%s' % (x[3], x[4]))
|
self.storage_url = str('/%s/%s' % (x[3], x[4]))
|
||||||
|
self.account_name = str(x[4])
|
||||||
|
self.auth_user = auth_user
|
||||||
self.storage_token = storage_token
|
self.storage_token = storage_token
|
||||||
|
self.user_acl = '%s:%s' % (self.account, self.username)
|
||||||
|
|
||||||
self.http_connect()
|
self.http_connect()
|
||||||
return self.storage_url, self.storage_token
|
return self.storage_url, self.storage_token
|
||||||
@ -664,6 +666,32 @@ class File(Base):
|
|||||||
return self.conn.make_request('COPY', self.path, hdrs=headers,
|
return self.conn.make_request('COPY', self.path, hdrs=headers,
|
||||||
parms=parms) == 201
|
parms=parms) == 201
|
||||||
|
|
||||||
|
def copy_account(self, dest_account, dest_cont, dest_file,
|
||||||
|
hdrs=None, parms=None, cfg=None):
|
||||||
|
if hdrs is None:
|
||||||
|
hdrs = {}
|
||||||
|
if parms is None:
|
||||||
|
parms = {}
|
||||||
|
if cfg is None:
|
||||||
|
cfg = {}
|
||||||
|
if 'destination' in cfg:
|
||||||
|
headers = {'Destination': cfg['destination']}
|
||||||
|
elif cfg.get('no_destination'):
|
||||||
|
headers = {}
|
||||||
|
else:
|
||||||
|
headers = {'Destination-Account': dest_account,
|
||||||
|
'Destination': '%s/%s' % (dest_cont, dest_file)}
|
||||||
|
headers.update(hdrs)
|
||||||
|
|
||||||
|
if 'Destination-Account' in headers:
|
||||||
|
headers['Destination-Account'] = \
|
||||||
|
urllib.quote(headers['Destination-Account'])
|
||||||
|
if 'Destination' in headers:
|
||||||
|
headers['Destination'] = urllib.quote(headers['Destination'])
|
||||||
|
|
||||||
|
return self.conn.make_request('COPY', self.path, hdrs=headers,
|
||||||
|
parms=parms) == 201
|
||||||
|
|
||||||
def delete(self, hdrs=None, parms=None):
|
def delete(self, hdrs=None, parms=None):
|
||||||
if hdrs is None:
|
if hdrs is None:
|
||||||
hdrs = {}
|
hdrs = {}
|
||||||
|
@ -35,6 +35,7 @@ class TestObject(unittest.TestCase):
|
|||||||
|
|
||||||
self.containers = []
|
self.containers = []
|
||||||
self._create_container(self.container)
|
self._create_container(self.container)
|
||||||
|
self._create_container(self.container, use_account=2)
|
||||||
|
|
||||||
self.obj = uuid4().hex
|
self.obj = uuid4().hex
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ 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):
|
def _create_container(self, name=None, headers=None, use_account=1):
|
||||||
if not name:
|
if not name:
|
||||||
name = uuid4().hex
|
name = uuid4().hex
|
||||||
self.containers.append(name)
|
self.containers.append(name)
|
||||||
@ -58,7 +59,7 @@ class TestObject(unittest.TestCase):
|
|||||||
conn.request('PUT', parsed.path + '/' + name, '',
|
conn.request('PUT', parsed.path + '/' + name, '',
|
||||||
new_headers)
|
new_headers)
|
||||||
return check_response(conn)
|
return check_response(conn)
|
||||||
resp = retry(put, name)
|
resp = retry(put, name, use_account=use_account)
|
||||||
resp.read()
|
resp.read()
|
||||||
self.assertEqual(resp.status, 201)
|
self.assertEqual(resp.status, 201)
|
||||||
return name
|
return name
|
||||||
@ -246,6 +247,116 @@ class TestObject(unittest.TestCase):
|
|||||||
resp.read()
|
resp.read()
|
||||||
self.assertEqual(resp.status, 204)
|
self.assertEqual(resp.status, 204)
|
||||||
|
|
||||||
|
def test_copy_between_accounts(self):
|
||||||
|
if tf.skip:
|
||||||
|
raise SkipTest
|
||||||
|
|
||||||
|
source = '%s/%s' % (self.container, self.obj)
|
||||||
|
dest = '%s/%s' % (self.container, 'test_copy')
|
||||||
|
|
||||||
|
# get contents of source
|
||||||
|
def get_source(url, token, parsed, conn):
|
||||||
|
conn.request('GET',
|
||||||
|
'%s/%s' % (parsed.path, source),
|
||||||
|
'', {'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(get_source)
|
||||||
|
source_contents = resp.read()
|
||||||
|
self.assertEqual(resp.status, 200)
|
||||||
|
self.assertEqual(source_contents, 'test')
|
||||||
|
|
||||||
|
acct = tf.parsed[0].path.split('/', 2)[2]
|
||||||
|
|
||||||
|
# copy source to dest with X-Copy-From-Account
|
||||||
|
def put(url, token, parsed, conn):
|
||||||
|
conn.request('PUT', '%s/%s' % (parsed.path, dest), '',
|
||||||
|
{'X-Auth-Token': token,
|
||||||
|
'Content-Length': '0',
|
||||||
|
'X-Copy-From-Account': acct,
|
||||||
|
'X-Copy-From': source})
|
||||||
|
return check_response(conn)
|
||||||
|
# try to put, will not succeed
|
||||||
|
# user does not have permissions to read from source
|
||||||
|
resp = retry(put, use_account=2)
|
||||||
|
self.assertEqual(resp.status, 403)
|
||||||
|
|
||||||
|
# add acl to allow reading from source
|
||||||
|
def post(url, token, parsed, conn):
|
||||||
|
conn.request('POST', '%s/%s' % (parsed.path, self.container), '',
|
||||||
|
{'X-Auth-Token': token,
|
||||||
|
'X-Container-Read': tf.swift_test_perm[1]})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(post)
|
||||||
|
self.assertEqual(resp.status, 204)
|
||||||
|
|
||||||
|
# retry previous put, now should succeed
|
||||||
|
resp = retry(put, use_account=2)
|
||||||
|
self.assertEqual(resp.status, 201)
|
||||||
|
|
||||||
|
# contents of dest should be the same as source
|
||||||
|
def get_dest(url, token, parsed, conn):
|
||||||
|
conn.request('GET',
|
||||||
|
'%s/%s' % (parsed.path, dest),
|
||||||
|
'', {'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(get_dest, use_account=2)
|
||||||
|
dest_contents = resp.read()
|
||||||
|
self.assertEqual(resp.status, 200)
|
||||||
|
self.assertEqual(dest_contents, source_contents)
|
||||||
|
|
||||||
|
# delete the copy
|
||||||
|
def delete(url, token, parsed, conn):
|
||||||
|
conn.request('DELETE', '%s/%s' % (parsed.path, dest), '',
|
||||||
|
{'X-Auth-Token': token})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(delete, use_account=2)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 204)
|
||||||
|
# verify dest does not exist
|
||||||
|
resp = retry(get_dest, use_account=2)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 404)
|
||||||
|
|
||||||
|
acct_dest = tf.parsed[1].path.split('/', 2)[2]
|
||||||
|
|
||||||
|
# copy source to dest with COPY
|
||||||
|
def copy(url, token, parsed, conn):
|
||||||
|
conn.request('COPY', '%s/%s' % (parsed.path, source), '',
|
||||||
|
{'X-Auth-Token': token,
|
||||||
|
'Destination-Account': acct_dest,
|
||||||
|
'Destination': dest})
|
||||||
|
return check_response(conn)
|
||||||
|
# try to copy, will not succeed
|
||||||
|
# user does not have permissions to write to destination
|
||||||
|
resp = retry(copy)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 403)
|
||||||
|
|
||||||
|
# add acl to allow write to destination
|
||||||
|
def post(url, token, parsed, conn):
|
||||||
|
conn.request('POST', '%s/%s' % (parsed.path, self.container), '',
|
||||||
|
{'X-Auth-Token': token,
|
||||||
|
'X-Container-Write': tf.swift_test_perm[0]})
|
||||||
|
return check_response(conn)
|
||||||
|
resp = retry(post, use_account=2)
|
||||||
|
self.assertEqual(resp.status, 204)
|
||||||
|
|
||||||
|
# now copy will succeed
|
||||||
|
resp = retry(copy)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 201)
|
||||||
|
|
||||||
|
# contents of dest should be the same as source
|
||||||
|
resp = retry(get_dest, use_account=2)
|
||||||
|
dest_contents = resp.read()
|
||||||
|
self.assertEqual(resp.status, 200)
|
||||||
|
self.assertEqual(dest_contents, source_contents)
|
||||||
|
|
||||||
|
# delete the copy
|
||||||
|
resp = retry(delete, use_account=2)
|
||||||
|
resp.read()
|
||||||
|
self.assertEqual(resp.status, 204)
|
||||||
|
|
||||||
def test_public_object(self):
|
def test_public_object(self):
|
||||||
if tf.skip:
|
if tf.skip:
|
||||||
raise SkipTest
|
raise SkipTest
|
||||||
|
@ -25,6 +25,7 @@ import time
|
|||||||
import unittest
|
import unittest
|
||||||
import urllib
|
import urllib
|
||||||
import uuid
|
import uuid
|
||||||
|
from copy import deepcopy
|
||||||
import eventlet
|
import eventlet
|
||||||
from nose import SkipTest
|
from nose import SkipTest
|
||||||
|
|
||||||
@ -790,9 +791,22 @@ class TestFileEnv(object):
|
|||||||
def setUp(cls):
|
def setUp(cls):
|
||||||
cls.conn = Connection(tf.config)
|
cls.conn = Connection(tf.config)
|
||||||
cls.conn.authenticate()
|
cls.conn.authenticate()
|
||||||
|
cls.account = Account(cls.conn, tf.config.get('account',
|
||||||
|
tf.config['username']))
|
||||||
|
# creating another account and connection
|
||||||
|
# for account to account copy tests
|
||||||
|
config2 = deepcopy(tf.config)
|
||||||
|
config2['account'] = tf.config['account2']
|
||||||
|
config2['username'] = tf.config['username2']
|
||||||
|
config2['password'] = tf.config['password2']
|
||||||
|
cls.conn2 = Connection(config2)
|
||||||
|
cls.conn2.authenticate()
|
||||||
|
|
||||||
cls.account = Account(cls.conn, tf.config.get('account',
|
cls.account = Account(cls.conn, tf.config.get('account',
|
||||||
tf.config['username']))
|
tf.config['username']))
|
||||||
cls.account.delete_containers()
|
cls.account.delete_containers()
|
||||||
|
cls.account2 = cls.conn2.get_account()
|
||||||
|
cls.account2.delete_containers()
|
||||||
|
|
||||||
cls.container = cls.account.container(Utils.create_name())
|
cls.container = cls.account.container(Utils.create_name())
|
||||||
if not cls.container.create():
|
if not cls.container.create():
|
||||||
@ -846,6 +860,62 @@ class TestFile(Base):
|
|||||||
self.assert_(file_item.initialize())
|
self.assert_(file_item.initialize())
|
||||||
self.assert_(metadata == file_item.metadata)
|
self.assert_(metadata == file_item.metadata)
|
||||||
|
|
||||||
|
def testCopyAccount(self):
|
||||||
|
# makes sure to test encoded characters
|
||||||
|
source_filename = 'dealde%2Fl04 011e%204c8df/flash.png'
|
||||||
|
file_item = self.env.container.file(source_filename)
|
||||||
|
|
||||||
|
metadata = {Utils.create_ascii_name(): Utils.create_name()}
|
||||||
|
|
||||||
|
data = file_item.write_random()
|
||||||
|
file_item.sync_metadata(metadata)
|
||||||
|
|
||||||
|
dest_cont = self.env.account.container(Utils.create_name())
|
||||||
|
self.assert_(dest_cont.create())
|
||||||
|
|
||||||
|
acct = self.env.conn.account_name
|
||||||
|
# copy both from within and across containers
|
||||||
|
for cont in (self.env.container, dest_cont):
|
||||||
|
# copy both with and without initial slash
|
||||||
|
for prefix in ('', '/'):
|
||||||
|
dest_filename = Utils.create_name()
|
||||||
|
|
||||||
|
file_item = self.env.container.file(source_filename)
|
||||||
|
file_item.copy_account(acct,
|
||||||
|
'%s%s' % (prefix, cont),
|
||||||
|
dest_filename)
|
||||||
|
|
||||||
|
self.assert_(dest_filename in cont.files())
|
||||||
|
|
||||||
|
file_item = cont.file(dest_filename)
|
||||||
|
|
||||||
|
self.assert_(data == file_item.read())
|
||||||
|
self.assert_(file_item.initialize())
|
||||||
|
self.assert_(metadata == file_item.metadata)
|
||||||
|
|
||||||
|
dest_cont = self.env.account2.container(Utils.create_name())
|
||||||
|
self.assert_(dest_cont.create(hdrs={
|
||||||
|
'X-Container-Write': self.env.conn.user_acl
|
||||||
|
}))
|
||||||
|
|
||||||
|
acct = self.env.conn2.account_name
|
||||||
|
# copy both with and without initial slash
|
||||||
|
for prefix in ('', '/'):
|
||||||
|
dest_filename = Utils.create_name()
|
||||||
|
|
||||||
|
file_item = self.env.container.file(source_filename)
|
||||||
|
file_item.copy_account(acct,
|
||||||
|
'%s%s' % (prefix, dest_cont),
|
||||||
|
dest_filename)
|
||||||
|
|
||||||
|
self.assert_(dest_filename in dest_cont.files())
|
||||||
|
|
||||||
|
file_item = dest_cont.file(dest_filename)
|
||||||
|
|
||||||
|
self.assert_(data == file_item.read())
|
||||||
|
self.assert_(file_item.initialize())
|
||||||
|
self.assert_(metadata == file_item.metadata)
|
||||||
|
|
||||||
def testCopy404s(self):
|
def testCopy404s(self):
|
||||||
source_filename = Utils.create_name()
|
source_filename = Utils.create_name()
|
||||||
file_item = self.env.container.file(source_filename)
|
file_item = self.env.container.file(source_filename)
|
||||||
@ -884,6 +954,77 @@ class TestFile(Base):
|
|||||||
'%s%s' % (prefix, Utils.create_name()),
|
'%s%s' % (prefix, Utils.create_name()),
|
||||||
Utils.create_name()))
|
Utils.create_name()))
|
||||||
|
|
||||||
|
def testCopyAccount404s(self):
|
||||||
|
acct = self.env.conn.account_name
|
||||||
|
acct2 = self.env.conn2.account_name
|
||||||
|
source_filename = Utils.create_name()
|
||||||
|
file_item = self.env.container.file(source_filename)
|
||||||
|
file_item.write_random()
|
||||||
|
|
||||||
|
dest_cont = self.env.account.container(Utils.create_name())
|
||||||
|
self.assert_(dest_cont.create(hdrs={
|
||||||
|
'X-Container-Read': self.env.conn2.user_acl
|
||||||
|
}))
|
||||||
|
dest_cont2 = self.env.account2.container(Utils.create_name())
|
||||||
|
self.assert_(dest_cont2.create(hdrs={
|
||||||
|
'X-Container-Write': self.env.conn.user_acl,
|
||||||
|
'X-Container-Read': self.env.conn.user_acl
|
||||||
|
}))
|
||||||
|
|
||||||
|
for acct, cont in ((acct, dest_cont), (acct2, dest_cont2)):
|
||||||
|
for prefix in ('', '/'):
|
||||||
|
# invalid source container
|
||||||
|
source_cont = self.env.account.container(Utils.create_name())
|
||||||
|
file_item = source_cont.file(source_filename)
|
||||||
|
self.assert_(not file_item.copy_account(
|
||||||
|
acct,
|
||||||
|
'%s%s' % (prefix, self.env.container),
|
||||||
|
Utils.create_name()))
|
||||||
|
if acct == acct2:
|
||||||
|
# there is no such source container
|
||||||
|
# and foreign user can have no permission to read it
|
||||||
|
self.assert_status(403)
|
||||||
|
else:
|
||||||
|
self.assert_status(404)
|
||||||
|
|
||||||
|
self.assert_(not file_item.copy_account(
|
||||||
|
acct,
|
||||||
|
'%s%s' % (prefix, cont),
|
||||||
|
Utils.create_name()))
|
||||||
|
self.assert_status(404)
|
||||||
|
|
||||||
|
# invalid source object
|
||||||
|
file_item = self.env.container.file(Utils.create_name())
|
||||||
|
self.assert_(not file_item.copy_account(
|
||||||
|
acct,
|
||||||
|
'%s%s' % (prefix, self.env.container),
|
||||||
|
Utils.create_name()))
|
||||||
|
if acct == acct2:
|
||||||
|
# there is no such object
|
||||||
|
# and foreign user can have no permission to read it
|
||||||
|
self.assert_status(403)
|
||||||
|
else:
|
||||||
|
self.assert_status(404)
|
||||||
|
|
||||||
|
self.assert_(not file_item.copy_account(
|
||||||
|
acct,
|
||||||
|
'%s%s' % (prefix, cont),
|
||||||
|
Utils.create_name()))
|
||||||
|
self.assert_status(404)
|
||||||
|
|
||||||
|
# invalid destination container
|
||||||
|
file_item = self.env.container.file(source_filename)
|
||||||
|
self.assert_(not file_item.copy_account(
|
||||||
|
acct,
|
||||||
|
'%s%s' % (prefix, Utils.create_name()),
|
||||||
|
Utils.create_name()))
|
||||||
|
if acct == acct2:
|
||||||
|
# there is no such destination container
|
||||||
|
# and foreign user can have no permission to write there
|
||||||
|
self.assert_status(403)
|
||||||
|
else:
|
||||||
|
self.assert_status(404)
|
||||||
|
|
||||||
def testCopyNoDestinationHeader(self):
|
def testCopyNoDestinationHeader(self):
|
||||||
source_filename = Utils.create_name()
|
source_filename = Utils.create_name()
|
||||||
file_item = self.env.container.file(source_filename)
|
file_item = self.env.container.file(source_filename)
|
||||||
@ -938,6 +1079,49 @@ class TestFile(Base):
|
|||||||
self.assert_(file_item.initialize())
|
self.assert_(file_item.initialize())
|
||||||
self.assert_(metadata == file_item.metadata)
|
self.assert_(metadata == file_item.metadata)
|
||||||
|
|
||||||
|
def testCopyFromAccountHeader(self):
|
||||||
|
acct = self.env.conn.account_name
|
||||||
|
src_cont = self.env.account.container(Utils.create_name())
|
||||||
|
self.assert_(src_cont.create(hdrs={
|
||||||
|
'X-Container-Read': self.env.conn2.user_acl
|
||||||
|
}))
|
||||||
|
source_filename = Utils.create_name()
|
||||||
|
file_item = src_cont.file(source_filename)
|
||||||
|
|
||||||
|
metadata = {}
|
||||||
|
for i in range(1):
|
||||||
|
metadata[Utils.create_ascii_name()] = Utils.create_name()
|
||||||
|
file_item.metadata = metadata
|
||||||
|
|
||||||
|
data = file_item.write_random()
|
||||||
|
|
||||||
|
dest_cont = self.env.account.container(Utils.create_name())
|
||||||
|
self.assert_(dest_cont.create())
|
||||||
|
dest_cont2 = self.env.account2.container(Utils.create_name())
|
||||||
|
self.assert_(dest_cont2.create(hdrs={
|
||||||
|
'X-Container-Write': self.env.conn.user_acl
|
||||||
|
}))
|
||||||
|
|
||||||
|
for cont in (src_cont, dest_cont, dest_cont2):
|
||||||
|
# copy both with and without initial slash
|
||||||
|
for prefix in ('', '/'):
|
||||||
|
dest_filename = Utils.create_name()
|
||||||
|
|
||||||
|
file_item = cont.file(dest_filename)
|
||||||
|
file_item.write(hdrs={'X-Copy-From-Account': acct,
|
||||||
|
'X-Copy-From': '%s%s/%s' % (
|
||||||
|
prefix,
|
||||||
|
src_cont.name,
|
||||||
|
source_filename)})
|
||||||
|
|
||||||
|
self.assert_(dest_filename in cont.files())
|
||||||
|
|
||||||
|
file_item = cont.file(dest_filename)
|
||||||
|
|
||||||
|
self.assert_(data == file_item.read())
|
||||||
|
self.assert_(file_item.initialize())
|
||||||
|
self.assert_(metadata == file_item.metadata)
|
||||||
|
|
||||||
def testCopyFromHeader404s(self):
|
def testCopyFromHeader404s(self):
|
||||||
source_filename = Utils.create_name()
|
source_filename = Utils.create_name()
|
||||||
file_item = self.env.container.file(source_filename)
|
file_item = self.env.container.file(source_filename)
|
||||||
@ -969,6 +1153,52 @@ class TestFile(Base):
|
|||||||
self.env.container.name, source_filename)})
|
self.env.container.name, source_filename)})
|
||||||
self.assert_status(404)
|
self.assert_status(404)
|
||||||
|
|
||||||
|
def testCopyFromAccountHeader404s(self):
|
||||||
|
acct = self.env.conn2.account_name
|
||||||
|
src_cont = self.env.account2.container(Utils.create_name())
|
||||||
|
self.assert_(src_cont.create(hdrs={
|
||||||
|
'X-Container-Read': self.env.conn.user_acl
|
||||||
|
}))
|
||||||
|
source_filename = Utils.create_name()
|
||||||
|
file_item = src_cont.file(source_filename)
|
||||||
|
file_item.write_random()
|
||||||
|
dest_cont = self.env.account.container(Utils.create_name())
|
||||||
|
self.assert_(dest_cont.create())
|
||||||
|
|
||||||
|
for prefix in ('', '/'):
|
||||||
|
# invalid source container
|
||||||
|
file_item = dest_cont.file(Utils.create_name())
|
||||||
|
self.assertRaises(ResponseError, file_item.write,
|
||||||
|
hdrs={'X-Copy-From-Account': acct,
|
||||||
|
'X-Copy-From': '%s%s/%s' %
|
||||||
|
(prefix,
|
||||||
|
Utils.create_name(),
|
||||||
|
source_filename)})
|
||||||
|
# looks like cached responses leak "not found"
|
||||||
|
# to un-authorized users, not going to fix it now, but...
|
||||||
|
self.assert_status([403, 404])
|
||||||
|
|
||||||
|
# invalid source object
|
||||||
|
file_item = self.env.container.file(Utils.create_name())
|
||||||
|
self.assertRaises(ResponseError, file_item.write,
|
||||||
|
hdrs={'X-Copy-From-Account': acct,
|
||||||
|
'X-Copy-From': '%s%s/%s' %
|
||||||
|
(prefix,
|
||||||
|
src_cont,
|
||||||
|
Utils.create_name())})
|
||||||
|
self.assert_status(404)
|
||||||
|
|
||||||
|
# invalid destination container
|
||||||
|
dest_cont = self.env.account.container(Utils.create_name())
|
||||||
|
file_item = dest_cont.file(Utils.create_name())
|
||||||
|
self.assertRaises(ResponseError, file_item.write,
|
||||||
|
hdrs={'X-Copy-From-Account': acct,
|
||||||
|
'X-Copy-From': '%s%s/%s' %
|
||||||
|
(prefix,
|
||||||
|
src_cont,
|
||||||
|
source_filename)})
|
||||||
|
self.assert_status(404)
|
||||||
|
|
||||||
def testNameLimit(self):
|
def testNameLimit(self):
|
||||||
limit = load_constraint('max_object_name_length')
|
limit = load_constraint('max_object_name_length')
|
||||||
|
|
||||||
@ -1591,6 +1821,30 @@ class TestDlo(Base):
|
|||||||
file_contents,
|
file_contents,
|
||||||
"aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffff")
|
"aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffff")
|
||||||
|
|
||||||
|
def test_copy_account(self):
|
||||||
|
# dlo use same account and same container only
|
||||||
|
acct = self.env.conn.account_name
|
||||||
|
# Adding a new segment, copying the manifest, and then deleting the
|
||||||
|
# segment proves that the new object is really the concatenated
|
||||||
|
# segments and not just a manifest.
|
||||||
|
f_segment = self.env.container.file("%s/seg_lowerf" %
|
||||||
|
(self.env.segment_prefix))
|
||||||
|
f_segment.write('ffffffffff')
|
||||||
|
try:
|
||||||
|
man1_item = self.env.container.file('man1')
|
||||||
|
man1_item.copy_account(acct,
|
||||||
|
self.env.container.name,
|
||||||
|
"copied-man1")
|
||||||
|
finally:
|
||||||
|
# try not to leave this around for other tests to stumble over
|
||||||
|
f_segment.delete()
|
||||||
|
|
||||||
|
file_item = self.env.container.file('copied-man1')
|
||||||
|
file_contents = file_item.read()
|
||||||
|
self.assertEqual(
|
||||||
|
file_contents,
|
||||||
|
"aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffff")
|
||||||
|
|
||||||
def test_copy_manifest(self):
|
def test_copy_manifest(self):
|
||||||
# Copying the manifest should result in another manifest
|
# Copying the manifest should result in another manifest
|
||||||
try:
|
try:
|
||||||
@ -1787,6 +2041,14 @@ class TestSloEnv(object):
|
|||||||
def setUp(cls):
|
def setUp(cls):
|
||||||
cls.conn = Connection(tf.config)
|
cls.conn = Connection(tf.config)
|
||||||
cls.conn.authenticate()
|
cls.conn.authenticate()
|
||||||
|
config2 = deepcopy(tf.config)
|
||||||
|
config2['account'] = tf.config['account2']
|
||||||
|
config2['username'] = tf.config['username2']
|
||||||
|
config2['password'] = tf.config['password2']
|
||||||
|
cls.conn2 = Connection(config2)
|
||||||
|
cls.conn2.authenticate()
|
||||||
|
cls.account2 = cls.conn2.get_account()
|
||||||
|
cls.account2.delete_containers()
|
||||||
|
|
||||||
if cls.slo_enabled is None:
|
if cls.slo_enabled is None:
|
||||||
cls.slo_enabled = 'slo' in cluster_info
|
cls.slo_enabled = 'slo' in cluster_info
|
||||||
@ -1969,6 +2231,29 @@ class TestSlo(Base):
|
|||||||
copied_contents = copied.read(parms={'multipart-manifest': 'get'})
|
copied_contents = copied.read(parms={'multipart-manifest': 'get'})
|
||||||
self.assertEqual(4 * 1024 * 1024 + 1, len(copied_contents))
|
self.assertEqual(4 * 1024 * 1024 + 1, len(copied_contents))
|
||||||
|
|
||||||
|
def test_slo_copy_account(self):
|
||||||
|
acct = self.env.conn.account_name
|
||||||
|
# same account copy
|
||||||
|
file_item = self.env.container.file("manifest-abcde")
|
||||||
|
file_item.copy_account(acct, self.env.container.name, "copied-abcde")
|
||||||
|
|
||||||
|
copied = self.env.container.file("copied-abcde")
|
||||||
|
copied_contents = copied.read(parms={'multipart-manifest': 'get'})
|
||||||
|
self.assertEqual(4 * 1024 * 1024 + 1, len(copied_contents))
|
||||||
|
|
||||||
|
# copy to different account
|
||||||
|
acct = self.env.conn2.account_name
|
||||||
|
dest_cont = self.env.account2.container(Utils.create_name())
|
||||||
|
self.assert_(dest_cont.create(hdrs={
|
||||||
|
'X-Container-Write': self.env.conn.user_acl
|
||||||
|
}))
|
||||||
|
file_item = self.env.container.file("manifest-abcde")
|
||||||
|
file_item.copy_account(acct, dest_cont, "copied-abcde")
|
||||||
|
|
||||||
|
copied = dest_cont.file("copied-abcde")
|
||||||
|
copied_contents = copied.read(parms={'multipart-manifest': 'get'})
|
||||||
|
self.assertEqual(4 * 1024 * 1024 + 1, len(copied_contents))
|
||||||
|
|
||||||
def test_slo_copy_the_manifest(self):
|
def test_slo_copy_the_manifest(self):
|
||||||
file_item = self.env.container.file("manifest-abcde")
|
file_item = self.env.container.file("manifest-abcde")
|
||||||
file_item.copy(self.env.container.name, "copied-abcde-manifest-only",
|
file_item.copy(self.env.container.name, "copied-abcde-manifest-only",
|
||||||
@ -1981,6 +2266,40 @@ class TestSlo(Base):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
self.fail("COPY didn't copy the manifest (invalid json on GET)")
|
self.fail("COPY didn't copy the manifest (invalid json on GET)")
|
||||||
|
|
||||||
|
def test_slo_copy_the_manifest_account(self):
|
||||||
|
acct = self.env.conn.account_name
|
||||||
|
# same account
|
||||||
|
file_item = self.env.container.file("manifest-abcde")
|
||||||
|
file_item.copy_account(acct,
|
||||||
|
self.env.container.name,
|
||||||
|
"copied-abcde-manifest-only",
|
||||||
|
parms={'multipart-manifest': 'get'})
|
||||||
|
|
||||||
|
copied = self.env.container.file("copied-abcde-manifest-only")
|
||||||
|
copied_contents = copied.read(parms={'multipart-manifest': 'get'})
|
||||||
|
try:
|
||||||
|
json.loads(copied_contents)
|
||||||
|
except ValueError:
|
||||||
|
self.fail("COPY didn't copy the manifest (invalid json on GET)")
|
||||||
|
|
||||||
|
# different account
|
||||||
|
acct = self.env.conn2.account_name
|
||||||
|
dest_cont = self.env.account2.container(Utils.create_name())
|
||||||
|
self.assert_(dest_cont.create(hdrs={
|
||||||
|
'X-Container-Write': self.env.conn.user_acl
|
||||||
|
}))
|
||||||
|
file_item.copy_account(acct,
|
||||||
|
dest_cont,
|
||||||
|
"copied-abcde-manifest-only",
|
||||||
|
parms={'multipart-manifest': 'get'})
|
||||||
|
|
||||||
|
copied = dest_cont.file("copied-abcde-manifest-only")
|
||||||
|
copied_contents = copied.read(parms={'multipart-manifest': 'get'})
|
||||||
|
try:
|
||||||
|
json.loads(copied_contents)
|
||||||
|
except ValueError:
|
||||||
|
self.fail("COPY didn't copy the manifest (invalid json on GET)")
|
||||||
|
|
||||||
def test_slo_get_the_manifest(self):
|
def test_slo_get_the_manifest(self):
|
||||||
manifest = self.env.container.file("manifest-abcde")
|
manifest = self.env.container.file("manifest-abcde")
|
||||||
got_body = manifest.read(parms={'multipart-manifest': 'get'})
|
got_body = manifest.read(parms={'multipart-manifest': 'get'})
|
||||||
|
@ -260,6 +260,41 @@ class TestConstraints(unittest.TestCase):
|
|||||||
self.assertRaises(HTTPException,
|
self.assertRaises(HTTPException,
|
||||||
constraints.check_copy_from_header, req)
|
constraints.check_copy_from_header, req)
|
||||||
|
|
||||||
|
def test_validate_destination(self):
|
||||||
|
req = Request.blank(
|
||||||
|
'/v/a/c/o',
|
||||||
|
headers={'destination': 'c/o2'})
|
||||||
|
src_cont, src_obj = constraints.check_destination_header(req)
|
||||||
|
self.assertEqual(src_cont, 'c')
|
||||||
|
self.assertEqual(src_obj, 'o2')
|
||||||
|
req = Request.blank(
|
||||||
|
'/v/a/c/o',
|
||||||
|
headers={'destination': 'c/subdir/o2'})
|
||||||
|
src_cont, src_obj = constraints.check_destination_header(req)
|
||||||
|
self.assertEqual(src_cont, 'c')
|
||||||
|
self.assertEqual(src_obj, 'subdir/o2')
|
||||||
|
req = Request.blank(
|
||||||
|
'/v/a/c/o',
|
||||||
|
headers={'destination': '/c/o2'})
|
||||||
|
src_cont, src_obj = constraints.check_destination_header(req)
|
||||||
|
self.assertEqual(src_cont, 'c')
|
||||||
|
self.assertEqual(src_obj, 'o2')
|
||||||
|
|
||||||
|
def test_validate_bad_destination(self):
|
||||||
|
req = Request.blank(
|
||||||
|
'/v/a/c/o',
|
||||||
|
headers={'destination': 'bad_object'})
|
||||||
|
self.assertRaises(HTTPException,
|
||||||
|
constraints.check_destination_header, req)
|
||||||
|
|
||||||
|
def test_check_account_format(self):
|
||||||
|
req = Request.blank(
|
||||||
|
'/v/a/c/o',
|
||||||
|
headers={'X-Copy-From-Account': 'account/with/slashes'})
|
||||||
|
self.assertRaises(HTTPException,
|
||||||
|
constraints.check_account_format,
|
||||||
|
req, req.headers['X-Copy-From-Account'])
|
||||||
|
|
||||||
|
|
||||||
class TestConstraintsConfig(unittest.TestCase):
|
class TestConstraintsConfig(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -179,6 +179,20 @@ def do_setup(the_object_server):
|
|||||||
'x-trans-id': 'test'})
|
'x-trans-id': 'test'})
|
||||||
resp = conn.getresponse()
|
resp = conn.getresponse()
|
||||||
assert(resp.status == 201)
|
assert(resp.status == 201)
|
||||||
|
# Create another account
|
||||||
|
# used for account-to-account tests
|
||||||
|
ts = normalize_timestamp(time.time())
|
||||||
|
partition, nodes = prosrv.account_ring.get_nodes('a1')
|
||||||
|
for node in nodes:
|
||||||
|
conn = swift.proxy.controllers.obj.http_connect(node['ip'],
|
||||||
|
node['port'],
|
||||||
|
node['device'],
|
||||||
|
partition, 'PUT',
|
||||||
|
'/a1',
|
||||||
|
{'X-Timestamp': ts,
|
||||||
|
'x-trans-id': 'test'})
|
||||||
|
resp = conn.getresponse()
|
||||||
|
assert(resp.status == 201)
|
||||||
# Create containers, 1 per test policy
|
# Create containers, 1 per test policy
|
||||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||||
fd = sock.makefile()
|
fd = sock.makefile()
|
||||||
@ -188,6 +202,18 @@ def do_setup(the_object_server):
|
|||||||
fd.flush()
|
fd.flush()
|
||||||
headers = readuntil2crlfs(fd)
|
headers = readuntil2crlfs(fd)
|
||||||
exp = 'HTTP/1.1 201'
|
exp = 'HTTP/1.1 201'
|
||||||
|
assert headers[:len(exp)] == exp, "Expected '%s', encountered '%s'" % (
|
||||||
|
exp, headers[:len(exp)])
|
||||||
|
# Create container in other account
|
||||||
|
# used for account-to-account tests
|
||||||
|
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('PUT /v1/a1/c1 HTTP/1.1\r\nHost: localhost\r\n'
|
||||||
|
'Connection: close\r\nX-Auth-Token: t\r\n'
|
||||||
|
'Content-Length: 0\r\n\r\n')
|
||||||
|
fd.flush()
|
||||||
|
headers = readuntil2crlfs(fd)
|
||||||
|
exp = 'HTTP/1.1 201'
|
||||||
assert headers[:len(exp)] == exp, "Expected '%s', encountered '%s'" % (
|
assert headers[:len(exp)] == exp, "Expected '%s', encountered '%s'" % (
|
||||||
exp, headers[:len(exp)])
|
exp, headers[:len(exp)])
|
||||||
|
|
||||||
@ -2909,6 +2935,19 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
||||||
|
|
||||||
|
def test_basic_put_with_x_copy_from_account(self):
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': 'c/o',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acc1 con1 objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_basic_put_with_x_copy_from_across_container(self):
|
def test_basic_put_with_x_copy_from_across_container(self):
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Content-Length': '0',
|
headers={'Content-Length': '0',
|
||||||
@ -2920,6 +2959,19 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c2/o')
|
self.assertEquals(resp.headers['x-copied-from'], 'c2/o')
|
||||||
|
|
||||||
|
def test_basic_put_with_x_copy_from_across_container_and_account(self):
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': 'c2/o',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acc1 con1 objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c2/o')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_copy_non_zero_content_length(self):
|
def test_copy_non_zero_content_length(self):
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Content-Length': '5',
|
headers={'Content-Length': '5',
|
||||||
@ -2930,6 +2982,17 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = controller.PUT(req)
|
resp = controller.PUT(req)
|
||||||
self.assertEquals(resp.status_int, 400)
|
self.assertEquals(resp.status_int, 400)
|
||||||
|
|
||||||
|
def test_copy_non_zero_content_length_with_account(self):
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '5',
|
||||||
|
'X-Copy-From': 'c/o',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200)
|
||||||
|
# acct cont
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 400)
|
||||||
|
|
||||||
def test_copy_with_slashes_in_x_copy_from(self):
|
def test_copy_with_slashes_in_x_copy_from(self):
|
||||||
# extra source path parsing
|
# extra source path parsing
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
@ -2942,6 +3005,20 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
||||||
|
|
||||||
|
def test_copy_with_slashes_in_x_copy_from_and_account(self):
|
||||||
|
# extra source path parsing
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': 'c/o/o2',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acc1 con1 objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_copy_with_spaces_in_x_copy_from(self):
|
def test_copy_with_spaces_in_x_copy_from(self):
|
||||||
# space in soure path
|
# space in soure path
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
@ -2954,6 +3031,20 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o%20o2')
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o%20o2')
|
||||||
|
|
||||||
|
def test_copy_with_spaces_in_x_copy_from_and_account(self):
|
||||||
|
# space in soure path
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': 'c/o%20o2',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acc1 con1 objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o%20o2')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_copy_with_leading_slash_in_x_copy_from(self):
|
def test_copy_with_leading_slash_in_x_copy_from(self):
|
||||||
# repeat tests with leading /
|
# repeat tests with leading /
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
@ -2966,6 +3057,20 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
||||||
|
|
||||||
|
def test_copy_with_leading_slash_in_x_copy_from_and_account(self):
|
||||||
|
# repeat tests with leading /
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': '/c/o',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acc1 con1 objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_copy_with_leading_slash_and_slashes_in_x_copy_from(self):
|
def test_copy_with_leading_slash_and_slashes_in_x_copy_from(self):
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Content-Length': '0',
|
headers={'Content-Length': '0',
|
||||||
@ -2977,6 +3082,19 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
||||||
|
|
||||||
|
def test_copy_with_leading_slash_and_slashes_in_x_copy_from_acct(self):
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': '/c/o/o2',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acc1 con1 objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_copy_with_no_object_in_x_copy_from(self):
|
def test_copy_with_no_object_in_x_copy_from(self):
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Content-Length': '0',
|
headers={'Content-Length': '0',
|
||||||
@ -2992,6 +3110,22 @@ class TestObjectController(unittest.TestCase):
|
|||||||
raise self.fail('Invalid X-Copy-From did not raise '
|
raise self.fail('Invalid X-Copy-From did not raise '
|
||||||
'client error')
|
'client error')
|
||||||
|
|
||||||
|
def test_copy_with_no_object_in_x_copy_from_and_account(self):
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': '/c',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200)
|
||||||
|
# acct cont
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
try:
|
||||||
|
controller.PUT(req)
|
||||||
|
except HTTPException as resp:
|
||||||
|
self.assertEquals(resp.status_int // 100, 4) # client error
|
||||||
|
else:
|
||||||
|
raise self.fail('Invalid X-Copy-From did not raise '
|
||||||
|
'client error')
|
||||||
|
|
||||||
def test_copy_server_error_reading_source(self):
|
def test_copy_server_error_reading_source(self):
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Content-Length': '0',
|
headers={'Content-Length': '0',
|
||||||
@ -3002,6 +3136,17 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = controller.PUT(req)
|
resp = controller.PUT(req)
|
||||||
self.assertEquals(resp.status_int, 503)
|
self.assertEquals(resp.status_int, 503)
|
||||||
|
|
||||||
|
def test_copy_server_error_reading_source_and_account(self):
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': '/c/o',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200, 200, 200, 503, 503, 503)
|
||||||
|
# acct cont acct cont objc objc objc
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 503)
|
||||||
|
|
||||||
def test_copy_not_found_reading_source(self):
|
def test_copy_not_found_reading_source(self):
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Content-Length': '0',
|
headers={'Content-Length': '0',
|
||||||
@ -3013,6 +3158,18 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = controller.PUT(req)
|
resp = controller.PUT(req)
|
||||||
self.assertEquals(resp.status_int, 404)
|
self.assertEquals(resp.status_int, 404)
|
||||||
|
|
||||||
|
def test_copy_not_found_reading_source_and_account(self):
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': '/c/o',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
# not found
|
||||||
|
status_list = (200, 200, 200, 200, 404, 404, 404)
|
||||||
|
# acct cont acct cont objc objc objc
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 404)
|
||||||
|
|
||||||
def test_copy_with_some_missing_sources(self):
|
def test_copy_with_some_missing_sources(self):
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Content-Length': '0',
|
headers={'Content-Length': '0',
|
||||||
@ -3023,6 +3180,17 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = controller.PUT(req)
|
resp = controller.PUT(req)
|
||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
|
||||||
|
def test_copy_with_some_missing_sources_and_account(self):
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': '/c/o',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
status_list = (200, 200, 200, 200, 404, 404, 200, 201, 201, 201)
|
||||||
|
# acct cont acct cont objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
|
||||||
def test_copy_with_object_metadata(self):
|
def test_copy_with_object_metadata(self):
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Content-Length': '0',
|
headers={'Content-Length': '0',
|
||||||
@ -3038,6 +3206,22 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
|
self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
|
||||||
self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
|
self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
|
||||||
|
|
||||||
|
def test_copy_with_object_metadata_and_account(self):
|
||||||
|
req = Request.blank('/v1/a1/c1/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
|
headers={'Content-Length': '0',
|
||||||
|
'X-Copy-From': '/c/o',
|
||||||
|
'X-Object-Meta-Ours': 'okay',
|
||||||
|
'X-Copy-From-Account': 'a'})
|
||||||
|
# test object metadata
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acct cont objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.PUT(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers.get('x-object-meta-test'), 'testing')
|
||||||
|
self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
|
||||||
|
self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
|
||||||
|
|
||||||
def test_copy_source_larger_than_max_file_size(self):
|
def test_copy_source_larger_than_max_file_size(self):
|
||||||
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Content-Length': '0',
|
headers={'Content-Length': '0',
|
||||||
@ -3075,6 +3259,19 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
||||||
|
|
||||||
|
def test_basic_COPY_account(self):
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': 'c1/o2',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acct cont objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_COPY_across_containers(self):
|
def test_COPY_across_containers(self):
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'COPY'},
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
@ -3097,6 +3294,19 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
||||||
|
|
||||||
|
def test_COPY_account_source_with_slashes_in_name(self):
|
||||||
|
req = Request.blank('/v1/a/c/o/o2',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': 'c1/o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acct cont objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_COPY_destination_leading_slash(self):
|
def test_COPY_destination_leading_slash(self):
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'COPY'},
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
@ -3108,6 +3318,19 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
||||||
|
|
||||||
|
def test_COPY_account_destination_leading_slash(self):
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': '/c1/o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acct cont objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_COPY_source_with_slashes_destination_leading_slash(self):
|
def test_COPY_source_with_slashes_destination_leading_slash(self):
|
||||||
req = Request.blank('/v1/a/c/o/o2',
|
req = Request.blank('/v1/a/c/o/o2',
|
||||||
environ={'REQUEST_METHOD': 'COPY'},
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
@ -3119,14 +3342,35 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
||||||
|
|
||||||
|
def test_COPY_account_source_with_slashes_destination_leading_slash(self):
|
||||||
|
req = Request.blank('/v1/a/c/o/o2',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': '/c1/o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acct cont objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-account'], 'a')
|
||||||
|
|
||||||
def test_COPY_no_object_in_destination(self):
|
def test_COPY_no_object_in_destination(self):
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'COPY'},
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
headers={'Destination': 'c_o'})
|
headers={'Destination': 'c_o'})
|
||||||
status_list = [] # no requests needed
|
status_list = [] # no requests needed
|
||||||
with self.controller_context(req, *status_list) as controller:
|
with self.controller_context(req, *status_list) as controller:
|
||||||
resp = controller.COPY(req)
|
self.assertRaises(HTTPException, controller.COPY, req)
|
||||||
self.assertEquals(resp.status_int, 412)
|
|
||||||
|
def test_COPY_account_no_object_in_destination(self):
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': 'c_o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
status_list = [] # no requests needed
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
self.assertRaises(HTTPException, controller.COPY, req)
|
||||||
|
|
||||||
def test_COPY_server_error_reading_source(self):
|
def test_COPY_server_error_reading_source(self):
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
@ -3138,6 +3382,17 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = controller.COPY(req)
|
resp = controller.COPY(req)
|
||||||
self.assertEquals(resp.status_int, 503)
|
self.assertEquals(resp.status_int, 503)
|
||||||
|
|
||||||
|
def test_COPY_account_server_error_reading_source(self):
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': '/c1/o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
status_list = (200, 200, 200, 200, 503, 503, 503)
|
||||||
|
# acct cont acct cont objc objc objc
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 503)
|
||||||
|
|
||||||
def test_COPY_not_found_reading_source(self):
|
def test_COPY_not_found_reading_source(self):
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'COPY'},
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
@ -3148,6 +3403,17 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = controller.COPY(req)
|
resp = controller.COPY(req)
|
||||||
self.assertEquals(resp.status_int, 404)
|
self.assertEquals(resp.status_int, 404)
|
||||||
|
|
||||||
|
def test_COPY_account_not_found_reading_source(self):
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': '/c1/o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
status_list = (200, 200, 200, 200, 404, 404, 404)
|
||||||
|
# acct cont acct cont objc objc objc
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 404)
|
||||||
|
|
||||||
def test_COPY_with_some_missing_sources(self):
|
def test_COPY_with_some_missing_sources(self):
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'COPY'},
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
@ -3158,6 +3424,17 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = controller.COPY(req)
|
resp = controller.COPY(req)
|
||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
|
||||||
|
def test_COPY_account_with_some_missing_sources(self):
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': '/c1/o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
status_list = (200, 200, 200, 200, 404, 404, 200, 201, 201, 201)
|
||||||
|
# acct cont acct cont objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
|
||||||
def test_COPY_with_metadata(self):
|
def test_COPY_with_metadata(self):
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'COPY'},
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
@ -3173,6 +3450,22 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
|
self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
|
||||||
self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
|
self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
|
||||||
|
|
||||||
|
def test_COPY_account_with_metadata(self):
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': '/c1/o',
|
||||||
|
'X-Object-Meta-Ours': 'okay',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
status_list = (200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
# acct cont acct cont objc objc objc obj obj obj
|
||||||
|
with self.controller_context(req, *status_list) as controller:
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers.get('x-object-meta-test'),
|
||||||
|
'testing')
|
||||||
|
self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
|
||||||
|
self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
|
||||||
|
|
||||||
def test_COPY_source_larger_than_max_file_size(self):
|
def test_COPY_source_larger_than_max_file_size(self):
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'COPY'},
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
@ -3195,6 +3488,29 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = controller.COPY(req)
|
resp = controller.COPY(req)
|
||||||
self.assertEquals(resp.status_int, 413)
|
self.assertEquals(resp.status_int, 413)
|
||||||
|
|
||||||
|
def test_COPY_account_source_larger_than_max_file_size(self):
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': '/c1/o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
|
||||||
|
class LargeResponseBody(object):
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return constraints.MAX_FILE_SIZE + 1
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
copy_from_obj_body = LargeResponseBody()
|
||||||
|
status_list = (200, 200, 200, 200, 200)
|
||||||
|
# acct cont objc objc objc
|
||||||
|
kwargs = dict(body=copy_from_obj_body)
|
||||||
|
with self.controller_context(req, *status_list,
|
||||||
|
**kwargs) as controller:
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 413)
|
||||||
|
|
||||||
def test_COPY_newest(self):
|
def test_COPY_newest(self):
|
||||||
with save_globals():
|
with save_globals():
|
||||||
controller = proxy_server.ObjectController(self.app, 'a', 'c', 'o')
|
controller = proxy_server.ObjectController(self.app, 'a', 'c', 'o')
|
||||||
@ -3213,6 +3529,25 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(resp.headers['x-copied-from-last-modified'],
|
self.assertEquals(resp.headers['x-copied-from-last-modified'],
|
||||||
'3')
|
'3')
|
||||||
|
|
||||||
|
def test_COPY_account_newest(self):
|
||||||
|
with save_globals():
|
||||||
|
controller = proxy_server.ObjectController(self.app, 'a', 'c', 'o')
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': '/c1/o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
req.account = 'a'
|
||||||
|
controller.object_name = 'o'
|
||||||
|
set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201,
|
||||||
|
#act cont acct cont objc objc objc obj obj obj
|
||||||
|
timestamps=('1', '1', '1', '1', '3', '2', '1',
|
||||||
|
'4', '4', '4'))
|
||||||
|
self.app.memcache.store = {}
|
||||||
|
resp = controller.COPY(req)
|
||||||
|
self.assertEquals(resp.status_int, 201)
|
||||||
|
self.assertEquals(resp.headers['x-copied-from-last-modified'],
|
||||||
|
'3')
|
||||||
|
|
||||||
def test_COPY_delete_at(self):
|
def test_COPY_delete_at(self):
|
||||||
with save_globals():
|
with save_globals():
|
||||||
given_headers = {}
|
given_headers = {}
|
||||||
@ -3238,6 +3573,32 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertTrue('X-Delete-At-Partition' in given_headers)
|
self.assertTrue('X-Delete-At-Partition' in given_headers)
|
||||||
self.assertTrue('X-Delete-At-Container' in given_headers)
|
self.assertTrue('X-Delete-At-Container' in given_headers)
|
||||||
|
|
||||||
|
def test_COPY_account_delete_at(self):
|
||||||
|
with save_globals():
|
||||||
|
given_headers = {}
|
||||||
|
|
||||||
|
def fake_connect_put_node(nodes, part, path, headers,
|
||||||
|
logger_thread_locals):
|
||||||
|
given_headers.update(headers)
|
||||||
|
|
||||||
|
controller = proxy_server.ObjectController(self.app, 'a',
|
||||||
|
'c', 'o')
|
||||||
|
controller._connect_put_node = fake_connect_put_node
|
||||||
|
set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
|
||||||
|
self.app.memcache.store = {}
|
||||||
|
req = Request.blank('/v1/a/c/o',
|
||||||
|
environ={'REQUEST_METHOD': 'COPY'},
|
||||||
|
headers={'Destination': '/c1/o',
|
||||||
|
'Destination-Account': 'a1'})
|
||||||
|
|
||||||
|
self.app.update_request(req)
|
||||||
|
controller.COPY(req)
|
||||||
|
self.assertEquals(given_headers.get('X-Delete-At'), '9876543210')
|
||||||
|
self.assertTrue('X-Delete-At-Host' in given_headers)
|
||||||
|
self.assertTrue('X-Delete-At-Device' in given_headers)
|
||||||
|
self.assertTrue('X-Delete-At-Partition' in given_headers)
|
||||||
|
self.assertTrue('X-Delete-At-Container' in given_headers)
|
||||||
|
|
||||||
def test_chunked_put(self):
|
def test_chunked_put(self):
|
||||||
|
|
||||||
class ChunkedFile(object):
|
class ChunkedFile(object):
|
||||||
|
Loading…
Reference in New Issue
Block a user