From 7c9e542c020e3ad846fa8b28385783e04daa48ce Mon Sep 17 00:00:00 2001 From: gholt Date: Wed, 8 Jun 2011 04:19:34 +0000 Subject: [PATCH] Implemented object POST as COPY --- doc/source/deployment_guide.rst | 10 ++ etc/proxy-server.conf-sample | 5 + swift/common/direct_client.py | 51 +++++++++ swift/proxy/server.py | 72 +++++++----- test/functional/swift.py | 2 +- test/functional/tests.py | 4 +- test/probe/test_account_failures.py | 8 +- test/probe/test_container_failures.py | 23 ++-- test/probe/test_object_handoff.py | 84 +++++++------- test/unit/proxy/test_server.py | 154 +++++++++++++++++++++++--- 10 files changed, 312 insertions(+), 101 deletions(-) diff --git a/doc/source/deployment_guide.rst b/doc/source/deployment_guide.rst index 9a78c56960..ce011d3fb9 100644 --- a/doc/source/deployment_guide.rst +++ b/doc/source/deployment_guide.rst @@ -547,6 +547,16 @@ error_suppression_limit 10 Error count to consider a node error limited allow_account_management false Whether account PUTs and DELETEs are even callable +post_as_copy true Set post_as_copy = false to turn + on fast posts where only the + metadata changes are stored anew + and the original data file is + kept in place. This makes for + quicker posts; but since the + container metadata isn't updated + in this mode, features like + container sync won't be able to + sync posts. ============================ =============== ============================= [auth] diff --git a/etc/proxy-server.conf-sample b/etc/proxy-server.conf-sample index 9129e1e0ba..a7fa155422 100644 --- a/etc/proxy-server.conf-sample +++ b/etc/proxy-server.conf-sample @@ -40,6 +40,11 @@ use = egg:swift#proxy # If set to 'true' any authorized user may create and delete accounts; if # 'false' no one, even authorized, can. # allow_account_management = false +# Set post_as_copy = false to turn on fast posts where only the metadata +# changes are stored anew and the original data file is kept in place. This +# makes for quicker posts; but since the container metadata isn't updated in +# this mode, features like container sync won't be able to sync posts. +# post_as_copy = true [filter:swauth] use = egg:swift#swauth diff --git a/swift/common/direct_client.py b/swift/common/direct_client.py index d7fde1f221..165ed3ed8c 100644 --- a/swift/common/direct_client.py +++ b/swift/common/direct_client.py @@ -36,6 +36,57 @@ def quote(value, safe='/'): return _quote(value, safe) +def direct_get_account(node, part, account, marker=None, limit=None, + prefix=None, delimiter=None, conn_timeout=5, + response_timeout=15): + """ + Get listings directly from the account server. + + :param node: node dictionary from the ring + :param part: partition the account is on + :param account: account name + :param marker: marker query + :param limit: query limit + :param prefix: prefix query + :param delimeter: delimeter for the query + :param conn_timeout: timeout in seconds for establishing the connection + :param response_timeout: timeout in seconds for getting the response + :returns: a tuple of (response headers, a list of containers) The response + headers will be a dict and all header names will be lowercase. + """ + path = '/' + account + qs = 'format=json' + if marker: + qs += '&marker=%s' % quote(marker) + if limit: + qs += '&limit=%d' % limit + if prefix: + qs += '&prefix=%s' % quote(prefix) + if delimiter: + qs += '&delimiter=%s' % quote(delimiter) + with Timeout(conn_timeout): + conn = http_connect(node['ip'], node['port'], node['device'], part, + 'GET', path, query_string='format=json') + with Timeout(response_timeout): + resp = conn.getresponse() + if resp.status < 200 or resp.status >= 300: + resp.read() + raise ClientException( + 'Account server %s:%s direct GET %s gave status %s' % (node['ip'], + node['port'], repr('/%s/%s%s' % (node['device'], part, path)), + resp.status), + http_host=node['ip'], http_port=node['port'], + http_device=node['device'], http_status=resp.status, + http_reason=resp.reason) + resp_headers = {} + for header, value in resp.getheaders(): + resp_headers[header.lower()] = value + if resp.status == 204: + resp.read() + return resp_headers, [] + return resp_headers, json_loads(resp.read()) + + def direct_head_container(node, part, account, container, conn_timeout=5, response_timeout=15): """ diff --git a/swift/proxy/server.py b/swift/proxy/server.py index b092f7f9a4..0d0e0d944a 100644 --- a/swift/proxy/server.py +++ b/swift/proxy/server.py @@ -872,29 +872,39 @@ class ObjectController(Controller): @delay_denial def POST(self, req): """HTTP POST request handler.""" - error_response = check_metadata(req, 'object') - if error_response: - return error_response - container_partition, containers, _junk, req.acl = \ - self.container_info(self.account_name, self.container_name) - if 'swift.authorize' in req.environ: - aresp = req.environ['swift.authorize'](req) - if aresp: - return aresp - if not containers: - return HTTPNotFound(request=req) - partition, nodes = self.app.object_ring.get_nodes( - self.account_name, self.container_name, self.object_name) - req.headers['X-Timestamp'] = normalize_timestamp(time.time()) - headers = [] - for container in containers: - nheaders = dict(req.headers.iteritems()) - nheaders['X-Container-Host'] = '%(ip)s:%(port)s' % container - nheaders['X-Container-Partition'] = container_partition - nheaders['X-Container-Device'] = container['device'] - headers.append(nheaders) - return self.make_requests(req, self.app.object_ring, - partition, 'POST', req.path_info, headers) + if self.app.post_as_copy: + req.method = 'PUT' + req.path_info = '/%s/%s/%s' % (self.account_name, + self.container_name, self.object_name) + req.headers['Content-Length'] = 0 + req.headers['X-Copy-From'] = '/%s/%s' % (self.container_name, + self.object_name) + req.headers['X-Fresh-Metadata'] = 'true' + return self.PUT(req) + else: + error_response = check_metadata(req, 'object') + if error_response: + return error_response + container_partition, containers, _junk, req.acl = \ + self.container_info(self.account_name, self.container_name) + if 'swift.authorize' in req.environ: + aresp = req.environ['swift.authorize'](req) + if aresp: + return aresp + if not containers: + return HTTPNotFound(request=req) + partition, nodes = self.app.object_ring.get_nodes( + self.account_name, self.container_name, self.object_name) + req.headers['X-Timestamp'] = normalize_timestamp(time.time()) + headers = [] + for container in containers: + nheaders = dict(req.headers.iteritems()) + nheaders['X-Container-Host'] = '%(ip)s:%(port)s' % container + nheaders['X-Container-Partition'] = container_partition + nheaders['X-Container-Device'] = container['device'] + headers.append(nheaders) + return self.make_requests(req, self.app.object_ring, + partition, 'POST', req.path_info, headers) def _send_file(self, conn, path): """Method for a file PUT coro""" @@ -998,12 +1008,14 @@ class ObjectController(Controller): if not content_type_manually_set: new_req.headers['Content-Type'] = \ source_resp.headers['Content-Type'] - for k, v in source_resp.headers.items(): - if k.lower().startswith('x-object-meta-'): - new_req.headers[k] = v - for k, v in req.headers.items(): - if k.lower().startswith('x-object-meta-'): - new_req.headers[k] = v + if new_req.headers.get('x-fresh-metadata', 'false').lower() \ + not in TRUE_VALUES: + for k, v in source_resp.headers.items(): + if k.lower().startswith('x-object-meta-'): + new_req.headers[k] = v + for k, v in req.headers.items(): + if k.lower().startswith('x-object-meta-'): + new_req.headers[k] = v req = new_req node_iter = self.iter_nodes(partition, nodes, self.app.object_ring) pile = GreenPile(len(nodes)) @@ -1431,6 +1443,8 @@ class BaseApplication(object): int(conf.get('recheck_account_existence', 60)) self.allow_account_management = \ conf.get('allow_account_management', 'false').lower() == 'true' + self.post_as_copy = \ + conf.get('post_as_copy', 'true').lower() in TRUE_VALUES self.resellers_conf = ConfigParser() self.resellers_conf.read(os.path.join(swift_dir, 'resellers.conf')) self.object_ring = object_ring or \ diff --git a/test/functional/swift.py b/test/functional/swift.py index 9d395511b2..c9180e68da 100644 --- a/test/functional/swift.py +++ b/test/functional/swift.py @@ -668,7 +668,7 @@ class File(Base): self.conn.make_request('POST', self.path, hdrs=headers, cfg=cfg) - if self.conn.response.status != 202: + if self.conn.response.status not in (201, 202): raise ResponseError(self.conn.response) return True diff --git a/test/functional/tests.py b/test/functional/tests.py index 59dcf38960..c24bd87cf0 100644 --- a/test/functional/tests.py +++ b/test/functional/tests.py @@ -1032,7 +1032,7 @@ class TestFile(Base): self.assert_(file.write()) self.assert_status(201) self.assert_(file.sync_metadata()) - self.assert_status(202) + self.assert_status((201, 202)) else: self.assertRaises(ResponseError, file.write) self.assert_status(400) @@ -1245,7 +1245,7 @@ class TestFile(Base): file.metadata = metadata self.assert_(file.sync_metadata()) - self.assert_status(202) + self.assert_status((201, 202)) file = self.env.container.file(file.name) self.assert_(file.initialize()) diff --git a/test/probe/test_account_failures.py b/test/probe/test_account_failures.py index 807e397a57..29bfb6fc6f 100755 --- a/test/probe/test_account_failures.py +++ b/test/probe/test_account_failures.py @@ -20,7 +20,7 @@ from signal import SIGTERM from subprocess import Popen from time import sleep -from swift.common import client +from swift.common import client, direct_client from test.probe.common import get_to_final_state, kill_pids, reset_environment @@ -146,7 +146,8 @@ class TestAccountFailures(unittest.TestCase): sleep(2) # This is the earlier counts and bytes because the first node doesn't # have the newest udpates yet. - headers, containers = client.get_account(self.url, self.token) + headers, containers = \ + direct_client.direct_get_account(anodes[0], apart, self.account) self.assertEquals(headers['x-account-container-count'], '2') self.assertEquals(headers['x-account-object-count'], '1') self.assertEquals(headers['x-account-bytes-used'], '4') @@ -167,7 +168,8 @@ class TestAccountFailures(unittest.TestCase): self.assert_(found2) get_to_final_state() - headers, containers = client.get_account(self.url, self.token) + headers, containers = \ + direct_client.direct_get_account(anodes[0], apart, self.account) self.assertEquals(headers['x-account-container-count'], '1') self.assertEquals(headers['x-account-object-count'], '2') self.assertEquals(headers['x-account-bytes-used'], '9') diff --git a/test/probe/test_container_failures.py b/test/probe/test_container_failures.py index a493bffc27..005ece6290 100755 --- a/test/probe/test_container_failures.py +++ b/test/probe/test_container_failures.py @@ -24,7 +24,7 @@ from uuid import uuid4 import eventlet import sqlite3 -from swift.common import client +from swift.common import client, direct_client from swift.common.utils import hash_path, readconf from test.probe.common import get_to_final_state, kill_pids, reset_environment @@ -72,7 +72,8 @@ class TestContainerFailures(unittest.TestCase): # This okay because the first node hasn't got the update that the # object was deleted yet. self.assert_(object1 in [o['name'] for o in - client.get_container(self.url, self.token, container)[1]]) + direct_client.direct_get_container(cnodes[0], cpart, + self.account, container)[1]]) # Unfortunately, the following might pass or fail, depending on the # position of the account server associated with the first container @@ -88,7 +89,8 @@ class TestContainerFailures(unittest.TestCase): client.put_object(self.url, self.token, container, object2, 'test') # First node still doesn't know object1 was deleted yet; this is okay. self.assert_(object1 in [o['name'] for o in - client.get_container(self.url, self.token, container)[1]]) + direct_client.direct_get_container(cnodes[0], cpart, + self.account, container)[1]]) # And, of course, our new object2 exists. self.assert_(object2 in [o['name'] for o in client.get_container(self.url, self.token, container)[1]]) @@ -150,7 +152,8 @@ class TestContainerFailures(unittest.TestCase): # server has to indicate the container exists for the put to continue. client.put_object(self.url, self.token, container, object2, 'test') self.assert_(object1 not in [o['name'] for o in - client.get_container(self.url, self.token, container)[1]]) + direct_client.direct_get_container(cnodes[0], cpart, + self.account, container)[1]]) # And, of course, our new object2 exists. self.assert_(object2 in [o['name'] for o in client.get_container(self.url, self.token, container)[1]]) @@ -201,7 +204,8 @@ class TestContainerFailures(unittest.TestCase): # This okay because the first node hasn't got the update that the # object was deleted yet. self.assert_(object1 in [o['name'] for o in - client.get_container(self.url, self.token, container)[1]]) + direct_client.direct_get_container(cnodes[0], cpart, + self.account, container)[1]]) # This fails because all three nodes have to indicate deletion before # we tell the user it worked. Since the first node 409s (it hasn't got @@ -228,7 +232,8 @@ class TestContainerFailures(unittest.TestCase): client.put_object(self.url, self.token, container, object2, 'test') # First node still doesn't know object1 was deleted yet; this is okay. self.assert_(object1 in [o['name'] for o in - client.get_container(self.url, self.token, container)[1]]) + direct_client.direct_get_container(cnodes[0], cpart, + self.account, container)[1]]) # And, of course, our new object2 exists. self.assert_(object2 in [o['name'] for o in client.get_container(self.url, self.token, container)[1]]) @@ -277,7 +282,8 @@ class TestContainerFailures(unittest.TestCase): self.assert_(container in [c['name'] for c in client.get_account(self.url, self.token)[1]]) self.assert_(object1 not in [o['name'] for o in - client.get_container(self.url, self.token, container)[1]]) + direct_client.direct_get_container(cnodes[0], cpart, + self.account, container)[1]]) # This fails because all three nodes have to indicate deletion before # we tell the user it worked. Since the first node 409s (it hasn't got @@ -303,7 +309,8 @@ class TestContainerFailures(unittest.TestCase): # server has to indicate the container exists for the put to continue. client.put_object(self.url, self.token, container, object2, 'test') self.assert_(object1 not in [o['name'] for o in - client.get_container(self.url, self.token, container)[1]]) + direct_client.direct_get_container(cnodes[0], cpart, + self.account, container)[1]]) # And, of course, our new object2 exists. self.assert_(object2 in [o['name'] for o in client.get_container(self.url, self.token, container)[1]]) diff --git a/test/probe/test_object_handoff.py b/test/probe/test_object_handoff.py index 212fcc2c5e..8a4f9986ab 100755 --- a/test/probe/test_object_handoff.py +++ b/test/probe/test_object_handoff.py @@ -124,47 +124,49 @@ class TestObjectHandoff(unittest.TestCase): if not exc: raise Exception('Handoff object server still had test object') - kill(self.pids[self.port2server[onode['port']]], SIGTERM) - client.post_object(self.url, self.token, container, obj, - headers={'x-object-meta-probe': 'value'}) - oheaders = client.head_object(self.url, self.token, container, obj) - if oheaders.get('x-object-meta-probe') != 'value': - raise Exception('Metadata incorrect, was %s' % repr(oheaders)) - exc = False - try: - direct_client.direct_get_object(another_onode, opart, self.account, - container, obj) - except Exception: - exc = True - if not exc: - raise Exception('Handoff server claimed it had the object when ' - 'it should not have it') - self.pids[self.port2server[onode['port']]] = Popen([ - 'swift-object-server', - '/etc/swift/object-server/%d.conf' % - ((onode['port'] - 6000) / 10)]).pid - sleep(2) - oheaders = direct_client.direct_get_object(onode, opart, self.account, - container, obj)[0] - if oheaders.get('x-object-meta-probe') == 'value': - raise Exception('Previously downed object server had the new ' - 'metadata when it should not have it') - # Run the extra server last so it'll remove it's extra partition - ps = [] - for n in onodes: - ps.append(Popen(['swift-object-replicator', - '/etc/swift/object-server/%d.conf' % - ((n['port'] - 6000) / 10), 'once'])) - for p in ps: - p.wait() - call(['swift-object-replicator', - '/etc/swift/object-server/%d.conf' % - ((another_onode['port'] - 6000) / 10), 'once']) - oheaders = direct_client.direct_get_object(onode, opart, self.account, - container, obj)[0] - if oheaders.get('x-object-meta-probe') != 'value': - raise Exception( - 'Previously downed object server did not have the new metadata') +# Because POST has changed to a COPY by default, POSTs will succeed on all up +# nodes now if at least one up node has the object. +# kill(self.pids[self.port2server[onode['port']]], SIGTERM) +# client.post_object(self.url, self.token, container, obj, +# headers={'x-object-meta-probe': 'value'}) +# oheaders = client.head_object(self.url, self.token, container, obj) +# if oheaders.get('x-object-meta-probe') != 'value': +# raise Exception('Metadata incorrect, was %s' % repr(oheaders)) +# exc = False +# try: +# direct_client.direct_get_object(another_onode, opart, self.account, +# container, obj) +# except Exception: +# exc = True +# if not exc: +# raise Exception('Handoff server claimed it had the object when ' +# 'it should not have it') +# self.pids[self.port2server[onode['port']]] = Popen([ +# 'swift-object-server', +# '/etc/swift/object-server/%d.conf' % +# ((onode['port'] - 6000) / 10)]).pid +# sleep(2) +# oheaders = direct_client.direct_get_object(onode, opart, self.account, +# container, obj)[0] +# if oheaders.get('x-object-meta-probe') == 'value': +# raise Exception('Previously downed object server had the new ' +# 'metadata when it should not have it') +# # Run the extra server last so it'll remove it's extra partition +# ps = [] +# for n in onodes: +# ps.append(Popen(['swift-object-replicator', +# '/etc/swift/object-server/%d.conf' % +# ((n['port'] - 6000) / 10), 'once'])) +# for p in ps: +# p.wait() +# call(['swift-object-replicator', +# '/etc/swift/object-server/%d.conf' % +# ((another_onode['port'] - 6000) / 10), 'once']) +# oheaders = direct_client.direct_get_object(onode, opart, self.account, +# container, obj)[0] +# if oheaders.get('x-object-meta-probe') != 'value': +# raise Exception( +# 'Previously downed object server did not have the new metadata') kill(self.pids[self.port2server[onode['port']]], SIGTERM) client.delete_object(self.url, self.token, container, obj) diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index cc93877c45..0c9fa1aafd 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -925,6 +925,7 @@ class TestObjectController(unittest.TestCase): def test_POST(self): with save_globals(): + self.app.post_as_copy = False controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') @@ -945,6 +946,28 @@ class TestObjectController(unittest.TestCase): test_status_map((200, 200, 404, 500, 500), 503) test_status_map((200, 200, 404, 404, 404), 404) + def test_POST_as_copy(self): + with save_globals(): + controller = proxy_server.ObjectController(self.app, 'account', + 'container', 'object') + + def test_status_map(statuses, expected): + proxy_server.http_connect = fake_http_connect(*statuses) + self.app.memcache.store = {} + req = Request.blank('/a/c/o', {}, headers={ + 'Content-Type': 'foo/bar'}) + self.app.update_request(req) + res = controller.POST(req) + expected = str(expected) + self.assertEquals(res.status[:len(expected)], expected) + test_status_map((200, 200, 200, 200, 200, 202, 202, 202), 202) + test_status_map((200, 200, 200, 200, 200, 202, 202, 500), 202) + test_status_map((200, 200, 200, 200, 200, 202, 500, 500), 503) + test_status_map((200, 200, 200, 200, 200, 202, 404, 500), 503) + test_status_map((200, 200, 200, 200, 200, 202, 404, 404), 404) + test_status_map((200, 200, 200, 200, 200, 404, 500, 500), 503) + test_status_map((200, 200, 200, 200, 200, 404, 404, 404), 404) + def test_DELETE(self): with save_globals(): controller = proxy_server.ObjectController(self.app, 'account', @@ -1061,6 +1084,7 @@ class TestObjectController(unittest.TestCase): def test_POST_meta_val_len(self): with save_globals(): + self.app.post_as_copy = False controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') proxy_server.http_connect = \ @@ -1080,8 +1104,30 @@ class TestObjectController(unittest.TestCase): res = controller.POST(req) self.assertEquals(res.status_int, 400) + def test_POST_as_copy_meta_val_len(self): + with save_globals(): + controller = proxy_server.ObjectController(self.app, 'account', + 'container', 'object') + proxy_server.http_connect = \ + fake_http_connect(200, 200, 200, 200, 200, 202, 202, 202) + # acct cont objc objc objc obj obj obj + req = Request.blank('/a/c/o', {}, headers={ + 'Content-Type': 'foo/bar', + 'X-Object-Meta-Foo': 'x' * 256}) + self.app.update_request(req) + res = controller.POST(req) + self.assertEquals(res.status_int, 202) + proxy_server.http_connect = fake_http_connect(202, 202, 202) + req = Request.blank('/a/c/o', {}, headers={ + 'Content-Type': 'foo/bar', + 'X-Object-Meta-Foo': 'x' * 257}) + self.app.update_request(req) + res = controller.POST(req) + self.assertEquals(res.status_int, 400) + def test_POST_meta_key_len(self): with save_globals(): + self.app.post_as_copy = False controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') proxy_server.http_connect = \ @@ -1101,6 +1147,27 @@ class TestObjectController(unittest.TestCase): res = controller.POST(req) self.assertEquals(res.status_int, 400) + def test_POST_as_copy_meta_key_len(self): + with save_globals(): + controller = proxy_server.ObjectController(self.app, 'account', + 'container', 'object') + proxy_server.http_connect = \ + fake_http_connect(200, 200, 200, 200, 200, 202, 202, 202) + # acct cont objc objc objc obj obj obj + req = Request.blank('/a/c/o', {}, headers={ + 'Content-Type': 'foo/bar', + ('X-Object-Meta-' + 'x' * 128): 'x'}) + self.app.update_request(req) + res = controller.POST(req) + self.assertEquals(res.status_int, 202) + proxy_server.http_connect = fake_http_connect(202, 202, 202) + req = Request.blank('/a/c/o', {}, headers={ + 'Content-Type': 'foo/bar', + ('X-Object-Meta-' + 'x' * 129): 'x'}) + self.app.update_request(req) + res = controller.POST(req) + self.assertEquals(res.status_int, 400) + def test_POST_meta_count(self): with save_globals(): controller = proxy_server.ObjectController(self.app, 'account', @@ -1375,7 +1442,8 @@ class TestObjectController(unittest.TestCase): self.assert_status_map(controller.HEAD, (200, 200, 200), 503) self.assert_('last_error' in controller.app.object_ring.devs[0]) self.assert_status_map(controller.PUT, (200, 201, 201, 201), 503) - self.assert_status_map(controller.POST, (200, 202, 202, 202), 503) + self.assert_status_map(controller.POST, + (200, 200, 200, 200, 202, 202, 202), 503) self.assert_status_map(controller.DELETE, (200, 204, 204, 204), 503) self.app.error_suppression_interval = -300 @@ -1468,18 +1536,41 @@ class TestObjectController(unittest.TestCase): def test_PUT_POST_requires_container_exist(self): with save_globals(): + self.app.post_as_copy = False self.app.memcache = FakeMemcacheReturnsNone() controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') + proxy_server.http_connect = \ - fake_http_connect(404, 404, 404, 200, 200, 200) + fake_http_connect(200, 404, 404, 404, 200, 200, 200) req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}) self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 404) proxy_server.http_connect = \ - fake_http_connect(404, 404, 404, 200, 200, 200) + fake_http_connect(200, 404, 404, 404, 200, 200) + req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'POST'}, + headers={'Content-Type': 'text/plain'}) + self.app.update_request(req) + resp = controller.POST(req) + self.assertEquals(resp.status_int, 404) + + def test_PUT_POST_as_copy_requires_container_exist(self): + with save_globals(): + self.app.memcache = FakeMemcacheReturnsNone() + controller = proxy_server.ObjectController(self.app, 'account', + 'container', 'object') + proxy_server.http_connect = \ + fake_http_connect(200, 404, 404, 404, 200, 200, 200) + req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}) + self.app.update_request(req) + resp = controller.PUT(req) + self.assertEquals(resp.status_int, 404) + + proxy_server.http_connect = \ + fake_http_connect(200, 404, 404, 404, 200, 200, 200, 200, 200, + 200) req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'POST'}, headers={'Content-Type': 'text/plain'}) self.app.update_request(req) @@ -1599,8 +1690,10 @@ class TestObjectController(unittest.TestCase): 'X-Copy-From': 'c/o'}) self.app.update_request(req) proxy_server.http_connect = \ - fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc obj obj obj + fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, + 201) + # acct cont acct cont objc objc objc obj obj + # obj self.app.memcache.store = {} resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) @@ -1612,8 +1705,8 @@ class TestObjectController(unittest.TestCase): 'X-Copy-From': 'c/o'}) self.app.update_request(req) proxy_server.http_connect = \ - fake_http_connect(200, 200, 200, 200, 200) - # acct cont acct cont objc + fake_http_connect(200, 200, 200, 200, 200, 200, 200) + # acct cont acct cont objc objc objc self.app.memcache.store = {} resp = controller.PUT(req) self.assertEquals(resp.status_int, 400) @@ -1624,8 +1717,10 @@ class TestObjectController(unittest.TestCase): 'X-Copy-From': 'c/o/o2'}) req.account = 'a' proxy_server.http_connect = \ - fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc obj obj obj + fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, + 201) + # acct cont acct cont objc objc objc obj obj + # obj self.app.memcache.store = {} resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) @@ -1637,8 +1732,10 @@ class TestObjectController(unittest.TestCase): 'X-Copy-From': 'c/o%20o2'}) req.account = 'a' proxy_server.http_connect = \ - fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc obj obj obj + fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, + 201) + # acct cont acct cont objc objc objc obj obj + # obj self.app.memcache.store = {} resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) @@ -1650,8 +1747,10 @@ class TestObjectController(unittest.TestCase): 'X-Copy-From': '/c/o'}) self.app.update_request(req) proxy_server.http_connect = \ - fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc obj obj obj + fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, + 201) + # acct cont acct cont objc objc objc obj obj + # obj self.app.memcache.store = {} resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) @@ -1662,8 +1761,10 @@ class TestObjectController(unittest.TestCase): 'X-Copy-From': '/c/o/o2'}) req.account = 'a' proxy_server.http_connect = \ - fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc obj obj obj + fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, + 201) + # acct cont acct cont objc objc objc obj obj + # obj self.app.memcache.store = {} resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) @@ -1723,8 +1824,8 @@ class TestObjectController(unittest.TestCase): 'X-Object-Meta-Ours': 'okay'}) self.app.update_request(req) proxy_server.http_connect = \ - fake_http_connect(200, 200, 200, 201, 201, 201) - # acct cont objc obj obj obj + fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj self.app.memcache.store = {} resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) @@ -2652,6 +2753,7 @@ class TestObjectController(unittest.TestCase): called[0] = True return HTTPUnauthorized(request=req) with save_globals(): + self.app.post_as_copy = False proxy_server.http_connect = \ fake_http_connect(200, 200, 201, 201, 201) controller = proxy_server.ObjectController(self.app, 'account', @@ -2663,6 +2765,24 @@ class TestObjectController(unittest.TestCase): res = controller.POST(req) self.assert_(called[0]) + def test_POST_as_copy_calls_authorize(self): + called = [False] + + def authorize(req): + called[0] = True + return HTTPUnauthorized(request=req) + with save_globals(): + proxy_server.http_connect = \ + fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) + controller = proxy_server.ObjectController(self.app, 'account', + 'container', 'object') + req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'POST'}, + headers={'Content-Length': '5'}, body='12345') + req.environ['swift.authorize'] = authorize + self.app.update_request(req) + res = controller.POST(req) + self.assert_(called[0]) + def test_PUT_calls_authorize(self): called = [False]