Merge lp:~gholt/swift/postcopy into lp:~hudson-openstack/swift/trunk

Proposed by gholt
Status: Merged
Approved by: David Goetz
Approved revision: 311
Merged at revision: 315
Proposed branch: lp:~gholt/swift/postcopy
Merge into: lp:~hudson-openstack/swift/trunk
Diff against target: 1104 lines (+479/-138)
11 files modified
doc/source/deployment_guide.rst (+10/-0)
etc/proxy-server.conf-sample (+5/-0)
swift/common/direct_client.py (+51/-0)
swift/obj/server.py (+2/-0)
swift/proxy/server.py (+86/-43)
test/functional/swift.py (+1/-1)
test/functional/tests.py (+2/-2)
test/probe/test_account_failures.py (+5/-3)
test/probe/test_container_failures.py (+15/-8)
test/probe/test_object_handoff.py (+43/-41)
test/unit/proxy/test_server.py (+259/-40)
To merge this branch: bzr merge lp:~gholt/swift/postcopy
Reviewer Review Type Date Requested Status
David Goetz (community) Approve
John Dickinson Approve
Review via email: mp+63812@code.launchpad.net

Commit message

You can specify X-Newest: true on GETs and HEADs to indicate you want Swift to query all backend copies and return the newest version retrieved.
Object COPY requests now always copy the newest object they can find.
Object POSTs are implemented as COPYs now by default (you can revert to previous implementation with conf object_post_as_copy = false)
Account and container GETs and HEADs now shuffle the nodes they use to balance load.

Description of the change

You can specify X-Newest: true on GETs and HEADs to indicate you want Swift to query all backend copies and return the newest version retrieved.
Object COPY requests now always copy the newest object they can find.
Object POSTs are implemented as COPYs now by default (you can revert to previous implementation with conf object_post_as_copy = false)
Account and container GETs and HEADs now shuffle the nodes they use to balance load.

That last part I added because at first I thought it'd be quick and easy; but it ended up having to improve quite a few tests. I can separate it out to another merge if desired; but since it's already here, I left it for the moment.

To post a comment you must log in.
lp:~gholt/swift/postcopy updated
310. By gholt

Merged from trunk

Revision history for this message
John Dickinson (notmyname) wrote :

looks go to me

review: Approve
lp:~gholt/swift/postcopy updated
311. By gholt

Merged from trunk

Revision history for this message
David Goetz (david-goetz) wrote :

looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'doc/source/deployment_guide.rst'
--- doc/source/deployment_guide.rst 2011-06-05 23:22:35 +0000
+++ doc/source/deployment_guide.rst 2011-06-14 22:21:02 +0000
@@ -547,6 +547,16 @@
547 node error limited547 node error limited
548allow_account_management false Whether account PUTs and DELETEs548allow_account_management false Whether account PUTs and DELETEs
549 are even callable549 are even callable
550object_post_as_copy true Set object_post_as_copy = false
551 to turn on fast posts where only
552 the metadata changes are stored
553 anew and the original data file
554 is kept in place. This makes for
555 quicker posts; but since the
556 container metadata isn't updated
557 in this mode, features like
558 container sync won't be able to
559 sync posts.
550account_autocreate false If set to 'true' authorized560account_autocreate false If set to 'true' authorized
551 accounts that do not yet exist561 accounts that do not yet exist
552 within the Swift cluster will562 within the Swift cluster will
553563
=== modified file 'etc/proxy-server.conf-sample'
--- etc/proxy-server.conf-sample 2011-06-05 23:22:35 +0000
+++ etc/proxy-server.conf-sample 2011-06-14 22:21:02 +0000
@@ -40,6 +40,11 @@
40# If set to 'true' any authorized user may create and delete accounts; if40# If set to 'true' any authorized user may create and delete accounts; if
41# 'false' no one, even authorized, can.41# 'false' no one, even authorized, can.
42# allow_account_management = false42# allow_account_management = false
43# Set object_post_as_copy = false to turn on fast posts where only the metadata
44# changes are stored anew and the original data file is kept in place. This
45# makes for quicker posts; but since the container metadata isn't updated in
46# this mode, features like container sync won't be able to sync posts.
47# object_post_as_copy = true
43# If set to 'true' authorized accounts that do not yet exist within the Swift48# If set to 'true' authorized accounts that do not yet exist within the Swift
44# cluster will be automatically created.49# cluster will be automatically created.
45# account_autocreate = false50# account_autocreate = false
4651
=== modified file 'swift/common/direct_client.py'
--- swift/common/direct_client.py 2011-03-29 02:09:24 +0000
+++ swift/common/direct_client.py 2011-06-14 22:21:02 +0000
@@ -36,6 +36,57 @@
36 return _quote(value, safe)36 return _quote(value, safe)
3737
3838
39def direct_get_account(node, part, account, marker=None, limit=None,
40 prefix=None, delimiter=None, conn_timeout=5,
41 response_timeout=15):
42 """
43 Get listings directly from the account server.
44
45 :param node: node dictionary from the ring
46 :param part: partition the account is on
47 :param account: account name
48 :param marker: marker query
49 :param limit: query limit
50 :param prefix: prefix query
51 :param delimeter: delimeter for the query
52 :param conn_timeout: timeout in seconds for establishing the connection
53 :param response_timeout: timeout in seconds for getting the response
54 :returns: a tuple of (response headers, a list of containers) The response
55 headers will be a dict and all header names will be lowercase.
56 """
57 path = '/' + account
58 qs = 'format=json'
59 if marker:
60 qs += '&marker=%s' % quote(marker)
61 if limit:
62 qs += '&limit=%d' % limit
63 if prefix:
64 qs += '&prefix=%s' % quote(prefix)
65 if delimiter:
66 qs += '&delimiter=%s' % quote(delimiter)
67 with Timeout(conn_timeout):
68 conn = http_connect(node['ip'], node['port'], node['device'], part,
69 'GET', path, query_string='format=json')
70 with Timeout(response_timeout):
71 resp = conn.getresponse()
72 if resp.status < 200 or resp.status >= 300:
73 resp.read()
74 raise ClientException(
75 'Account server %s:%s direct GET %s gave status %s' % (node['ip'],
76 node['port'], repr('/%s/%s%s' % (node['device'], part, path)),
77 resp.status),
78 http_host=node['ip'], http_port=node['port'],
79 http_device=node['device'], http_status=resp.status,
80 http_reason=resp.reason)
81 resp_headers = {}
82 for header, value in resp.getheaders():
83 resp_headers[header.lower()] = value
84 if resp.status == 204:
85 resp.read()
86 return resp_headers, []
87 return resp_headers, json_loads(resp.read())
88
89
39def direct_head_container(node, part, account, container, conn_timeout=5,90def direct_head_container(node, part, account, container, conn_timeout=5,
40 response_timeout=15):91 response_timeout=15):
41 """92 """
4293
=== modified file 'swift/obj/server.py'
--- swift/obj/server.py 2011-05-09 20:21:34 +0000
+++ swift/obj/server.py 2011-06-14 22:21:02 +0000
@@ -623,6 +623,7 @@
623 file.keep_cache = True623 file.keep_cache = True
624 if 'Content-Encoding' in file.metadata:624 if 'Content-Encoding' in file.metadata:
625 response.content_encoding = file.metadata['Content-Encoding']625 response.content_encoding = file.metadata['Content-Encoding']
626 response.headers['X-Timestamp'] = file.metadata['X-Timestamp']
626 return request.get_response(response)627 return request.get_response(response)
627628
628 def HEAD(self, request):629 def HEAD(self, request):
@@ -657,6 +658,7 @@
657 response.content_length = file_size658 response.content_length = file_size
658 if 'Content-Encoding' in file.metadata:659 if 'Content-Encoding' in file.metadata:
659 response.content_encoding = file.metadata['Content-Encoding']660 response.content_encoding = file.metadata['Content-Encoding']
661 response.headers['X-Timestamp'] = file.metadata['X-Timestamp']
660 return response662 return response
661663
662 def DELETE(self, request):664 def DELETE(self, request):
663665
=== modified file 'swift/proxy/server.py'
--- swift/proxy/server.py 2011-06-11 04:57:04 +0000
+++ swift/proxy/server.py 2011-06-14 22:21:02 +0000
@@ -162,6 +162,7 @@
162 if self.segment > 10:162 if self.segment > 10:
163 sleep(max(self.next_get_time - time.time(), 0))163 sleep(max(self.next_get_time - time.time(), 0))
164 self.next_get_time = time.time() + 1164 self.next_get_time = time.time() + 1
165 shuffle(nodes)
165 resp = self.controller.GETorHEAD_base(req, _('Object'), partition,166 resp = self.controller.GETorHEAD_base(req, _('Object'), partition,
166 self.controller.iter_nodes(partition, nodes,167 self.controller.iter_nodes(partition, nodes,
167 self.controller.app.object_ring), path,168 self.controller.app.object_ring), path,
@@ -605,6 +606,8 @@
605 statuses = []606 statuses = []
606 reasons = []607 reasons = []
607 bodies = []608 bodies = []
609 source = None
610 newest = req.headers.get('x-newest', 'f').lower() in TRUE_VALUES
608 for node in nodes:611 for node in nodes:
609 if len(statuses) >= attempts:612 if len(statuses) >= attempts:
610 break613 break
@@ -617,23 +620,48 @@
617 headers=req.headers,620 headers=req.headers,
618 query_string=req.query_string)621 query_string=req.query_string)
619 with Timeout(self.app.node_timeout):622 with Timeout(self.app.node_timeout):
620 source = conn.getresponse()623 possible_source = conn.getresponse()
621 except (Exception, TimeoutError):624 except (Exception, TimeoutError):
622 self.exception_occurred(node, server_type,625 self.exception_occurred(node, server_type,
623 _('Trying to %(method)s %(path)s') %626 _('Trying to %(method)s %(path)s') %
624 {'method': req.method, 'path': req.path})627 {'method': req.method, 'path': req.path})
625 continue628 continue
626 if source.status == 507:629 if possible_source.status == 507:
627 self.error_limit(node)630 self.error_limit(node)
628 continue631 continue
629 if 200 <= source.status <= 399:632 if 200 <= possible_source.status <= 399:
630 # 404 if we know we don't have a synced copy633 # 404 if we know we don't have a synced copy
631 if not float(source.getheader('X-PUT-Timestamp', '1')):634 if not float(possible_source.getheader('X-PUT-Timestamp', 1)):
632 statuses.append(404)635 statuses.append(404)
633 reasons.append('')636 reasons.append('')
634 bodies.append('')637 bodies.append('')
635 source.read()638 possible_source.read()
636 continue639 continue
640 if (req.method == 'GET' and
641 possible_source.status in (200, 206)) or \
642 200 <= possible_source.status <= 399:
643 if newest:
644 ts = 0
645 if source:
646 ts = float(source.getheader('x-put-timestamp') or
647 source.getheader('x-timestamp') or 0)
648 pts = float(possible_source.getheader('x-put-timestamp') or
649 possible_source.getheader('x-timestamp') or 0)
650 if pts > ts:
651 source = possible_source
652 continue
653 else:
654 source = possible_source
655 break
656 statuses.append(possible_source.status)
657 reasons.append(possible_source.reason)
658 bodies.append(possible_source.read())
659 if possible_source.status >= 500:
660 self.error_occurred(node, _('ERROR %(status)d %(body)s ' \
661 'From %(type)s Server') %
662 {'status': possible_source.status,
663 'body': bodies[-1][:1024], 'type': server_type})
664 if source:
637 if req.method == 'GET' and source.status in (200, 206):665 if req.method == 'GET' and source.status in (200, 206):
638 res = Response(request=req, conditional_response=True)666 res = Response(request=req, conditional_response=True)
639 res.bytes_transferred = 0667 res.bytes_transferred = 0
@@ -673,13 +701,6 @@
673 res.charset = None701 res.charset = None
674 res.content_type = source.getheader('Content-Type')702 res.content_type = source.getheader('Content-Type')
675 return res703 return res
676 statuses.append(source.status)
677 reasons.append(source.reason)
678 bodies.append(source.read())
679 if source.status >= 500:
680 self.error_occurred(node, _('ERROR %(status)d %(body)s ' \
681 'From %(type)s Server') % {'status': source.status,
682 'body': bodies[-1][:1024], 'type': server_type})
683 return self.best_response(req, statuses, reasons, bodies,704 return self.best_response(req, statuses, reasons, bodies,
684 '%s %s' % (server_type, req.method))705 '%s %s' % (server_type, req.method))
685706
@@ -734,6 +755,7 @@
734 lreq = Request.blank('/%s/%s?prefix=%s&format=json&marker=%s' %755 lreq = Request.blank('/%s/%s?prefix=%s&format=json&marker=%s' %
735 (quote(self.account_name), quote(lcontainer),756 (quote(self.account_name), quote(lcontainer),
736 quote(lprefix), quote(marker)))757 quote(lprefix), quote(marker)))
758 shuffle(lnodes)
737 lresp = self.GETorHEAD_base(lreq, _('Container'), lpartition,759 lresp = self.GETorHEAD_base(lreq, _('Container'), lpartition,
738 lnodes, lreq.path_info,760 lnodes, lreq.path_info,
739 self.app.container_ring.replica_count)761 self.app.container_ring.replica_count)
@@ -861,30 +883,40 @@
861 @delay_denial883 @delay_denial
862 def POST(self, req):884 def POST(self, req):
863 """HTTP POST request handler."""885 """HTTP POST request handler."""
864 error_response = check_metadata(req, 'object')886 if self.app.object_post_as_copy:
865 if error_response:887 req.method = 'PUT'
866 return error_response888 req.path_info = '/%s/%s/%s' % (self.account_name,
867 container_partition, containers, _junk, req.acl = \889 self.container_name, self.object_name)
868 self.container_info(self.account_name, self.container_name,890 req.headers['Content-Length'] = 0
869 account_autocreate=self.app.account_autocreate)891 req.headers['X-Copy-From'] = '/%s/%s' % (self.container_name,
870 if 'swift.authorize' in req.environ:892 self.object_name)
871 aresp = req.environ['swift.authorize'](req)893 req.headers['X-Fresh-Metadata'] = 'true'
872 if aresp:894 return self.PUT(req)
873 return aresp895 else:
874 if not containers:896 error_response = check_metadata(req, 'object')
875 return HTTPNotFound(request=req)897 if error_response:
876 partition, nodes = self.app.object_ring.get_nodes(898 return error_response
877 self.account_name, self.container_name, self.object_name)899 container_partition, containers, _junk, req.acl = \
878 req.headers['X-Timestamp'] = normalize_timestamp(time.time())900 self.container_info(self.account_name, self.container_name,
879 headers = []901 account_autocreate=self.app.account_autocreate)
880 for container in containers:902 if 'swift.authorize' in req.environ:
881 nheaders = dict(req.headers.iteritems())903 aresp = req.environ['swift.authorize'](req)
882 nheaders['X-Container-Host'] = '%(ip)s:%(port)s' % container904 if aresp:
883 nheaders['X-Container-Partition'] = container_partition905 return aresp
884 nheaders['X-Container-Device'] = container['device']906 if not containers:
885 headers.append(nheaders)907 return HTTPNotFound(request=req)
886 return self.make_requests(req, self.app.object_ring,908 partition, nodes = self.app.object_ring.get_nodes(
887 partition, 'POST', req.path_info, headers)909 self.account_name, self.container_name, self.object_name)
910 req.headers['X-Timestamp'] = normalize_timestamp(time.time())
911 headers = []
912 for container in containers:
913 nheaders = dict(req.headers.iteritems())
914 nheaders['X-Container-Host'] = '%(ip)s:%(port)s' % container
915 nheaders['X-Container-Partition'] = container_partition
916 nheaders['X-Container-Device'] = container['device']
917 headers.append(nheaders)
918 return self.make_requests(req, self.app.object_ring,
919 partition, 'POST', req.path_info, headers)
888920
889 def _send_file(self, conn, path):921 def _send_file(self, conn, path):
890 """Method for a file PUT coro"""922 """Method for a file PUT coro"""
@@ -947,6 +979,7 @@
947 reader = req.environ['wsgi.input'].read979 reader = req.environ['wsgi.input'].read
948 data_source = iter(lambda: reader(self.app.client_chunk_size), '')980 data_source = iter(lambda: reader(self.app.client_chunk_size), '')
949 source_header = req.headers.get('X-Copy-From')981 source_header = req.headers.get('X-Copy-From')
982 source_resp = None
950 if source_header:983 if source_header:
951 source_header = unquote(source_header)984 source_header = unquote(source_header)
952 acct = req.path_info.split('/', 2)[1]985 acct = req.path_info.split('/', 2)[1]
@@ -962,6 +995,7 @@
962 '<container name>/<object name>')995 '<container name>/<object name>')
963 source_req = req.copy_get()996 source_req = req.copy_get()
964 source_req.path_info = source_header997 source_req.path_info = source_header
998 source_req.headers['X-Newest'] = 'true'
965 orig_obj_name = self.object_name999 orig_obj_name = self.object_name
966 orig_container_name = self.container_name1000 orig_container_name = self.container_name
967 self.object_name = src_obj_name1001 self.object_name = src_obj_name
@@ -987,12 +1021,14 @@
987 if not content_type_manually_set:1021 if not content_type_manually_set:
988 new_req.headers['Content-Type'] = \1022 new_req.headers['Content-Type'] = \
989 source_resp.headers['Content-Type']1023 source_resp.headers['Content-Type']
990 for k, v in source_resp.headers.items():1024 if new_req.headers.get('x-fresh-metadata', 'false').lower() \
991 if k.lower().startswith('x-object-meta-'):1025 not in TRUE_VALUES:
992 new_req.headers[k] = v1026 for k, v in source_resp.headers.items():
993 for k, v in req.headers.items():1027 if k.lower().startswith('x-object-meta-'):
994 if k.lower().startswith('x-object-meta-'):1028 new_req.headers[k] = v
995 new_req.headers[k] = v1029 for k, v in req.headers.items():
1030 if k.lower().startswith('x-object-meta-'):
1031 new_req.headers[k] = v
996 req = new_req1032 req = new_req
997 node_iter = self.iter_nodes(partition, nodes, self.app.object_ring)1033 node_iter = self.iter_nodes(partition, nodes, self.app.object_ring)
998 pile = GreenPile(len(nodes))1034 pile = GreenPile(len(nodes))
@@ -1094,6 +1130,9 @@
1094 if source_header:1130 if source_header:
1095 resp.headers['X-Copied-From'] = quote(1131 resp.headers['X-Copied-From'] = quote(
1096 source_header.split('/', 2)[2])1132 source_header.split('/', 2)[2])
1133 if 'last-modified' in source_resp.headers:
1134 resp.headers['X-Copied-From-Last-Modified'] = \
1135 source_resp.headers['last-modified']
1097 for k, v in req.headers.items():1136 for k, v in req.headers.items():
1098 if k.lower().startswith('x-object-meta-'):1137 if k.lower().startswith('x-object-meta-'):
1099 resp.headers[k] = v1138 resp.headers[k] = v
@@ -1187,6 +1226,7 @@
1187 return HTTPNotFound(request=req)1226 return HTTPNotFound(request=req)
1188 part, nodes = self.app.container_ring.get_nodes(1227 part, nodes = self.app.container_ring.get_nodes(
1189 self.account_name, self.container_name)1228 self.account_name, self.container_name)
1229 shuffle(nodes)
1190 resp = self.GETorHEAD_base(req, _('Container'), part, nodes,1230 resp = self.GETorHEAD_base(req, _('Container'), part, nodes,
1191 req.path_info, self.app.container_ring.replica_count)1231 req.path_info, self.app.container_ring.replica_count)
11921232
@@ -1319,6 +1359,7 @@
1319 def GETorHEAD(self, req):1359 def GETorHEAD(self, req):
1320 """Handler for HTTP GET/HEAD requests."""1360 """Handler for HTTP GET/HEAD requests."""
1321 partition, nodes = self.app.account_ring.get_nodes(self.account_name)1361 partition, nodes = self.app.account_ring.get_nodes(self.account_name)
1362 shuffle(nodes)
1322 resp = self.GETorHEAD_base(req, _('Account'), partition, nodes,1363 resp = self.GETorHEAD_base(req, _('Account'), partition, nodes,
1323 req.path_info.rstrip('/'), self.app.account_ring.replica_count)1364 req.path_info.rstrip('/'), self.app.account_ring.replica_count)
1324 if resp.status_int == 404 and self.app.account_autocreate:1365 if resp.status_int == 404 and self.app.account_autocreate:
@@ -1449,6 +1490,8 @@
1449 int(conf.get('recheck_account_existence', 60))1490 int(conf.get('recheck_account_existence', 60))
1450 self.allow_account_management = \1491 self.allow_account_management = \
1451 conf.get('allow_account_management', 'no').lower() in TRUE_VALUES1492 conf.get('allow_account_management', 'no').lower() in TRUE_VALUES
1493 self.object_post_as_copy = \
1494 conf.get('object_post_as_copy', 'true').lower() in TRUE_VALUES
1452 self.resellers_conf = ConfigParser()1495 self.resellers_conf = ConfigParser()
1453 self.resellers_conf.read(os.path.join(swift_dir, 'resellers.conf'))1496 self.resellers_conf.read(os.path.join(swift_dir, 'resellers.conf'))
1454 self.object_ring = object_ring or \1497 self.object_ring = object_ring or \
14551498
=== modified file 'test/functional/swift.py'
--- test/functional/swift.py 2011-02-23 04:25:38 +0000
+++ test/functional/swift.py 2011-06-14 22:21:02 +0000
@@ -668,7 +668,7 @@
668668
669 self.conn.make_request('POST', self.path, hdrs=headers, cfg=cfg)669 self.conn.make_request('POST', self.path, hdrs=headers, cfg=cfg)
670670
671 if self.conn.response.status != 202:671 if self.conn.response.status not in (201, 202):
672 raise ResponseError(self.conn.response)672 raise ResponseError(self.conn.response)
673673
674 return True674 return True
675675
=== modified file 'test/functional/tests.py'
--- test/functional/tests.py 2011-06-10 18:49:41 +0000
+++ test/functional/tests.py 2011-06-14 22:21:02 +0000
@@ -1032,7 +1032,7 @@
1032 self.assert_(file.write())1032 self.assert_(file.write())
1033 self.assert_status(201)1033 self.assert_status(201)
1034 self.assert_(file.sync_metadata())1034 self.assert_(file.sync_metadata())
1035 self.assert_status(202)1035 self.assert_status((201, 202))
1036 else:1036 else:
1037 self.assertRaises(ResponseError, file.write)1037 self.assertRaises(ResponseError, file.write)
1038 self.assert_status(400)1038 self.assert_status(400)
@@ -1245,7 +1245,7 @@
12451245
1246 file.metadata = metadata1246 file.metadata = metadata
1247 self.assert_(file.sync_metadata())1247 self.assert_(file.sync_metadata())
1248 self.assert_status(202)1248 self.assert_status((201, 202))
12491249
1250 file = self.env.container.file(file.name)1250 file = self.env.container.file(file.name)
1251 self.assert_(file.initialize())1251 self.assert_(file.initialize())
12521252
=== modified file 'test/probe/test_account_failures.py'
--- test/probe/test_account_failures.py 2011-01-04 23:34:43 +0000
+++ test/probe/test_account_failures.py 2011-06-14 22:21:02 +0000
@@ -20,7 +20,7 @@
20from subprocess import Popen20from subprocess import Popen
21from time import sleep21from time import sleep
2222
23from swift.common import client23from swift.common import client, direct_client
24from test.probe.common import get_to_final_state, kill_pids, reset_environment24from test.probe.common import get_to_final_state, kill_pids, reset_environment
2525
2626
@@ -146,7 +146,8 @@
146 sleep(2)146 sleep(2)
147 # This is the earlier counts and bytes because the first node doesn't147 # This is the earlier counts and bytes because the first node doesn't
148 # have the newest udpates yet.148 # have the newest udpates yet.
149 headers, containers = client.get_account(self.url, self.token)149 headers, containers = \
150 direct_client.direct_get_account(anodes[0], apart, self.account)
150 self.assertEquals(headers['x-account-container-count'], '2')151 self.assertEquals(headers['x-account-container-count'], '2')
151 self.assertEquals(headers['x-account-object-count'], '1')152 self.assertEquals(headers['x-account-object-count'], '1')
152 self.assertEquals(headers['x-account-bytes-used'], '4')153 self.assertEquals(headers['x-account-bytes-used'], '4')
@@ -167,7 +168,8 @@
167 self.assert_(found2)168 self.assert_(found2)
168169
169 get_to_final_state()170 get_to_final_state()
170 headers, containers = client.get_account(self.url, self.token)171 headers, containers = \
172 direct_client.direct_get_account(anodes[0], apart, self.account)
171 self.assertEquals(headers['x-account-container-count'], '1')173 self.assertEquals(headers['x-account-container-count'], '1')
172 self.assertEquals(headers['x-account-object-count'], '2')174 self.assertEquals(headers['x-account-object-count'], '2')
173 self.assertEquals(headers['x-account-bytes-used'], '9')175 self.assertEquals(headers['x-account-bytes-used'], '9')
174176
=== modified file 'test/probe/test_container_failures.py'
--- test/probe/test_container_failures.py 2011-04-13 17:57:59 +0000
+++ test/probe/test_container_failures.py 2011-06-14 22:21:02 +0000
@@ -24,7 +24,7 @@
24import eventlet24import eventlet
25import sqlite325import sqlite3
2626
27from swift.common import client27from swift.common import client, direct_client
28from swift.common.utils import hash_path, readconf28from swift.common.utils import hash_path, readconf
2929
30from test.probe.common import get_to_final_state, kill_pids, reset_environment30from test.probe.common import get_to_final_state, kill_pids, reset_environment
@@ -72,7 +72,8 @@
72 # This okay because the first node hasn't got the update that the72 # This okay because the first node hasn't got the update that the
73 # object was deleted yet.73 # object was deleted yet.
74 self.assert_(object1 in [o['name'] for o in74 self.assert_(object1 in [o['name'] for o in
75 client.get_container(self.url, self.token, container)[1]])75 direct_client.direct_get_container(cnodes[0], cpart,
76 self.account, container)[1]])
7677
77 # Unfortunately, the following might pass or fail, depending on the78 # Unfortunately, the following might pass or fail, depending on the
78 # position of the account server associated with the first container79 # position of the account server associated with the first container
@@ -88,7 +89,8 @@
88 client.put_object(self.url, self.token, container, object2, 'test')89 client.put_object(self.url, self.token, container, object2, 'test')
89 # First node still doesn't know object1 was deleted yet; this is okay.90 # First node still doesn't know object1 was deleted yet; this is okay.
90 self.assert_(object1 in [o['name'] for o in91 self.assert_(object1 in [o['name'] for o in
91 client.get_container(self.url, self.token, container)[1]])92 direct_client.direct_get_container(cnodes[0], cpart,
93 self.account, container)[1]])
92 # And, of course, our new object2 exists.94 # And, of course, our new object2 exists.
93 self.assert_(object2 in [o['name'] for o in95 self.assert_(object2 in [o['name'] for o in
94 client.get_container(self.url, self.token, container)[1]])96 client.get_container(self.url, self.token, container)[1]])
@@ -150,7 +152,8 @@
150 # server has to indicate the container exists for the put to continue.152 # server has to indicate the container exists for the put to continue.
151 client.put_object(self.url, self.token, container, object2, 'test')153 client.put_object(self.url, self.token, container, object2, 'test')
152 self.assert_(object1 not in [o['name'] for o in154 self.assert_(object1 not in [o['name'] for o in
153 client.get_container(self.url, self.token, container)[1]])155 direct_client.direct_get_container(cnodes[0], cpart,
156 self.account, container)[1]])
154 # And, of course, our new object2 exists.157 # And, of course, our new object2 exists.
155 self.assert_(object2 in [o['name'] for o in158 self.assert_(object2 in [o['name'] for o in
156 client.get_container(self.url, self.token, container)[1]])159 client.get_container(self.url, self.token, container)[1]])
@@ -201,7 +204,8 @@
201 # This okay because the first node hasn't got the update that the204 # This okay because the first node hasn't got the update that the
202 # object was deleted yet.205 # object was deleted yet.
203 self.assert_(object1 in [o['name'] for o in206 self.assert_(object1 in [o['name'] for o in
204 client.get_container(self.url, self.token, container)[1]])207 direct_client.direct_get_container(cnodes[0], cpart,
208 self.account, container)[1]])
205209
206 # This fails because all three nodes have to indicate deletion before210 # This fails because all three nodes have to indicate deletion before
207 # we tell the user it worked. Since the first node 409s (it hasn't got211 # we tell the user it worked. Since the first node 409s (it hasn't got
@@ -228,7 +232,8 @@
228 client.put_object(self.url, self.token, container, object2, 'test')232 client.put_object(self.url, self.token, container, object2, 'test')
229 # First node still doesn't know object1 was deleted yet; this is okay.233 # First node still doesn't know object1 was deleted yet; this is okay.
230 self.assert_(object1 in [o['name'] for o in234 self.assert_(object1 in [o['name'] for o in
231 client.get_container(self.url, self.token, container)[1]])235 direct_client.direct_get_container(cnodes[0], cpart,
236 self.account, container)[1]])
232 # And, of course, our new object2 exists.237 # And, of course, our new object2 exists.
233 self.assert_(object2 in [o['name'] for o in238 self.assert_(object2 in [o['name'] for o in
234 client.get_container(self.url, self.token, container)[1]])239 client.get_container(self.url, self.token, container)[1]])
@@ -277,7 +282,8 @@
277 self.assert_(container in [c['name'] for c in282 self.assert_(container in [c['name'] for c in
278 client.get_account(self.url, self.token)[1]])283 client.get_account(self.url, self.token)[1]])
279 self.assert_(object1 not in [o['name'] for o in284 self.assert_(object1 not in [o['name'] for o in
280 client.get_container(self.url, self.token, container)[1]])285 direct_client.direct_get_container(cnodes[0], cpart,
286 self.account, container)[1]])
281287
282 # This fails because all three nodes have to indicate deletion before288 # This fails because all three nodes have to indicate deletion before
283 # we tell the user it worked. Since the first node 409s (it hasn't got289 # we tell the user it worked. Since the first node 409s (it hasn't got
@@ -303,7 +309,8 @@
303 # server has to indicate the container exists for the put to continue.309 # server has to indicate the container exists for the put to continue.
304 client.put_object(self.url, self.token, container, object2, 'test')310 client.put_object(self.url, self.token, container, object2, 'test')
305 self.assert_(object1 not in [o['name'] for o in311 self.assert_(object1 not in [o['name'] for o in
306 client.get_container(self.url, self.token, container)[1]])312 direct_client.direct_get_container(cnodes[0], cpart,
313 self.account, container)[1]])
307 # And, of course, our new object2 exists.314 # And, of course, our new object2 exists.
308 self.assert_(object2 in [o['name'] for o in315 self.assert_(object2 in [o['name'] for o in
309 client.get_container(self.url, self.token, container)[1]])316 client.get_container(self.url, self.token, container)[1]])
310317
=== modified file 'test/probe/test_object_handoff.py'
--- test/probe/test_object_handoff.py 2011-01-26 22:31:33 +0000
+++ test/probe/test_object_handoff.py 2011-06-14 22:21:02 +0000
@@ -124,47 +124,49 @@
124 if not exc:124 if not exc:
125 raise Exception('Handoff object server still had test object')125 raise Exception('Handoff object server still had test object')
126126
127 kill(self.pids[self.port2server[onode['port']]], SIGTERM)127# Because POST has changed to a COPY by default, POSTs will succeed on all up
128 client.post_object(self.url, self.token, container, obj,128# nodes now if at least one up node has the object.
129 headers={'x-object-meta-probe': 'value'})129# kill(self.pids[self.port2server[onode['port']]], SIGTERM)
130 oheaders = client.head_object(self.url, self.token, container, obj)130# client.post_object(self.url, self.token, container, obj,
131 if oheaders.get('x-object-meta-probe') != 'value':131# headers={'x-object-meta-probe': 'value'})
132 raise Exception('Metadata incorrect, was %s' % repr(oheaders))132# oheaders = client.head_object(self.url, self.token, container, obj)
133 exc = False133# if oheaders.get('x-object-meta-probe') != 'value':
134 try:134# raise Exception('Metadata incorrect, was %s' % repr(oheaders))
135 direct_client.direct_get_object(another_onode, opart, self.account,135# exc = False
136 container, obj)136# try:
137 except Exception:137# direct_client.direct_get_object(another_onode, opart, self.account,
138 exc = True138# container, obj)
139 if not exc:139# except Exception:
140 raise Exception('Handoff server claimed it had the object when '140# exc = True
141 'it should not have it')141# if not exc:
142 self.pids[self.port2server[onode['port']]] = Popen([142# raise Exception('Handoff server claimed it had the object when '
143 'swift-object-server',143# 'it should not have it')
144 '/etc/swift/object-server/%d.conf' %144# self.pids[self.port2server[onode['port']]] = Popen([
145 ((onode['port'] - 6000) / 10)]).pid145# 'swift-object-server',
146 sleep(2)146# '/etc/swift/object-server/%d.conf' %
147 oheaders = direct_client.direct_get_object(onode, opart, self.account,147# ((onode['port'] - 6000) / 10)]).pid
148 container, obj)[0]148# sleep(2)
149 if oheaders.get('x-object-meta-probe') == 'value':149# oheaders = direct_client.direct_get_object(onode, opart, self.account,
150 raise Exception('Previously downed object server had the new '150# container, obj)[0]
151 'metadata when it should not have it')151# if oheaders.get('x-object-meta-probe') == 'value':
152 # Run the extra server last so it'll remove it's extra partition152# raise Exception('Previously downed object server had the new '
153 ps = []153# 'metadata when it should not have it')
154 for n in onodes:154# # Run the extra server last so it'll remove it's extra partition
155 ps.append(Popen(['swift-object-replicator',155# ps = []
156 '/etc/swift/object-server/%d.conf' %156# for n in onodes:
157 ((n['port'] - 6000) / 10), 'once']))157# ps.append(Popen(['swift-object-replicator',
158 for p in ps:158# '/etc/swift/object-server/%d.conf' %
159 p.wait()159# ((n['port'] - 6000) / 10), 'once']))
160 call(['swift-object-replicator',160# for p in ps:
161 '/etc/swift/object-server/%d.conf' %161# p.wait()
162 ((another_onode['port'] - 6000) / 10), 'once'])162# call(['swift-object-replicator',
163 oheaders = direct_client.direct_get_object(onode, opart, self.account,163# '/etc/swift/object-server/%d.conf' %
164 container, obj)[0]164# ((another_onode['port'] - 6000) / 10), 'once'])
165 if oheaders.get('x-object-meta-probe') != 'value':165# oheaders = direct_client.direct_get_object(onode, opart, self.account,
166 raise Exception(166# container, obj)[0]
167 'Previously downed object server did not have the new metadata')167# if oheaders.get('x-object-meta-probe') != 'value':
168# raise Exception(
169# 'Previously downed object server did not have the new metadata')
168170
169 kill(self.pids[self.port2server[onode['port']]], SIGTERM)171 kill(self.pids[self.port2server[onode['port']]], SIGTERM)
170 client.delete_object(self.url, self.token, container, obj)172 client.delete_object(self.url, self.token, container, obj)
171173
=== modified file 'test/unit/proxy/test_server.py'
--- test/unit/proxy/test_server.py 2011-06-11 04:57:04 +0000
+++ test/unit/proxy/test_server.py 2011-06-14 22:21:02 +0000
@@ -150,7 +150,7 @@
150150
151 class FakeConn(object):151 class FakeConn(object):
152152
153 def __init__(self, status, etag=None, body=''):153 def __init__(self, status, etag=None, body='', timestamp='1'):
154 self.status = status154 self.status = status
155 self.reason = 'Fake'155 self.reason = 'Fake'
156 self.host = '1.2.3.4'156 self.host = '1.2.3.4'
@@ -159,6 +159,7 @@
159 self.received = 0159 self.received = 0
160 self.etag = etag160 self.etag = etag
161 self.body = body161 self.body = body
162 self.timestamp = timestamp
162163
163 def getresponse(self):164 def getresponse(self):
164 if kwargs.get('raise_exc'):165 if kwargs.get('raise_exc'):
@@ -173,7 +174,8 @@
173 def getheaders(self):174 def getheaders(self):
174 headers = {'content-length': len(self.body),175 headers = {'content-length': len(self.body),
175 'content-type': 'x-application/test',176 'content-type': 'x-application/test',
176 'x-timestamp': '1',177 'x-timestamp': self.timestamp,
178 'last-modified': self.timestamp,
177 'x-object-meta-test': 'testing',179 'x-object-meta-test': 'testing',
178 'etag':180 'etag':
179 self.etag or '"68b329da9893e34099c7d8ad5cb9c940"',181 self.etag or '"68b329da9893e34099c7d8ad5cb9c940"',
@@ -209,6 +211,7 @@
209 def getheader(self, name, default=None):211 def getheader(self, name, default=None):
210 return dict(self.getheaders()).get(name.lower(), default)212 return dict(self.getheaders()).get(name.lower(), default)
211213
214 timestamps_iter = iter(kwargs.get('timestamps') or ['1'] * len(code_iter))
212 etag_iter = iter(kwargs.get('etags') or [None] * len(code_iter))215 etag_iter = iter(kwargs.get('etags') or [None] * len(code_iter))
213 x = kwargs.get('missing_container', [False] * len(code_iter))216 x = kwargs.get('missing_container', [False] * len(code_iter))
214 if not isinstance(x, (tuple, list)):217 if not isinstance(x, (tuple, list)):
@@ -226,9 +229,11 @@
226 kwargs['give_connect'](*args, **ckwargs)229 kwargs['give_connect'](*args, **ckwargs)
227 status = code_iter.next()230 status = code_iter.next()
228 etag = etag_iter.next()231 etag = etag_iter.next()
232 timestamp = timestamps_iter.next()
229 if status == -1:233 if status == -1:
230 raise HTTPException()234 raise HTTPException()
231 return FakeConn(status, etag, body=kwargs.get('body', ''))235 return FakeConn(status, etag, body=kwargs.get('body', ''),
236 timestamp=timestamp)
232237
233 return connect238 return connect
234239
@@ -962,6 +967,7 @@
962967
963 def test_POST(self):968 def test_POST(self):
964 with save_globals():969 with save_globals():
970 self.app.object_post_as_copy = False
965 controller = proxy_server.ObjectController(self.app, 'account',971 controller = proxy_server.ObjectController(self.app, 'account',
966 'container', 'object')972 'container', 'object')
967973
@@ -982,6 +988,28 @@
982 test_status_map((200, 200, 404, 500, 500), 503)988 test_status_map((200, 200, 404, 500, 500), 503)
983 test_status_map((200, 200, 404, 404, 404), 404)989 test_status_map((200, 200, 404, 404, 404), 404)
984990
991 def test_POST_as_copy(self):
992 with save_globals():
993 controller = proxy_server.ObjectController(self.app, 'account',
994 'container', 'object')
995
996 def test_status_map(statuses, expected):
997 proxy_server.http_connect = fake_http_connect(*statuses)
998 self.app.memcache.store = {}
999 req = Request.blank('/a/c/o', {}, headers={
1000 'Content-Type': 'foo/bar'})
1001 self.app.update_request(req)
1002 res = controller.POST(req)
1003 expected = str(expected)
1004 self.assertEquals(res.status[:len(expected)], expected)
1005 test_status_map((200, 200, 200, 200, 200, 202, 202, 202), 202)
1006 test_status_map((200, 200, 200, 200, 200, 202, 202, 500), 202)
1007 test_status_map((200, 200, 200, 200, 200, 202, 500, 500), 503)
1008 test_status_map((200, 200, 200, 200, 200, 202, 404, 500), 503)
1009 test_status_map((200, 200, 200, 200, 200, 202, 404, 404), 404)
1010 test_status_map((200, 200, 200, 200, 200, 404, 500, 500), 503)
1011 test_status_map((200, 200, 200, 200, 200, 404, 404, 404), 404)
1012
985 def test_DELETE(self):1013 def test_DELETE(self):
986 with save_globals():1014 with save_globals():
987 controller = proxy_server.ObjectController(self.app, 'account',1015 controller = proxy_server.ObjectController(self.app, 'account',
@@ -1028,8 +1056,77 @@
1028 test_status_map((404, 404, 500), 404)1056 test_status_map((404, 404, 500), 404)
1029 test_status_map((500, 500, 500), 503)1057 test_status_map((500, 500, 500), 503)
10301058
1059 def test_HEAD_newest(self):
1060 with save_globals():
1061 controller = proxy_server.ObjectController(self.app, 'account',
1062 'container', 'object')
1063
1064 def test_status_map(statuses, expected, timestamps,
1065 expected_timestamp):
1066 proxy_server.http_connect = \
1067 fake_http_connect(*statuses, timestamps=timestamps)
1068 self.app.memcache.store = {}
1069 req = Request.blank('/a/c/o', {}, headers={'x-newest': 'true'})
1070 self.app.update_request(req)
1071 res = controller.HEAD(req)
1072 self.assertEquals(res.status[:len(str(expected))],
1073 str(expected))
1074 self.assertEquals(res.headers.get('last-modified'),
1075 expected_timestamp)
1076
1077 test_status_map((200, 200, 200), 200, ('1', '2', '3'), '3')
1078 test_status_map((200, 200, 200), 200, ('1', '3', '2'), '3')
1079 test_status_map((200, 200, 200), 200, ('1', '3', '1'), '3')
1080 test_status_map((200, 200, 200), 200, ('3', '3', '1'), '3')
1081
1082 def test_GET_newest(self):
1083 with save_globals():
1084 controller = proxy_server.ObjectController(self.app, 'account',
1085 'container', 'object')
1086
1087 def test_status_map(statuses, expected, timestamps,
1088 expected_timestamp):
1089 proxy_server.http_connect = \
1090 fake_http_connect(*statuses, timestamps=timestamps)
1091 self.app.memcache.store = {}
1092 req = Request.blank('/a/c/o', {}, headers={'x-newest': 'true'})
1093 self.app.update_request(req)
1094 res = controller.GET(req)
1095 self.assertEquals(res.status[:len(str(expected))],
1096 str(expected))
1097 self.assertEquals(res.headers.get('last-modified'),
1098 expected_timestamp)
1099
1100 test_status_map((200, 200, 200), 200, ('1', '2', '3'), '3')
1101 test_status_map((200, 200, 200), 200, ('1', '3', '2'), '3')
1102 test_status_map((200, 200, 200), 200, ('1', '3', '1'), '3')
1103 test_status_map((200, 200, 200), 200, ('3', '3', '1'), '3')
1104
1105 with save_globals():
1106 controller = proxy_server.ObjectController(self.app, 'account',
1107 'container', 'object')
1108
1109 def test_status_map(statuses, expected, timestamps,
1110 expected_timestamp):
1111 proxy_server.http_connect = \
1112 fake_http_connect(*statuses, timestamps=timestamps)
1113 self.app.memcache.store = {}
1114 req = Request.blank('/a/c/o', {})
1115 self.app.update_request(req)
1116 res = controller.HEAD(req)
1117 self.assertEquals(res.status[:len(str(expected))],
1118 str(expected))
1119 self.assertEquals(res.headers.get('last-modified'),
1120 expected_timestamp)
1121
1122 test_status_map((200, 200, 200), 200, ('1', '2', '3'), '1')
1123 test_status_map((200, 200, 200), 200, ('1', '3', '2'), '1')
1124 test_status_map((200, 200, 200), 200, ('1', '3', '1'), '1')
1125 test_status_map((200, 200, 200), 200, ('3', '3', '1'), '3')
1126
1031 def test_POST_meta_val_len(self):1127 def test_POST_meta_val_len(self):
1032 with save_globals():1128 with save_globals():
1129 self.app.object_post_as_copy = False
1033 controller = proxy_server.ObjectController(self.app, 'account',1130 controller = proxy_server.ObjectController(self.app, 'account',
1034 'container', 'object')1131 'container', 'object')
1035 proxy_server.http_connect = \1132 proxy_server.http_connect = \
@@ -1049,8 +1146,30 @@
1049 res = controller.POST(req)1146 res = controller.POST(req)
1050 self.assertEquals(res.status_int, 400)1147 self.assertEquals(res.status_int, 400)
10511148
1149 def test_POST_as_copy_meta_val_len(self):
1150 with save_globals():
1151 controller = proxy_server.ObjectController(self.app, 'account',
1152 'container', 'object')
1153 proxy_server.http_connect = \
1154 fake_http_connect(200, 200, 200, 200, 200, 202, 202, 202)
1155 # acct cont objc objc objc obj obj obj
1156 req = Request.blank('/a/c/o', {}, headers={
1157 'Content-Type': 'foo/bar',
1158 'X-Object-Meta-Foo': 'x' * 256})
1159 self.app.update_request(req)
1160 res = controller.POST(req)
1161 self.assertEquals(res.status_int, 202)
1162 proxy_server.http_connect = fake_http_connect(202, 202, 202)
1163 req = Request.blank('/a/c/o', {}, headers={
1164 'Content-Type': 'foo/bar',
1165 'X-Object-Meta-Foo': 'x' * 257})
1166 self.app.update_request(req)
1167 res = controller.POST(req)
1168 self.assertEquals(res.status_int, 400)
1169
1052 def test_POST_meta_key_len(self):1170 def test_POST_meta_key_len(self):
1053 with save_globals():1171 with save_globals():
1172 self.app.object_post_as_copy = False
1054 controller = proxy_server.ObjectController(self.app, 'account',1173 controller = proxy_server.ObjectController(self.app, 'account',
1055 'container', 'object')1174 'container', 'object')
1056 proxy_server.http_connect = \1175 proxy_server.http_connect = \
@@ -1070,6 +1189,27 @@
1070 res = controller.POST(req)1189 res = controller.POST(req)
1071 self.assertEquals(res.status_int, 400)1190 self.assertEquals(res.status_int, 400)
10721191
1192 def test_POST_as_copy_meta_key_len(self):
1193 with save_globals():
1194 controller = proxy_server.ObjectController(self.app, 'account',
1195 'container', 'object')
1196 proxy_server.http_connect = \
1197 fake_http_connect(200, 200, 200, 200, 200, 202, 202, 202)
1198 # acct cont objc objc objc obj obj obj
1199 req = Request.blank('/a/c/o', {}, headers={
1200 'Content-Type': 'foo/bar',
1201 ('X-Object-Meta-' + 'x' * 128): 'x'})
1202 self.app.update_request(req)
1203 res = controller.POST(req)
1204 self.assertEquals(res.status_int, 202)
1205 proxy_server.http_connect = fake_http_connect(202, 202, 202)
1206 req = Request.blank('/a/c/o', {}, headers={
1207 'Content-Type': 'foo/bar',
1208 ('X-Object-Meta-' + 'x' * 129): 'x'})
1209 self.app.update_request(req)
1210 res = controller.POST(req)
1211 self.assertEquals(res.status_int, 400)
1212
1073 def test_POST_meta_count(self):1213 def test_POST_meta_count(self):
1074 with save_globals():1214 with save_globals():
1075 controller = proxy_server.ObjectController(self.app, 'account',1215 controller = proxy_server.ObjectController(self.app, 'account',
@@ -1344,7 +1484,8 @@
1344 self.assert_status_map(controller.HEAD, (200, 200, 200), 503)1484 self.assert_status_map(controller.HEAD, (200, 200, 200), 503)
1345 self.assert_('last_error' in controller.app.object_ring.devs[0])1485 self.assert_('last_error' in controller.app.object_ring.devs[0])
1346 self.assert_status_map(controller.PUT, (200, 201, 201, 201), 503)1486 self.assert_status_map(controller.PUT, (200, 201, 201, 201), 503)
1347 self.assert_status_map(controller.POST, (200, 202, 202, 202), 503)1487 self.assert_status_map(controller.POST,
1488 (200, 200, 200, 200, 202, 202, 202), 503)
1348 self.assert_status_map(controller.DELETE,1489 self.assert_status_map(controller.DELETE,
1349 (200, 204, 204, 204), 503)1490 (200, 204, 204, 204), 503)
1350 self.app.error_suppression_interval = -3001491 self.app.error_suppression_interval = -300
@@ -1437,18 +1578,41 @@
14371578
1438 def test_PUT_POST_requires_container_exist(self):1579 def test_PUT_POST_requires_container_exist(self):
1439 with save_globals():1580 with save_globals():
1440 self.app.memcache = FakeMemcacheReturnsNone()1581 self.app.object_post_as_copy = False
1441 controller = proxy_server.ObjectController(self.app, 'account',1582 self.app.memcache = FakeMemcacheReturnsNone()
1442 'container', 'object')1583 controller = proxy_server.ObjectController(self.app, 'account',
1443 proxy_server.http_connect = \1584 'container', 'object')
1444 fake_http_connect(404, 404, 404, 200, 200, 200)1585
1445 req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'})1586 proxy_server.http_connect = \
1446 self.app.update_request(req)1587 fake_http_connect(200, 404, 404, 404, 200, 200, 200)
1447 resp = controller.PUT(req)1588 req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'})
1448 self.assertEquals(resp.status_int, 404)1589 self.app.update_request(req)
14491590 resp = controller.PUT(req)
1450 proxy_server.http_connect = \1591 self.assertEquals(resp.status_int, 404)
1451 fake_http_connect(404, 404, 404, 200, 200, 200)1592
1593 proxy_server.http_connect = \
1594 fake_http_connect(200, 404, 404, 404, 200, 200)
1595 req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'POST'},
1596 headers={'Content-Type': 'text/plain'})
1597 self.app.update_request(req)
1598 resp = controller.POST(req)
1599 self.assertEquals(resp.status_int, 404)
1600
1601 def test_PUT_POST_as_copy_requires_container_exist(self):
1602 with save_globals():
1603 self.app.memcache = FakeMemcacheReturnsNone()
1604 controller = proxy_server.ObjectController(self.app, 'account',
1605 'container', 'object')
1606 proxy_server.http_connect = \
1607 fake_http_connect(200, 404, 404, 404, 200, 200, 200)
1608 req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'})
1609 self.app.update_request(req)
1610 resp = controller.PUT(req)
1611 self.assertEquals(resp.status_int, 404)
1612
1613 proxy_server.http_connect = \
1614 fake_http_connect(200, 404, 404, 404, 200, 200, 200, 200, 200,
1615 200)
1452 req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'POST'},1616 req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'POST'},
1453 headers={'Content-Type': 'text/plain'})1617 headers={'Content-Type': 'text/plain'})
1454 self.app.update_request(req)1618 self.app.update_request(req)
@@ -1568,8 +1732,10 @@
1568 'X-Copy-From': 'c/o'})1732 'X-Copy-From': 'c/o'})
1569 self.app.update_request(req)1733 self.app.update_request(req)
1570 proxy_server.http_connect = \1734 proxy_server.http_connect = \
1571 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)1735 fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201,
1572 # acct cont acct cont objc obj obj obj1736 201)
1737 # acct cont acct cont objc objc objc obj obj
1738 # obj
1573 self.app.memcache.store = {}1739 self.app.memcache.store = {}
1574 resp = controller.PUT(req)1740 resp = controller.PUT(req)
1575 self.assertEquals(resp.status_int, 201)1741 self.assertEquals(resp.status_int, 201)
@@ -1581,8 +1747,8 @@
1581 'X-Copy-From': 'c/o'})1747 'X-Copy-From': 'c/o'})
1582 self.app.update_request(req)1748 self.app.update_request(req)
1583 proxy_server.http_connect = \1749 proxy_server.http_connect = \
1584 fake_http_connect(200, 200, 200, 200, 200)1750 fake_http_connect(200, 200, 200, 200, 200, 200, 200)
1585 # acct cont acct cont objc1751 # acct cont acct cont objc objc objc
1586 self.app.memcache.store = {}1752 self.app.memcache.store = {}
1587 resp = controller.PUT(req)1753 resp = controller.PUT(req)
1588 self.assertEquals(resp.status_int, 400)1754 self.assertEquals(resp.status_int, 400)
@@ -1593,8 +1759,10 @@
1593 'X-Copy-From': 'c/o/o2'})1759 'X-Copy-From': 'c/o/o2'})
1594 req.account = 'a'1760 req.account = 'a'
1595 proxy_server.http_connect = \1761 proxy_server.http_connect = \
1596 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)1762 fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201,
1597 # acct cont acct cont objc obj obj obj1763 201)
1764 # acct cont acct cont objc objc objc obj obj
1765 # obj
1598 self.app.memcache.store = {}1766 self.app.memcache.store = {}
1599 resp = controller.PUT(req)1767 resp = controller.PUT(req)
1600 self.assertEquals(resp.status_int, 201)1768 self.assertEquals(resp.status_int, 201)
@@ -1606,8 +1774,10 @@
1606 'X-Copy-From': 'c/o%20o2'})1774 'X-Copy-From': 'c/o%20o2'})
1607 req.account = 'a'1775 req.account = 'a'
1608 proxy_server.http_connect = \1776 proxy_server.http_connect = \
1609 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)1777 fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201,
1610 # acct cont acct cont objc obj obj obj1778 201)
1779 # acct cont acct cont objc objc objc obj obj
1780 # obj
1611 self.app.memcache.store = {}1781 self.app.memcache.store = {}
1612 resp = controller.PUT(req)1782 resp = controller.PUT(req)
1613 self.assertEquals(resp.status_int, 201)1783 self.assertEquals(resp.status_int, 201)
@@ -1619,8 +1789,10 @@
1619 'X-Copy-From': '/c/o'})1789 'X-Copy-From': '/c/o'})
1620 self.app.update_request(req)1790 self.app.update_request(req)
1621 proxy_server.http_connect = \1791 proxy_server.http_connect = \
1622 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)1792 fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201,
1623 # acct cont acct cont objc obj obj obj1793 201)
1794 # acct cont acct cont objc objc objc obj obj
1795 # obj
1624 self.app.memcache.store = {}1796 self.app.memcache.store = {}
1625 resp = controller.PUT(req)1797 resp = controller.PUT(req)
1626 self.assertEquals(resp.status_int, 201)1798 self.assertEquals(resp.status_int, 201)
@@ -1631,8 +1803,10 @@
1631 'X-Copy-From': '/c/o/o2'})1803 'X-Copy-From': '/c/o/o2'})
1632 req.account = 'a'1804 req.account = 'a'
1633 proxy_server.http_connect = \1805 proxy_server.http_connect = \
1634 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)1806 fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201,
1635 # acct cont acct cont objc obj obj obj1807 201)
1808 # acct cont acct cont objc objc objc obj obj
1809 # obj
1636 self.app.memcache.store = {}1810 self.app.memcache.store = {}
1637 resp = controller.PUT(req)1811 resp = controller.PUT(req)
1638 self.assertEquals(resp.status_int, 201)1812 self.assertEquals(resp.status_int, 201)
@@ -1692,8 +1866,8 @@
1692 'X-Object-Meta-Ours': 'okay'})1866 'X-Object-Meta-Ours': 'okay'})
1693 self.app.update_request(req)1867 self.app.update_request(req)
1694 proxy_server.http_connect = \1868 proxy_server.http_connect = \
1695 fake_http_connect(200, 200, 200, 201, 201, 201)1869 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)
1696 # acct cont objc obj obj obj1870 # acct cont objc objc objc obj obj obj
1697 self.app.memcache.store = {}1871 self.app.memcache.store = {}
1698 resp = controller.PUT(req)1872 resp = controller.PUT(req)
1699 self.assertEquals(resp.status_int, 201)1873 self.assertEquals(resp.status_int, 201)
@@ -1717,8 +1891,10 @@
1717 headers={'Destination': 'c/o'})1891 headers={'Destination': 'c/o'})
1718 req.account = 'a'1892 req.account = 'a'
1719 proxy_server.http_connect = \1893 proxy_server.http_connect = \
1720 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)1894 fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201,
1721 # acct cont acct cont objc obj obj obj1895 201)
1896 # acct cont acct cont objc objc objc obj obj
1897 # obj
1722 self.app.memcache.store = {}1898 self.app.memcache.store = {}
1723 resp = controller.COPY(req)1899 resp = controller.COPY(req)
1724 self.assertEquals(resp.status_int, 201)1900 self.assertEquals(resp.status_int, 201)
@@ -1730,8 +1906,10 @@
1730 req.account = 'a'1906 req.account = 'a'
1731 controller.object_name = 'o/o2'1907 controller.object_name = 'o/o2'
1732 proxy_server.http_connect = \1908 proxy_server.http_connect = \
1733 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)1909 fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201,
1734 # acct cont acct cont objc obj obj obj1910 201)
1911 # acct cont acct cont objc objc objc obj obj
1912 # obj
1735 self.app.memcache.store = {}1913 self.app.memcache.store = {}
1736 resp = controller.COPY(req)1914 resp = controller.COPY(req)
1737 self.assertEquals(resp.status_int, 201)1915 self.assertEquals(resp.status_int, 201)
@@ -1742,8 +1920,10 @@
1742 req.account = 'a'1920 req.account = 'a'
1743 controller.object_name = 'o'1921 controller.object_name = 'o'
1744 proxy_server.http_connect = \1922 proxy_server.http_connect = \
1745 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)1923 fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201,
1746 # acct cont acct cont objc obj obj obj1924 201)
1925 # acct cont acct cont objc objc objc obj obj
1926 # obj
1747 self.app.memcache.store = {}1927 self.app.memcache.store = {}
1748 resp = controller.COPY(req)1928 resp = controller.COPY(req)
1749 self.assertEquals(resp.status_int, 201)1929 self.assertEquals(resp.status_int, 201)
@@ -1755,8 +1935,10 @@
1755 req.account = 'a'1935 req.account = 'a'
1756 controller.object_name = 'o/o2'1936 controller.object_name = 'o/o2'
1757 proxy_server.http_connect = \1937 proxy_server.http_connect = \
1758 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)1938 fake_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201,
1759 # acct cont acct cont objc obj obj obj1939 201)
1940 # acct cont acct cont objc objc objc obj obj
1941 # obj
1760 self.app.memcache.store = {}1942 self.app.memcache.store = {}
1761 resp = controller.COPY(req)1943 resp = controller.COPY(req)
1762 self.assertEquals(resp.status_int, 201)1944 self.assertEquals(resp.status_int, 201)
@@ -1812,8 +1994,8 @@
1812 req.account = 'a'1994 req.account = 'a'
1813 controller.object_name = 'o'1995 controller.object_name = 'o'
1814 proxy_server.http_connect = \1996 proxy_server.http_connect = \
1815 fake_http_connect(200, 200, 200, 201, 201, 201)1997 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)
1816 # acct cont objc obj obj obj1998 # acct cont objc objc objc obj obj obj
1817 self.app.memcache.store = {}1999 self.app.memcache.store = {}
1818 resp = controller.COPY(req)2000 resp = controller.COPY(req)
1819 self.assertEquals(resp.status_int, 201)2001 self.assertEquals(resp.status_int, 201)
@@ -1821,6 +2003,23 @@
1821 'testing')2003 'testing')
1822 self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')2004 self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
18232005
2006 def test_COPY_newest(self):
2007 with save_globals():
2008 controller = proxy_server.ObjectController(self.app, 'a', 'c', 'o')
2009 req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'COPY'},
2010 headers={'Destination': '/c/o'})
2011 req.account = 'a'
2012 controller.object_name = 'o'
2013 proxy_server.http_connect = \
2014 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201,
2015 timestamps=('1', '1', '1', '3', '2', '4', '4', '4'))
2016 # acct cont objc objc objc obj obj obj
2017 self.app.memcache.store = {}
2018 resp = controller.COPY(req)
2019 self.assertEquals(resp.status_int, 201)
2020 self.assertEquals(resp.headers['x-copied-from-last-modified'],
2021 '3')
2022
1824 def test_chunked_put(self):2023 def test_chunked_put(self):
18252024
1826 class ChunkedFile():2025 class ChunkedFile():
@@ -2596,6 +2795,7 @@
2596 called[0] = True2795 called[0] = True
2597 return HTTPUnauthorized(request=req)2796 return HTTPUnauthorized(request=req)
2598 with save_globals():2797 with save_globals():
2798 self.app.object_post_as_copy = False
2599 proxy_server.http_connect = \2799 proxy_server.http_connect = \
2600 fake_http_connect(200, 200, 201, 201, 201)2800 fake_http_connect(200, 200, 201, 201, 201)
2601 controller = proxy_server.ObjectController(self.app, 'account',2801 controller = proxy_server.ObjectController(self.app, 'account',
@@ -2607,6 +2807,24 @@
2607 res = controller.POST(req)2807 res = controller.POST(req)
2608 self.assert_(called[0])2808 self.assert_(called[0])
26092809
2810 def test_POST_as_copy_calls_authorize(self):
2811 called = [False]
2812
2813 def authorize(req):
2814 called[0] = True
2815 return HTTPUnauthorized(request=req)
2816 with save_globals():
2817 proxy_server.http_connect = \
2818 fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201)
2819 controller = proxy_server.ObjectController(self.app, 'account',
2820 'container', 'object')
2821 req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'POST'},
2822 headers={'Content-Length': '5'}, body='12345')
2823 req.environ['swift.authorize'] = authorize
2824 self.app.update_request(req)
2825 res = controller.POST(req)
2826 self.assert_(called[0])
2827
2610 def test_PUT_calls_authorize(self):2828 def test_PUT_calls_authorize(self):
2611 called = [False]2829 called = [False]
26122830
@@ -2814,6 +3032,7 @@
28143032
2815 def test_error_limiting(self):3033 def test_error_limiting(self):
2816 with save_globals():3034 with save_globals():
3035 proxy_server.shuffle = lambda l: None
2817 controller = proxy_server.ContainerController(self.app, 'account',3036 controller = proxy_server.ContainerController(self.app, 'account',
2818 'container')3037 'container')
2819 self.assert_status_map(controller.HEAD, (200, 503, 200, 200), 200,3038 self.assert_status_map(controller.HEAD, (200, 503, 200, 200), 200,