a7c8becd4e
When the "copy" middleware tries to copy a segmented object which is bigger than max_file_size, it immediatly returns "413 Request Entity Too Large". But at that point, connections have already been established by the proxy server to the object servers. These connections must be closed before returning. Closes-Bug: #1698207 Change-Id: I430c48c4a81e8392fa271160bcbc1817ef0a88f7
1697 lines
81 KiB
Python
1697 lines
81 KiB
Python
#!/usr/bin/env python
|
|
# Copyright (c) 2015 OpenStack Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import time
|
|
import mock
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
from hashlib import md5
|
|
from six.moves import urllib
|
|
from textwrap import dedent
|
|
|
|
from swift.common import swob
|
|
from swift.common.middleware import copy
|
|
from swift.common.storage_policy import POLICIES
|
|
from swift.common.swob import Request, HTTPException
|
|
from swift.common.utils import closing_if_possible
|
|
from test.unit import patch_policies, debug_logger, FakeMemcache, FakeRing
|
|
from test.unit.common.middleware.helpers import FakeSwift
|
|
from test.unit.proxy.controllers.test_obj import set_http_connect, \
|
|
PatchedObjControllerApp
|
|
|
|
|
|
class TestCopyConstraints(unittest.TestCase):
|
|
def test_validate_copy_from(self):
|
|
req = Request.blank(
|
|
'/v/a/c/o',
|
|
headers={'x-copy-from': 'c/o2'})
|
|
src_cont, src_obj = copy._check_copy_from_header(req)
|
|
self.assertEqual(src_cont, 'c')
|
|
self.assertEqual(src_obj, 'o2')
|
|
req = Request.blank(
|
|
'/v/a/c/o',
|
|
headers={'x-copy-from': 'c/subdir/o2'})
|
|
src_cont, src_obj = copy._check_copy_from_header(req)
|
|
self.assertEqual(src_cont, 'c')
|
|
self.assertEqual(src_obj, 'subdir/o2')
|
|
req = Request.blank(
|
|
'/v/a/c/o',
|
|
headers={'x-copy-from': '/c/o2'})
|
|
src_cont, src_obj = copy._check_copy_from_header(req)
|
|
self.assertEqual(src_cont, 'c')
|
|
self.assertEqual(src_obj, 'o2')
|
|
|
|
def test_validate_bad_copy_from(self):
|
|
req = Request.blank(
|
|
'/v/a/c/o',
|
|
headers={'x-copy-from': 'bad_object'})
|
|
self.assertRaises(HTTPException,
|
|
copy._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 = copy._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 = copy._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 = copy._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,
|
|
copy._check_destination_header, req)
|
|
|
|
|
|
class TestServerSideCopyMiddleware(unittest.TestCase):
|
|
def setUp(self):
|
|
self.app = FakeSwift()
|
|
self.ssc = copy.filter_factory({
|
|
'object_post_as_copy': 'yes',
|
|
})(self.app)
|
|
self.ssc.logger = self.app.logger
|
|
|
|
def tearDown(self):
|
|
self.assertEqual(self.app.unclosed_requests, {})
|
|
|
|
def call_app(self, req, app=None, expect_exception=False):
|
|
if app is None:
|
|
app = self.app
|
|
|
|
self.authorized = []
|
|
|
|
def authorize(req):
|
|
self.authorized.append(req)
|
|
|
|
if 'swift.authorize' not in req.environ:
|
|
req.environ['swift.authorize'] = authorize
|
|
|
|
req.headers.setdefault("User-Agent", "Bruce Wayne")
|
|
|
|
status = [None]
|
|
headers = [None]
|
|
|
|
def start_response(s, h, ei=None):
|
|
status[0] = s
|
|
headers[0] = h
|
|
|
|
body_iter = app(req.environ, start_response)
|
|
body = ''
|
|
caught_exc = None
|
|
try:
|
|
# appease the close-checker
|
|
with closing_if_possible(body_iter):
|
|
for chunk in body_iter:
|
|
body += chunk
|
|
except Exception as exc:
|
|
if expect_exception:
|
|
caught_exc = exc
|
|
else:
|
|
raise
|
|
|
|
if expect_exception:
|
|
return status[0], headers[0], body, caught_exc
|
|
else:
|
|
return status[0], headers[0], body
|
|
|
|
def call_ssc(self, req, **kwargs):
|
|
return self.call_app(req, app=self.ssc, **kwargs)
|
|
|
|
def assertRequestEqual(self, req, other):
|
|
self.assertEqual(req.method, other.method)
|
|
self.assertEqual(req.path, other.path)
|
|
|
|
def test_no_object_in_path_pass_through(self):
|
|
self.app.register('PUT', '/v1/a/c', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c', method='PUT')
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_object_pass_through_methods(self):
|
|
for method in ['DELETE', 'GET', 'HEAD', 'REPLICATE']:
|
|
self.app.register(method, '/v1/a/c/o', swob.HTTPOk, {})
|
|
req = Request.blank('/v1/a/c/o', method=method)
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '200 OK')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
self.assertNotIn('swift.orig_req_method', req.environ)
|
|
|
|
def test_POST_as_COPY_simple(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPAccepted, {})
|
|
req = Request.blank('/v1/a/c/o', method='POST')
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
# For basic test cases, assert orig_req_method behavior
|
|
self.assertEqual(req.environ['swift.orig_req_method'], 'POST')
|
|
|
|
def test_POST_as_COPY_201_return_202(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', method='POST')
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_POST_delete_at(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPAccepted, {})
|
|
t = str(int(time.time() + 100))
|
|
req = Request.blank('/v1/a/c/o', method='POST',
|
|
headers={'Content-Type': 'foo/bar',
|
|
'X-Delete-At': t})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertTrue('X-Delete-At' in req_headers)
|
|
self.assertEqual(req_headers['X-Delete-At'], str(t))
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_POST_as_COPY_static_large_object(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'X-Static-Large-Object': True}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPAccepted, {})
|
|
req = Request.blank('/v1/a/c/o', method='POST',
|
|
headers={})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertNotIn('X-Static-Large-Object', req_headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_POST_as_COPY_dynamic_large_object_manifest(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'X-Object-Manifest': 'orig_manifest'}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', method='POST',
|
|
headers={'X-Object-Manifest': 'new_manifest'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('new_manifest', req_headers['x-object-manifest'])
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_POST_as_COPY_dynamic_large_object_no_manifest(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'X-Object-Manifest': 'orig_manifest'}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', method='POST',
|
|
headers={})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertNotIn('X-Object-Manifest', req_headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertRequestEqual(req, self.authorized[0])
|
|
|
|
def test_basic_put_with_x_copy_from(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o2', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o2', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': 'c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o2', self.authorized[1].path)
|
|
self.assertEqual(self.app.swift_sources[0], 'SSC')
|
|
self.assertEqual(self.app.swift_sources[1], 'SSC')
|
|
# For basic test cases, assert orig_req_method behavior
|
|
self.assertNotIn('swift.orig_req_method', req.environ)
|
|
|
|
def test_static_large_object_manifest(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'X-Static-Large-Object': 'True',
|
|
'Etag': 'should not be sent'}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o2?multipart-manifest=put',
|
|
swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o2?multipart-manifest=get',
|
|
environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': 'c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertEqual(2, len(self.app.calls))
|
|
self.assertEqual('GET', self.app.calls[0][0])
|
|
get_path, qs = self.app.calls[0][1].split('?')
|
|
params = urllib.parse.parse_qs(qs)
|
|
self.assertDictEqual(
|
|
{'format': ['raw'], 'multipart-manifest': ['get']}, params)
|
|
self.assertEqual(get_path, '/v1/a/c/o')
|
|
self.assertEqual(self.app.calls[1],
|
|
('PUT', '/v1/a/c/o2?multipart-manifest=put'))
|
|
req_headers = self.app.headers[1]
|
|
self.assertNotIn('X-Static-Large-Object', req_headers)
|
|
self.assertNotIn('Etag', req_headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o2', self.authorized[1].path)
|
|
|
|
def test_static_large_object(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'X-Static-Large-Object': 'True',
|
|
'Etag': 'should not be sent'}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o2',
|
|
swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o2',
|
|
environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': 'c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/c/o'),
|
|
('PUT', '/v1/a/c/o2')])
|
|
req_headers = self.app.headers[1]
|
|
self.assertNotIn('X-Static-Large-Object', req_headers)
|
|
self.assertNotIn('Etag', req_headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o2', self.authorized[1].path)
|
|
|
|
def test_basic_put_with_x_copy_from_across_container(self):
|
|
self.app.register('GET', '/v1/a/c1/o1', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c2/o2', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c2/o2', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': 'c1/o1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c1/o1') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c1/o1', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c2/o2', self.authorized[1].path)
|
|
|
|
def test_basic_put_with_x_copy_from_across_container_and_account(self):
|
|
self.app.register('GET', '/v1/a1/c1/o1', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a2/c2/o2', swob.HTTPCreated, {},
|
|
'passed')
|
|
req = Request.blank('/v1/a2/c2/o2', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': 'c1/o1',
|
|
'X-Copy-From-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c1/o1') in headers)
|
|
self.assertTrue(('X-Copied-From-Account', 'a1') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a1/c1/o1', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a2/c2/o2', self.authorized[1].path)
|
|
|
|
def test_copy_non_zero_content_length(self):
|
|
req = Request.blank('/v1/a/c2/o2', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '10',
|
|
'X-Copy-From': 'c1/o1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
|
|
def test_copy_non_zero_content_length_with_account(self):
|
|
req = Request.blank('/v1/a2/c2/o2', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '10',
|
|
'X-Copy-From': 'c1/o1',
|
|
'X-Copy-From-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '400 Bad Request')
|
|
|
|
def test_copy_with_slashes_in_x_copy_from(self):
|
|
self.app.register('GET', '/v1/a/c/o/o2', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': 'c/o/o2'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c/o/o2') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o/o2', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_copy_with_slashes_in_x_copy_from_and_account(self):
|
|
self.app.register('GET', '/v1/a1/c1/o/o1', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a2/c2/o2', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a2/c2/o2', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': 'c1/o/o1',
|
|
'X-Copy-From-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c1/o/o1') in headers)
|
|
self.assertTrue(('X-Copied-From-Account', 'a1') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a1/c1/o/o1', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a2/c2/o2', self.authorized[1].path)
|
|
|
|
def test_copy_with_spaces_in_x_copy_from(self):
|
|
self.app.register('GET', '/v1/a/c/o o2', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
# space in soure path
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': 'c/o%20o2'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[0]
|
|
self.assertEqual('GET', method)
|
|
self.assertEqual('/v1/a/c/o o2', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o%20o2') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o%20o2', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_copy_with_spaces_in_x_copy_from_and_account(self):
|
|
self.app.register('GET', '/v1/a/c/o o2', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a1/c1/o', swob.HTTPCreated, {})
|
|
# 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, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[0]
|
|
self.assertEqual('GET', method)
|
|
self.assertEqual('/v1/a/c/o o2', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o%20o2') in headers)
|
|
self.assertTrue(('X-Copied-From-Account', 'a') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o%20o2', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a1/c1/o', self.authorized[1].path)
|
|
|
|
def test_copy_with_leading_slash_in_x_copy_from(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
# repeat tests with leading /
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[0]
|
|
self.assertEqual('GET', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_copy_with_leading_slash_in_x_copy_from_and_account(self):
|
|
# repeat tests with leading /
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a1/c1/o', swob.HTTPCreated, {})
|
|
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, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[0]
|
|
self.assertEqual('GET', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertTrue(('X-Copied-From-Account', 'a') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a1/c1/o', self.authorized[1].path)
|
|
|
|
def test_copy_with_leading_slash_and_slashes_in_x_copy_from(self):
|
|
self.app.register('GET', '/v1/a/c/o/o2', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c/o/o2'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[0]
|
|
self.assertEqual('GET', method)
|
|
self.assertEqual('/v1/a/c/o/o2', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o/o2') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o/o2', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_copy_with_leading_slash_and_slashes_in_x_copy_from_acct(self):
|
|
self.app.register('GET', '/v1/a/c/o/o2', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a1/c1/o', swob.HTTPCreated, {})
|
|
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, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[0]
|
|
self.assertEqual('GET', method)
|
|
self.assertEqual('/v1/a/c/o/o2', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o/o2') in headers)
|
|
self.assertTrue(('X-Copied-From-Account', 'a') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o/o2', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a1/c1/o', self.authorized[1].path)
|
|
|
|
def test_copy_with_no_object_in_x_copy_from(self):
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '412 Precondition Failed')
|
|
|
|
def test_copy_with_no_object_in_x_copy_from_and_account(self):
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c',
|
|
'X-Copy-From-Account': 'a'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '412 Precondition Failed')
|
|
|
|
def test_copy_with_bad_x_copy_from_account(self):
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c/o',
|
|
'X-Copy-From-Account': '/i/am/bad'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '412 Precondition Failed')
|
|
|
|
def test_copy_server_error_reading_source(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPServiceUnavailable, {})
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '503 Service Unavailable')
|
|
|
|
def test_copy_server_error_reading_source_and_account(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPServiceUnavailable, {})
|
|
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, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '503 Service Unavailable')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_copy_not_found_reading_source(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPNotFound, {})
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_copy_not_found_reading_source_and_account(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPNotFound, {})
|
|
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, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_copy_with_object_metadata(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c/o',
|
|
'X-Object-Meta-Ours': 'okay'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
self.assertEqual(req_headers['X-Object-Meta-Ours'], 'okay')
|
|
self.assertTrue(('X-Object-Meta-Ours', 'okay') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_copy_with_object_metadata_and_account(self):
|
|
self.app.register('GET', '/v1/a1/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c/o',
|
|
'X-Object-Meta-Ours': 'okay',
|
|
'X-Copy-From-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
self.assertEqual(req_headers['X-Object-Meta-Ours'], 'okay')
|
|
self.assertTrue(('X-Object-Meta-Ours', 'okay') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a1/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_copy_source_larger_than_max_file_size(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, "largebody")
|
|
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'Content-Length': '0',
|
|
'X-Copy-From': '/c/o'})
|
|
with mock.patch('swift.common.middleware.copy.'
|
|
'MAX_FILE_SIZE', 1):
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '413 Request Entity Too Large')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_basic_COPY(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {
|
|
'etag': 'is sent'}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o-copy', swob.HTTPCreated, {})
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='COPY',
|
|
headers={'Content-Length': 0,
|
|
'Destination': 'c/o-copy'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o-copy', self.authorized[1].path)
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/c/o'),
|
|
('PUT', '/v1/a/c/o-copy')])
|
|
self.assertIn('etag', self.app.headers[1])
|
|
self.assertEqual(self.app.headers[1]['etag'], 'is sent')
|
|
# For basic test cases, assert orig_req_method behavior
|
|
self.assertEqual(req.environ['swift.orig_req_method'], 'COPY')
|
|
|
|
def test_basic_DLO(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {
|
|
'x-object-manifest': 'some/path',
|
|
'etag': 'is not sent'}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o-copy', swob.HTTPCreated, {})
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='COPY',
|
|
headers={'Content-Length': 0,
|
|
'Destination': 'c/o-copy'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertEqual(self.app.calls, [
|
|
('GET', '/v1/a/c/o'),
|
|
('PUT', '/v1/a/c/o-copy')])
|
|
self.assertNotIn('x-object-manifest', self.app.headers[1])
|
|
self.assertNotIn('etag', self.app.headers[1])
|
|
|
|
def test_basic_DLO_manifest(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {
|
|
'x-object-manifest': 'some/path',
|
|
'etag': 'is sent'}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o-copy', swob.HTTPCreated, {})
|
|
req = Request.blank(
|
|
'/v1/a/c/o?multipart-manifest=get', method='COPY',
|
|
headers={'Content-Length': 0,
|
|
'Destination': 'c/o-copy'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertEqual(2, len(self.app.calls))
|
|
self.assertEqual('GET', self.app.calls[0][0])
|
|
get_path, qs = self.app.calls[0][1].split('?')
|
|
params = urllib.parse.parse_qs(qs)
|
|
self.assertDictEqual(
|
|
{'format': ['raw'], 'multipart-manifest': ['get']}, params)
|
|
self.assertEqual(get_path, '/v1/a/c/o')
|
|
self.assertEqual(self.app.calls[1], ('PUT', '/v1/a/c/o-copy'))
|
|
self.assertIn('x-object-manifest', self.app.headers[1])
|
|
self.assertEqual(self.app.headers[1]['x-object-manifest'], 'some/path')
|
|
self.assertIn('etag', self.app.headers[1])
|
|
self.assertEqual(self.app.headers[1]['etag'], 'is sent')
|
|
|
|
def test_COPY_source_metadata(self):
|
|
source_headers = {
|
|
'x-object-sysmeta-test1': 'copy me',
|
|
'x-object-meta-test2': 'copy me too',
|
|
'x-object-transient-sysmeta-test3': 'ditto',
|
|
'x-object-sysmeta-container-update-override-etag': 'etag val',
|
|
'x-object-sysmeta-container-update-override-size': 'size val',
|
|
'x-object-sysmeta-container-update-override-foo': 'bar',
|
|
'x-delete-at': 'delete-at-time'}
|
|
|
|
get_resp_headers = source_headers.copy()
|
|
get_resp_headers['etag'] = 'source etag'
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk,
|
|
headers=get_resp_headers, body='passed')
|
|
|
|
def verify_headers(expected_headers, unexpected_headers,
|
|
actual_headers):
|
|
for k, v in actual_headers:
|
|
if k.lower() in expected_headers:
|
|
expected_val = expected_headers.pop(k.lower())
|
|
self.assertEqual(expected_val, v)
|
|
self.assertNotIn(k.lower(), unexpected_headers)
|
|
self.assertFalse(expected_headers)
|
|
|
|
# use a COPY request
|
|
self.app.register('PUT', '/v1/a/c/o-copy0', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', method='COPY',
|
|
headers={'Content-Length': 0,
|
|
'Destination': 'c/o-copy0'})
|
|
status, resp_headers, body = self.call_ssc(req)
|
|
self.assertEqual('201 Created', status)
|
|
verify_headers(source_headers.copy(), [], resp_headers)
|
|
method, path, put_headers = self.app.calls_with_headers[-1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c/o-copy0', path)
|
|
verify_headers(source_headers.copy(), [], put_headers.items())
|
|
self.assertIn('etag', put_headers)
|
|
self.assertEqual(put_headers['etag'], 'source etag')
|
|
|
|
req = Request.blank('/v1/a/c/o-copy0', method='GET')
|
|
status, resp_headers, body = self.call_ssc(req)
|
|
self.assertEqual('200 OK', status)
|
|
verify_headers(source_headers.copy(), [], resp_headers)
|
|
|
|
# use a COPY request with a Range header
|
|
self.app.register('PUT', '/v1/a/c/o-copy1', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', method='COPY',
|
|
headers={'Content-Length': 0,
|
|
'Destination': 'c/o-copy1',
|
|
'Range': 'bytes=1-2'})
|
|
status, resp_headers, body = self.call_ssc(req)
|
|
expected_headers = source_headers.copy()
|
|
unexpected_headers = (
|
|
'x-object-sysmeta-container-update-override-etag',
|
|
'x-object-sysmeta-container-update-override-size',
|
|
'x-object-sysmeta-container-update-override-foo')
|
|
for h in unexpected_headers:
|
|
expected_headers.pop(h)
|
|
self.assertEqual('201 Created', status)
|
|
verify_headers(expected_headers, unexpected_headers, resp_headers)
|
|
method, path, put_headers = self.app.calls_with_headers[-1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c/o-copy1', path)
|
|
verify_headers(
|
|
expected_headers, unexpected_headers, put_headers.items())
|
|
# etag should not be copied with a Range request
|
|
self.assertNotIn('etag', put_headers)
|
|
|
|
req = Request.blank('/v1/a/c/o-copy1', method='GET')
|
|
status, resp_headers, body = self.call_ssc(req)
|
|
self.assertEqual('200 OK', status)
|
|
verify_headers(expected_headers, unexpected_headers, resp_headers)
|
|
|
|
# use a PUT with x-copy-from
|
|
self.app.register('PUT', '/v1/a/c/o-copy2', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o-copy2', method='PUT',
|
|
headers={'Content-Length': 0,
|
|
'X-Copy-From': 'c/o'})
|
|
status, resp_headers, body = self.call_ssc(req)
|
|
self.assertEqual('201 Created', status)
|
|
verify_headers(source_headers.copy(), [], resp_headers)
|
|
method, path, put_headers = self.app.calls_with_headers[-1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c/o-copy2', path)
|
|
verify_headers(source_headers.copy(), [], put_headers.items())
|
|
self.assertIn('etag', put_headers)
|
|
self.assertEqual(put_headers['etag'], 'source etag')
|
|
|
|
req = Request.blank('/v1/a/c/o-copy2', method='GET')
|
|
status, resp_headers, body = self.call_ssc(req)
|
|
self.assertEqual('200 OK', status)
|
|
verify_headers(source_headers.copy(), [], resp_headers)
|
|
|
|
# copy to same path as source
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', method='PUT',
|
|
headers={'Content-Length': 0,
|
|
'X-Copy-From': 'c/o'})
|
|
status, resp_headers, body = self.call_ssc(req)
|
|
self.assertEqual('201 Created', status)
|
|
verify_headers(source_headers.copy(), [], resp_headers)
|
|
method, path, put_headers = self.app.calls_with_headers[-1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
verify_headers(source_headers.copy(), [], put_headers.items())
|
|
self.assertIn('etag', put_headers)
|
|
self.assertEqual(put_headers['etag'], 'source etag')
|
|
|
|
def test_COPY_no_destination_header(self):
|
|
req = Request.blank(
|
|
'/v1/a/c/o', method='COPY', headers={'Content-Length': 0})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '412 Precondition Failed')
|
|
self.assertEqual(len(self.authorized), 0)
|
|
|
|
def test_basic_COPY_account(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a1/c1/o2', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': 'c1/o2',
|
|
'Destination-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[0]
|
|
self.assertEqual('GET', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a1/c1/o2', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertTrue(('X-Copied-From-Account', 'a') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a1/c1/o2', self.authorized[1].path)
|
|
|
|
def test_COPY_across_containers(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c2/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': 'c2/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c2/o', self.authorized[1].path)
|
|
|
|
def test_COPY_source_with_slashes_in_name(self):
|
|
self.app.register('GET', '/v1/a/c/o/o2', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o/o2',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': 'c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o/o2') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o/o2', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_COPY_account_source_with_slashes_in_name(self):
|
|
self.app.register('GET', '/v1/a/c/o/o2', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a1/c1/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o/o2',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': 'c1/o',
|
|
'Destination-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a1/c1/o', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o/o2') in headers)
|
|
self.assertTrue(('X-Copied-From-Account', 'a') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o/o2', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a1/c1/o', self.authorized[1].path)
|
|
|
|
def test_COPY_destination_leading_slash(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_COPY_account_destination_leading_slash(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a1/c1/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c1/o',
|
|
'Destination-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a1/c1/o', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o') in headers)
|
|
self.assertTrue(('X-Copied-From-Account', 'a') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a1/c1/o', self.authorized[1].path)
|
|
|
|
def test_COPY_source_with_slashes_destination_leading_slash(self):
|
|
self.app.register('GET', '/v1/a/c/o/o2', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o/o2',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o/o2') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o/o2', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_COPY_account_source_with_slashes_destination_leading_slash(self):
|
|
self.app.register('GET', '/v1/a/c/o/o2', swob.HTTPOk, {}, 'passed')
|
|
self.app.register('PUT', '/v1/a1/c1/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o/o2',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c1/o',
|
|
'Destination-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a1/c1/o', path)
|
|
self.assertTrue(('X-Copied-From', 'c/o/o2') in headers)
|
|
self.assertTrue(('X-Copied-From-Account', 'a') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o/o2', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a1/c1/o', self.authorized[1].path)
|
|
|
|
def test_COPY_no_object_in_destination(self):
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': 'c_o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
|
|
self.assertEqual(status, '412 Precondition Failed')
|
|
|
|
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, headers, body = self.call_ssc(req)
|
|
|
|
self.assertEqual(status, '412 Precondition Failed')
|
|
|
|
def test_COPY_account_bad_destination_account(self):
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o',
|
|
'Destination-Account': '/i/am/bad'})
|
|
status, headers, body = self.call_ssc(req)
|
|
|
|
self.assertEqual(status, '412 Precondition Failed')
|
|
|
|
def test_COPY_server_error_reading_source(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPServiceUnavailable, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '503 Service Unavailable')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_COPY_account_server_error_reading_source(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPServiceUnavailable, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c1/o',
|
|
'Destination-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '503 Service Unavailable')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_COPY_not_found_reading_source(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPNotFound, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_COPY_account_not_found_reading_source(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPNotFound, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c1/o',
|
|
'Destination-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '404 Not Found')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_COPY_with_metadata(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, "passed")
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o',
|
|
'X-Object-Meta-Ours': 'okay'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
self.assertEqual(req_headers['X-Object-Meta-Ours'], 'okay')
|
|
self.assertTrue(('X-Object-Meta-Ours', 'okay') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_COPY_account_with_metadata(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, "passed")
|
|
self.app.register('PUT', '/v1/a1/c1/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c1/o',
|
|
'X-Object-Meta-Ours': 'okay',
|
|
'Destination-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a1/c1/o', path)
|
|
self.assertEqual(req_headers['X-Object-Meta-Ours'], 'okay')
|
|
self.assertTrue(('X-Object-Meta-Ours', 'okay') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a1/c1/o', self.authorized[1].path)
|
|
|
|
def test_COPY_source_zero_content_length(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, None)
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '413 Request Entity Too Large')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_COPY_source_larger_than_max_file_size(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, "largebody")
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o'})
|
|
with mock.patch('swift.common.middleware.copy.'
|
|
'MAX_FILE_SIZE', 1):
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '413 Request Entity Too Large')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_COPY_account_source_zero_content_length(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, None)
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o',
|
|
'Destination-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '413 Request Entity Too Large')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_COPY_account_source_larger_than_max_file_size(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk, {}, "largebody")
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c1/o',
|
|
'Destination-Account': 'a1'})
|
|
with mock.patch('swift.common.middleware.copy.'
|
|
'MAX_FILE_SIZE', 1):
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '413 Request Entity Too Large')
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def test_COPY_newest(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'Last-Modified': '123'}, "passed")
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c/o'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From-Last-Modified', '123') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[1].path)
|
|
|
|
def test_COPY_account_newest(self):
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'Last-Modified': '123'}, "passed")
|
|
self.app.register('PUT', '/v1/a1/c1/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'COPY'},
|
|
headers={'Destination': '/c1/o',
|
|
'Destination-Account': 'a1'})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
self.assertTrue(('X-Copied-From-Last-Modified', '123') in headers)
|
|
self.assertEqual(len(self.authorized), 2)
|
|
self.assertEqual('GET', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
self.assertEqual('PUT', self.authorized[1].method)
|
|
self.assertEqual('/v1/a1/c1/o', self.authorized[1].path)
|
|
|
|
def test_COPY_in_OPTIONS_response(self):
|
|
self.app.register('OPTIONS', '/v1/a/c/o', swob.HTTPOk,
|
|
{'Allow': 'GET, PUT'})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'OPTIONS'}, headers={})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '200 OK')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[0]
|
|
self.assertEqual('OPTIONS', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
self.assertTrue(('Allow', 'GET, PUT, COPY') in headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('OPTIONS', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
# For basic test cases, assert orig_req_method behavior
|
|
self.assertNotIn('swift.orig_req_method', req.environ)
|
|
|
|
def test_COPY_in_OPTIONS_response_CORS(self):
|
|
self.app.register('OPTIONS', '/v1/a/c/o', swob.HTTPOk,
|
|
{'Allow': 'GET, PUT',
|
|
'Access-Control-Allow-Methods': 'GET, PUT'})
|
|
req = Request.blank('/v1/a/c/o',
|
|
environ={'REQUEST_METHOD': 'OPTIONS'}, headers={})
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '200 OK')
|
|
calls = self.app.calls_with_headers
|
|
method, path, req_headers = calls[0]
|
|
self.assertEqual('OPTIONS', method)
|
|
self.assertEqual('/v1/a/c/o', path)
|
|
self.assertTrue(('Allow', 'GET, PUT, COPY') in headers)
|
|
self.assertTrue(('Access-Control-Allow-Methods',
|
|
'GET, PUT, COPY') in headers)
|
|
self.assertEqual(len(self.authorized), 1)
|
|
self.assertEqual('OPTIONS', self.authorized[0].method)
|
|
self.assertEqual('/v1/a/c/o', self.authorized[0].path)
|
|
|
|
def _test_COPY_source_headers(self, extra_put_headers):
|
|
# helper method to perform a COPY with some metadata headers that
|
|
# should always be sent to the destination
|
|
put_headers = {'Destination': '/c1/o',
|
|
'X-Object-Meta-Test2': 'added',
|
|
'X-Object-Sysmeta-Test2': 'added',
|
|
'X-Object-Transient-Sysmeta-Test2': 'added'}
|
|
put_headers.update(extra_put_headers)
|
|
get_resp_headers = {
|
|
'X-Timestamp': '1234567890.12345',
|
|
'X-Backend-Timestamp': '1234567890.12345',
|
|
'Content-Type': 'text/original',
|
|
'Content-Encoding': 'gzip',
|
|
'Content-Disposition': 'attachment; filename=myfile',
|
|
'X-Object-Meta-Test': 'original',
|
|
'X-Object-Sysmeta-Test': 'original',
|
|
'X-Object-Transient-Sysmeta-Test': 'original',
|
|
'X-Foo': 'Bar'}
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk, headers=get_resp_headers)
|
|
self.app.register('PUT', '/v1/a/c1/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', method='COPY', headers=put_headers)
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(2, len(calls))
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
# these headers should always be applied to the destination
|
|
self.assertEqual('added', req_headers.get('X-Object-Meta-Test2'))
|
|
self.assertEqual('added', req_headers.get('X-Object-Sysmeta-Test2'))
|
|
self.assertEqual('added',
|
|
req_headers.get('X-Object-Transient-Sysmeta-Test2'))
|
|
return req_headers
|
|
|
|
def test_COPY_source_headers_no_updates(self):
|
|
# copy should preserve existing metadata if not updated
|
|
req_headers = self._test_COPY_source_headers({})
|
|
self.assertEqual('text/original', req_headers.get('Content-Type'))
|
|
self.assertEqual('gzip', req_headers.get('Content-Encoding'))
|
|
self.assertEqual('attachment; filename=myfile',
|
|
req_headers.get('Content-Disposition'))
|
|
self.assertEqual('original', req_headers.get('X-Object-Meta-Test'))
|
|
self.assertEqual('original', req_headers.get('X-Object-Sysmeta-Test'))
|
|
self.assertEqual('original',
|
|
req_headers.get('X-Object-Transient-Sysmeta-Test'))
|
|
self.assertEqual('Bar', req_headers.get('X-Foo'))
|
|
self.assertNotIn('X-Timestamp', req_headers)
|
|
self.assertNotIn('X-Backend-Timestamp', req_headers)
|
|
|
|
def test_COPY_source_headers_with_updates(self):
|
|
# copy should apply any updated values to existing metadata
|
|
put_headers = {
|
|
'Content-Type': 'text/not_original',
|
|
'Content-Encoding': 'not_gzip',
|
|
'Content-Disposition': 'attachment; filename=notmyfile',
|
|
'X-Object-Meta-Test': 'not_original',
|
|
'X-Object-Sysmeta-Test': 'not_original',
|
|
'X-Object-Transient-Sysmeta-Test': 'not_original',
|
|
'X-Foo': 'Not Bar'}
|
|
req_headers = self._test_COPY_source_headers(put_headers)
|
|
self.assertEqual('text/not_original', req_headers.get('Content-Type'))
|
|
self.assertEqual('not_gzip', req_headers.get('Content-Encoding'))
|
|
self.assertEqual('attachment; filename=notmyfile',
|
|
req_headers.get('Content-Disposition'))
|
|
self.assertEqual('not_original', req_headers.get('X-Object-Meta-Test'))
|
|
self.assertEqual('not_original',
|
|
req_headers.get('X-Object-Sysmeta-Test'))
|
|
self.assertEqual('not_original',
|
|
req_headers.get('X-Object-Transient-Sysmeta-Test'))
|
|
self.assertEqual('Not Bar', req_headers.get('X-Foo'))
|
|
self.assertNotIn('X-Timestamp', req_headers)
|
|
self.assertNotIn('X-Backend-Timestamp', req_headers)
|
|
|
|
def test_COPY_x_fresh_metadata_no_updates(self):
|
|
# existing user metadata should not be copied, sysmeta is copied
|
|
put_headers = {
|
|
'X-Fresh-Metadata': 'true',
|
|
'X-Extra': 'Fresh'}
|
|
req_headers = self._test_COPY_source_headers(put_headers)
|
|
self.assertEqual('text/original', req_headers.get('Content-Type'))
|
|
self.assertEqual('Fresh', req_headers.get('X-Extra'))
|
|
self.assertEqual('original',
|
|
req_headers.get('X-Object-Sysmeta-Test'))
|
|
self.assertIn('X-Fresh-Metadata', req_headers)
|
|
self.assertNotIn('X-Object-Meta-Test', req_headers)
|
|
self.assertNotIn('X-Object-Transient-Sysmeta-Test', req_headers)
|
|
self.assertNotIn('X-Timestamp', req_headers)
|
|
self.assertNotIn('X-Backend-Timestamp', req_headers)
|
|
self.assertNotIn('Content-Encoding', req_headers)
|
|
self.assertNotIn('Content-Disposition', req_headers)
|
|
self.assertNotIn('X-Foo', req_headers)
|
|
|
|
def test_COPY_x_fresh_metadata_with_updates(self):
|
|
# existing user metadata should not be copied, new metadata replaces it
|
|
put_headers = {
|
|
'X-Fresh-Metadata': 'true',
|
|
'Content-Type': 'text/not_original',
|
|
'Content-Encoding': 'not_gzip',
|
|
'Content-Disposition': 'attachment; filename=notmyfile',
|
|
'X-Object-Meta-Test': 'not_original',
|
|
'X-Object-Sysmeta-Test': 'not_original',
|
|
'X-Object-Transient-Sysmeta-Test': 'not_original',
|
|
'X-Foo': 'Not Bar',
|
|
'X-Extra': 'Fresh'}
|
|
req_headers = self._test_COPY_source_headers(put_headers)
|
|
self.assertEqual('Fresh', req_headers.get('X-Extra'))
|
|
self.assertEqual('text/not_original', req_headers.get('Content-Type'))
|
|
self.assertEqual('not_gzip', req_headers.get('Content-Encoding'))
|
|
self.assertEqual('attachment; filename=notmyfile',
|
|
req_headers.get('Content-Disposition'))
|
|
self.assertEqual('not_original', req_headers.get('X-Object-Meta-Test'))
|
|
self.assertEqual('not_original',
|
|
req_headers.get('X-Object-Sysmeta-Test'))
|
|
self.assertEqual('not_original',
|
|
req_headers.get('X-Object-Transient-Sysmeta-Test'))
|
|
self.assertEqual('Not Bar', req_headers.get('X-Foo'))
|
|
|
|
def _test_POST_source_headers(self, extra_post_headers):
|
|
# helper method to perform a POST with metadata headers that should
|
|
# always be sent to the destination
|
|
post_headers = {'X-Object-Meta-Test2': 'added',
|
|
'X-Object-Sysmeta-Test2': 'added',
|
|
'X-Object-Transient-Sysmeta-Test2': 'added'}
|
|
post_headers.update(extra_post_headers)
|
|
get_resp_headers = {
|
|
'X-Timestamp': '1234567890.12345',
|
|
'X-Backend-Timestamp': '1234567890.12345',
|
|
'Content-Type': 'text/original',
|
|
'Content-Encoding': 'gzip',
|
|
'Content-Disposition': 'attachment; filename=myfile',
|
|
'X-Object-Meta-Test': 'original',
|
|
'X-Object-Sysmeta-Test': 'original',
|
|
'X-Object-Transient-Sysmeta-Test': 'original',
|
|
'X-Foo': 'Bar'}
|
|
self.app.register(
|
|
'GET', '/v1/a/c/o', swob.HTTPOk, headers=get_resp_headers)
|
|
self.app.register('PUT', '/v1/a/c/o', swob.HTTPCreated, {})
|
|
req = Request.blank('/v1/a/c/o', method='POST', headers=post_headers)
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual(status, '202 Accepted')
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(2, len(calls))
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
# these headers should always be applied to the destination
|
|
self.assertEqual('added', req_headers.get('X-Object-Meta-Test2'))
|
|
self.assertEqual('added',
|
|
req_headers.get('X-Object-Transient-Sysmeta-Test2'))
|
|
# POSTed sysmeta should never be applied to the destination
|
|
self.assertNotIn('X-Object-Sysmeta-Test2', req_headers)
|
|
# existing sysmeta should always be preserved
|
|
self.assertEqual('original',
|
|
req_headers.get('X-Object-Sysmeta-Test'))
|
|
return req_headers
|
|
|
|
def test_POST_no_updates(self):
|
|
post_headers = {}
|
|
req_headers = self._test_POST_source_headers(post_headers)
|
|
self.assertEqual('text/original', req_headers.get('Content-Type'))
|
|
self.assertNotIn('X-Object-Meta-Test', req_headers)
|
|
self.assertNotIn('X-Object-Transient-Sysmeta-Test', req_headers)
|
|
self.assertNotIn('X-Timestamp', req_headers)
|
|
self.assertNotIn('X-Backend-Timestamp', req_headers)
|
|
self.assertNotIn('Content-Encoding', req_headers)
|
|
self.assertNotIn('Content-Disposition', req_headers)
|
|
self.assertNotIn('X-Foo', req_headers)
|
|
|
|
def test_POST_with_updates(self):
|
|
post_headers = {
|
|
'Content-Type': 'text/not_original',
|
|
'Content-Encoding': 'not_gzip',
|
|
'Content-Disposition': 'attachment; filename=notmyfile',
|
|
'X-Object-Meta-Test': 'not_original',
|
|
'X-Object-Sysmeta-Test': 'not_original',
|
|
'X-Object-Transient-Sysmeta-Test': 'not_original',
|
|
'X-Foo': 'Not Bar',
|
|
}
|
|
req_headers = self._test_POST_source_headers(post_headers)
|
|
self.assertEqual('text/not_original', req_headers.get('Content-Type'))
|
|
self.assertEqual('not_gzip', req_headers.get('Content-Encoding'))
|
|
self.assertEqual('attachment; filename=notmyfile',
|
|
req_headers.get('Content-Disposition'))
|
|
self.assertEqual('not_original', req_headers.get('X-Object-Meta-Test'))
|
|
self.assertEqual('not_original',
|
|
req_headers.get('X-Object-Transient-Sysmeta-Test'))
|
|
self.assertEqual('Not Bar', req_headers.get('X-Foo'))
|
|
|
|
def test_POST_x_fresh_metadata_with_updates(self):
|
|
# post-as-copy trumps x-fresh-metadata i.e. existing user metadata
|
|
# should not be copied, sysmeta is copied *and not updated with new*
|
|
post_headers = {
|
|
'X-Fresh-Metadata': 'true',
|
|
'Content-Type': 'text/not_original',
|
|
'Content-Encoding': 'not_gzip',
|
|
'Content-Disposition': 'attachment; filename=notmyfile',
|
|
'X-Object-Meta-Test': 'not_original',
|
|
'X-Object-Sysmeta-Test': 'not_original',
|
|
'X-Object-Transient-Sysmeta-Test': 'not_original',
|
|
'X-Foo': 'Not Bar',
|
|
}
|
|
req_headers = self._test_POST_source_headers(post_headers)
|
|
self.assertEqual('text/not_original', req_headers.get('Content-Type'))
|
|
self.assertEqual('not_gzip', req_headers.get('Content-Encoding'))
|
|
self.assertEqual('attachment; filename=notmyfile',
|
|
req_headers.get('Content-Disposition'))
|
|
self.assertEqual('not_original', req_headers.get('X-Object-Meta-Test'))
|
|
self.assertEqual('not_original',
|
|
req_headers.get('X-Object-Transient-Sysmeta-Test'))
|
|
self.assertEqual('Not Bar', req_headers.get('X-Foo'))
|
|
self.assertIn('X-Fresh-Metadata', req_headers)
|
|
|
|
def test_COPY_with_single_range(self):
|
|
# verify that source etag is not copied when copying a range
|
|
self.app.register('GET', '/v1/a/c/o', swob.HTTPOk,
|
|
{'etag': 'bogus etag'}, "abcdefghijklmnop")
|
|
self.app.register('PUT', '/v1/a/c1/o', swob.HTTPCreated, {})
|
|
req = swob.Request.blank(
|
|
'/v1/a/c/o', method='COPY',
|
|
headers={'Destination': 'c1/o',
|
|
'Range': 'bytes=5-10'})
|
|
|
|
status, headers, body = self.call_ssc(req)
|
|
|
|
self.assertEqual(status, '201 Created')
|
|
calls = self.app.calls_with_headers
|
|
self.assertEqual(2, len(calls))
|
|
method, path, req_headers = calls[1]
|
|
self.assertEqual('PUT', method)
|
|
self.assertEqual('/v1/a/c1/o', path)
|
|
self.assertNotIn('etag', (h.lower() for h in req_headers))
|
|
self.assertEqual('6', req_headers['content-length'])
|
|
req = swob.Request.blank('/v1/a/c1/o', method='GET')
|
|
status, headers, body = self.call_ssc(req)
|
|
self.assertEqual('fghijk', body)
|
|
|
|
|
|
class TestServerSideCopyConfiguration(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.tmpdir = tempfile.mkdtemp()
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.tmpdir)
|
|
|
|
def test_post_as_copy_defaults_to_false(self):
|
|
ssc = copy.filter_factory({})("no app here")
|
|
self.assertEqual(ssc.object_post_as_copy, False)
|
|
|
|
def test_reading_proxy_conf_when_no_middleware_conf_present(self):
|
|
proxy_conf = dedent("""
|
|
[DEFAULT]
|
|
bind_ip = 10.4.5.6
|
|
|
|
[pipeline:main]
|
|
pipeline = catch_errors copy ye-olde-proxy-server
|
|
|
|
[filter:copy]
|
|
use = egg:swift#copy
|
|
|
|
[app:ye-olde-proxy-server]
|
|
use = egg:swift#proxy
|
|
object_post_as_copy = no
|
|
""")
|
|
|
|
conffile = tempfile.NamedTemporaryFile()
|
|
conffile.write(proxy_conf)
|
|
conffile.flush()
|
|
|
|
ssc = copy.filter_factory({
|
|
'__file__': conffile.name
|
|
})("no app here")
|
|
|
|
self.assertEqual(ssc.object_post_as_copy, False)
|
|
|
|
def test_middleware_conf_precedence(self):
|
|
proxy_conf = dedent("""
|
|
[DEFAULT]
|
|
bind_ip = 10.4.5.6
|
|
|
|
[pipeline:main]
|
|
pipeline = catch_errors copy ye-olde-proxy-server
|
|
|
|
[filter:copy]
|
|
use = egg:swift#copy
|
|
object_post_as_copy = no
|
|
|
|
[app:ye-olde-proxy-server]
|
|
use = egg:swift#proxy
|
|
object_post_as_copy = yes
|
|
""")
|
|
|
|
conffile = tempfile.NamedTemporaryFile()
|
|
conffile.write(proxy_conf)
|
|
conffile.flush()
|
|
|
|
with mock.patch('swift.common.middleware.copy.get_logger',
|
|
return_value=debug_logger('copy')):
|
|
ssc = copy.filter_factory({
|
|
'object_post_as_copy': 'no',
|
|
'__file__': conffile.name
|
|
})("no app here")
|
|
|
|
self.assertEqual(ssc.object_post_as_copy, False)
|
|
self.assertFalse(ssc.logger.get_lines_for_level('warning'))
|
|
|
|
def _test_post_as_copy_emits_warning(self, conf):
|
|
with mock.patch('swift.common.middleware.copy.get_logger',
|
|
return_value=debug_logger('copy')):
|
|
ssc = copy.filter_factory(conf)("no app here")
|
|
|
|
self.assertEqual(ssc.object_post_as_copy, True)
|
|
log_lines = ssc.logger.get_lines_for_level('warning')
|
|
self.assertEqual(1, len(log_lines))
|
|
self.assertIn('object_post_as_copy=true is deprecated', log_lines[0])
|
|
|
|
def test_post_as_copy_emits_warning(self):
|
|
self._test_post_as_copy_emits_warning({'object_post_as_copy': 'yes'})
|
|
|
|
proxy_conf = dedent("""
|
|
[DEFAULT]
|
|
bind_ip = 10.4.5.6
|
|
|
|
[pipeline:main]
|
|
pipeline = catch_errors copy ye-olde-proxy-server
|
|
|
|
[filter:copy]
|
|
use = egg:swift#copy
|
|
|
|
[app:ye-olde-proxy-server]
|
|
use = egg:swift#proxy
|
|
object_post_as_copy = yes
|
|
""")
|
|
|
|
conffile = tempfile.NamedTemporaryFile()
|
|
conffile.write(proxy_conf)
|
|
conffile.flush()
|
|
|
|
self._test_post_as_copy_emits_warning({'__file__': conffile.name})
|
|
|
|
|
|
@patch_policies(with_ec_default=True)
|
|
class TestServerSideCopyMiddlewareWithEC(unittest.TestCase):
|
|
container_info = {
|
|
'status': 200,
|
|
'write_acl': None,
|
|
'read_acl': None,
|
|
'storage_policy': None,
|
|
'sync_key': None,
|
|
'versions': None,
|
|
}
|
|
|
|
def setUp(self):
|
|
self.logger = debug_logger('proxy-server')
|
|
self.logger.thread_locals = ('txn1', '127.0.0.2')
|
|
self.app = PatchedObjControllerApp(
|
|
None, FakeMemcache(), account_ring=FakeRing(),
|
|
container_ring=FakeRing(), logger=self.logger)
|
|
self.ssc = copy.filter_factory({
|
|
'object_post_as_copy': 'yes',
|
|
})(self.app)
|
|
self.ssc.logger = self.app.logger
|
|
self.policy = POLICIES.default
|
|
self.app.container_info = dict(self.container_info)
|
|
|
|
def test_COPY_with_single_range(self):
|
|
req = swob.Request.blank(
|
|
'/v1/a/c/o', method='COPY',
|
|
headers={'Destination': 'c1/o',
|
|
'Range': 'bytes=5-10'})
|
|
# turn a real body into fragments
|
|
segment_size = self.policy.ec_segment_size
|
|
real_body = ('asdf' * segment_size)[:-10]
|
|
|
|
# split it up into chunks
|
|
chunks = [real_body[x:x + segment_size]
|
|
for x in range(0, len(real_body), segment_size)]
|
|
|
|
# we need only first chunk to rebuild 5-10 range
|
|
fragments = self.policy.pyeclib_driver.encode(chunks[0])
|
|
fragment_payloads = []
|
|
fragment_payloads.append(fragments)
|
|
|
|
node_fragments = zip(*fragment_payloads)
|
|
self.assertEqual(len(node_fragments),
|
|
self.policy.object_ring.replicas) # sanity
|
|
headers = {'X-Object-Sysmeta-Ec-Content-Length': str(len(real_body))}
|
|
responses = [(200, ''.join(node_fragments[i]), headers)
|
|
for i in range(POLICIES.default.ec_ndata)]
|
|
responses += [(201, '', {})] * self.policy.object_ring.replicas
|
|
status_codes, body_iter, headers = zip(*responses)
|
|
expect_headers = {
|
|
'X-Obj-Metadata-Footer': 'yes',
|
|
'X-Obj-Multiphase-Commit': 'yes'
|
|
}
|
|
|
|
put_hdrs = []
|
|
|
|
def capture_conn(host, port, dev, part, method, path, *args, **kwargs):
|
|
if method == 'PUT':
|
|
put_hdrs.append(args[0])
|
|
|
|
with set_http_connect(*status_codes, body_iter=body_iter,
|
|
headers=headers, expect_headers=expect_headers,
|
|
give_connect=capture_conn):
|
|
resp = req.get_response(self.ssc)
|
|
|
|
self.assertEqual(resp.status_int, 201)
|
|
expected_puts = POLICIES.default.ec_ndata + POLICIES.default.ec_nparity
|
|
self.assertEqual(expected_puts, len(put_hdrs))
|
|
for hdrs in put_hdrs:
|
|
# etag should not be copied from source
|
|
self.assertNotIn('etag', (h.lower() for h in hdrs))
|
|
|
|
def test_COPY_with_invalid_ranges(self):
|
|
# real body size is segment_size - 10 (just 1 segment)
|
|
segment_size = self.policy.ec_segment_size
|
|
real_body = ('a' * segment_size)[:-10]
|
|
|
|
# range is out of real body but in segment size
|
|
self._test_invalid_ranges('COPY', real_body,
|
|
segment_size, '%s-' % (segment_size - 10))
|
|
# range is out of both real body and segment size
|
|
self._test_invalid_ranges('COPY', real_body,
|
|
segment_size, '%s-' % (segment_size + 10))
|
|
|
|
def _test_invalid_ranges(self, method, real_body, segment_size, req_range):
|
|
# make a request with range starts from more than real size.
|
|
body_etag = md5(real_body).hexdigest()
|
|
req = swob.Request.blank(
|
|
'/v1/a/c/o', method=method,
|
|
headers={'Destination': 'c1/o',
|
|
'Range': 'bytes=%s' % (req_range)})
|
|
|
|
fragments = self.policy.pyeclib_driver.encode(real_body)
|
|
fragment_payloads = [fragments]
|
|
|
|
node_fragments = zip(*fragment_payloads)
|
|
self.assertEqual(len(node_fragments),
|
|
self.policy.object_ring.replicas) # sanity
|
|
headers = {'X-Object-Sysmeta-Ec-Content-Length': str(len(real_body)),
|
|
'X-Object-Sysmeta-Ec-Etag': body_etag}
|
|
start = int(req_range.split('-')[0])
|
|
self.assertTrue(start >= 0) # sanity
|
|
title, exp = swob.RESPONSE_REASONS[416]
|
|
range_not_satisfiable_body = \
|
|
'<html><h1>%s</h1><p>%s</p></html>' % (title, exp)
|
|
if start >= segment_size:
|
|
responses = [(416, range_not_satisfiable_body, headers)
|
|
for i in range(POLICIES.default.ec_ndata)]
|
|
else:
|
|
responses = [(200, ''.join(node_fragments[i]), headers)
|
|
for i in range(POLICIES.default.ec_ndata)]
|
|
status_codes, body_iter, headers = zip(*responses)
|
|
expect_headers = {
|
|
'X-Obj-Metadata-Footer': 'yes',
|
|
'X-Obj-Multiphase-Commit': 'yes'
|
|
}
|
|
# TODO possibly use FakeApp here
|
|
with set_http_connect(*status_codes, body_iter=body_iter,
|
|
headers=headers, expect_headers=expect_headers):
|
|
resp = req.get_response(self.ssc)
|
|
self.assertEqual(resp.status_int, 416)
|
|
self.assertEqual(resp.content_length, len(range_not_satisfiable_body))
|
|
self.assertEqual(resp.body, range_not_satisfiable_body)
|
|
self.assertEqual(resp.etag, body_etag)
|
|
self.assertEqual(resp.headers['Accept-Ranges'], 'bytes')
|