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

Proposed by gholt
Status: Merged
Approved by: David Goetz
Approved revision: 311
Merged at revision: 308
Proposed branch: lp:~gholt/swift/autocreate
Merge into: lp:~hudson-openstack/swift/trunk
Diff against target: 334 lines (+92/-21)
10 files modified
doc/source/deployment_guide.rst (+4/-0)
etc/proxy-server.conf-sample (+3/-0)
swift/common/bench.py (+2/-2)
swift/common/daemon.py (+2/-1)
swift/common/middleware/staticweb.py (+1/-1)
swift/common/utils.py (+1/-1)
swift/proxy/server.py (+29/-12)
swift/stats/log_uploader.py (+1/-1)
test/unit/common/test_utils.py (+4/-0)
test/unit/proxy/test_server.py (+45/-3)
To merge this branch: bzr merge lp:~gholt/swift/autocreate
Reviewer Review Type Date Requested Status
David Goetz (community) Approve
John Dickinson Approve
Review via email: mp+64206@code.launchpad.net

Description of the change

Added account_autocreate mode where authorized requests to accounts that don't yet exist within Swift will cause those accounts to be automatically created. Also did a small refactor surrounding swift.common.utils.TRUE_VALUES.

To post a comment you must log in.
Revision history for this message
John Dickinson (notmyname) wrote :

account_info doesn't retry or handle any results other than 200 to the autocreate PUT. Should it do more?

review: Needs Information
Revision history for this message
gholt (gholt) wrote :

Well, it's 2xx but yeah. Is there more you'd like it to do?

Revision history for this message
gholt (gholt) wrote :

Oh, I guess you mentioned retry. Not sure I'd want to implement retries on that since we don't really do that elsewhere in the proxy. But even if we don't retry, it seems like I should change non-2xx to cause a 5xx on the original request rather than a 404 or something.

Revision history for this message
gholt (gholt) wrote :

Okay, I made a failed account autocreate cause a 5xx now, which indicates the client should retry their request if desired.

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

lgtm

review: Approve
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-05-26 02:24:12 +0000
+++ doc/source/deployment_guide.rst 2011-06-10 16:57:25 +0000
@@ -547,6 +547,10 @@
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
550account_autocreate false If set to 'true' authorized
551 accounts that do not yet exist
552 within the Swift cluster will
553 be automatically created.
550============================ =============== =============================554============================ =============== =============================
551555
552[tempauth]556[tempauth]
553557
=== modified file 'etc/proxy-server.conf-sample'
--- etc/proxy-server.conf-sample 2011-05-26 02:24:12 +0000
+++ etc/proxy-server.conf-sample 2011-06-10 16:57:25 +0000
@@ -40,6 +40,9 @@
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# If set to 'true' authorized accounts that do not yet exist within the Swift
44# cluster will be automatically created.
45# account_autocreate = false
4346
44[filter:tempauth]47[filter:tempauth]
45use = egg:swift#tempauth48use = egg:swift#tempauth
4649
=== modified file 'swift/common/bench.py'
--- swift/common/bench.py 2011-05-25 11:56:53 +0000
+++ swift/common/bench.py 2011-06-10 16:57:25 +0000
@@ -43,7 +43,7 @@
43 self.user = conf.user43 self.user = conf.user
44 self.key = conf.key44 self.key = conf.key
45 self.auth_url = conf.auth45 self.auth_url = conf.auth
46 self.use_proxy = conf.use_proxy in TRUE_VALUES46 self.use_proxy = conf.use_proxy.lower() in TRUE_VALUES
47 if self.use_proxy:47 if self.use_proxy:
48 url, token = client.get_auth(self.auth_url, self.user, self.key)48 url, token = client.get_auth(self.auth_url, self.user, self.key)
49 self.token = token49 self.token = token
@@ -125,7 +125,7 @@
125 self.logger = logger125 self.logger = logger
126 self.conf = conf126 self.conf = conf
127 self.names = []127 self.names = []
128 self.delete = conf.delete in TRUE_VALUES128 self.delete = conf.delete.lower() in TRUE_VALUES
129 self.gets = int(conf.num_gets)129 self.gets = int(conf.num_gets)
130130
131 def run(self):131 def run(self):
132132
=== modified file 'swift/common/daemon.py'
--- swift/common/daemon.py 2011-02-21 18:50:56 +0000
+++ swift/common/daemon.py 2011-06-10 16:57:25 +0000
@@ -75,7 +75,8 @@
75 log_name=kwargs.get('log_name'))75 log_name=kwargs.get('log_name'))
7676
77 # once on command line (i.e. daemonize=false) will over-ride config77 # once on command line (i.e. daemonize=false) will over-ride config
78 once = once or conf.get('daemonize', 'true') not in utils.TRUE_VALUES78 once = once or \
79 conf.get('daemonize', 'true').lower() not in utils.TRUE_VALUES
7980
80 # pre-configure logger81 # pre-configure logger
81 if 'logger' in kwargs:82 if 'logger' in kwargs:
8283
=== modified file 'swift/common/middleware/staticweb.py'
--- swift/common/middleware/staticweb.py 2011-05-26 02:24:12 +0000
+++ swift/common/middleware/staticweb.py 2011-06-10 16:57:25 +0000
@@ -270,7 +270,7 @@
270 :param start_response: The original WSGI start_response hook.270 :param start_response: The original WSGI start_response hook.
271 :param prefix: Any prefix desired for the container listing.271 :param prefix: Any prefix desired for the container listing.
272 """272 """
273 if self._listings not in TRUE_VALUES:273 if self._listings.lower() not in TRUE_VALUES:
274 resp = HTTPNotFound()(env, self._start_response)274 resp = HTTPNotFound()(env, self._start_response)
275 return self._error_response(resp, env, start_response)275 return self._error_response(resp, env, start_response)
276 tmp_env = self._get_escalated_env(env)276 tmp_env = self._get_escalated_env(env)
277277
=== modified file 'swift/common/utils.py'
--- swift/common/utils.py 2011-04-20 19:54:28 +0000
+++ swift/common/utils.py 2011-06-10 16:57:25 +0000
@@ -72,7 +72,7 @@
72 pass72 pass
7373
74# Used when reading config values74# Used when reading config values
75TRUE_VALUES = set(('true', '1', 'yes', 'True', 'Yes', 'on', 'On', 't', 'y'))75TRUE_VALUES = set(('true', '1', 'yes', 'on', 't', 'y'))
7676
7777
78def validate_configuration():78def validate_configuration():
7979
=== modified file 'swift/proxy/server.py'
--- swift/proxy/server.py 2011-05-12 15:57:35 +0000
+++ swift/proxy/server.py 2011-06-10 16:57:25 +0000
@@ -41,8 +41,8 @@
41from webob import Request, Response41from webob import Request, Response
4242
43from swift.common.ring import Ring43from swift.common.ring import Ring
44from swift.common.utils import get_logger, normalize_timestamp, split_path, \44from swift.common.utils import cache_from_env, ContextPool, get_logger, \
45 cache_from_env, ContextPool45 normalize_timestamp, split_path, TRUE_VALUES
46from swift.common.bufferedhttp import http_connect46from swift.common.bufferedhttp import http_connect
47from swift.common.constraints import check_metadata, check_object_creation, \47from swift.common.constraints import check_metadata, check_object_creation, \
48 check_utf8, CONTAINER_LISTING_LIMIT, MAX_ACCOUNT_NAME_LENGTH, \48 check_utf8, CONTAINER_LISTING_LIMIT, MAX_ACCOUNT_NAME_LENGTH, \
@@ -338,7 +338,7 @@
338 node['errors'] = self.app.error_suppression_limit + 1338 node['errors'] = self.app.error_suppression_limit + 1
339 node['last_error'] = time.time()339 node['last_error'] = time.time()
340340
341 def account_info(self, account):341 def account_info(self, account, autocreate=False):
342 """342 """
343 Get account information, and also verify that the account exists.343 Get account information, and also verify that the account exists.
344344
@@ -353,7 +353,7 @@
353 result_code = self.app.memcache.get(cache_key)353 result_code = self.app.memcache.get(cache_key)
354 if result_code == 200:354 if result_code == 200:
355 return partition, nodes355 return partition, nodes
356 elif result_code == 404:356 elif result_code == 404 and not autocreate:
357 return None, None357 return None, None
358 result_code = 0358 result_code = 0
359 attempts_left = self.app.account_ring.replica_count359 attempts_left = self.app.account_ring.replica_count
@@ -386,6 +386,17 @@
386 except (Exception, TimeoutError):386 except (Exception, TimeoutError):
387 self.exception_occurred(node, _('Account'),387 self.exception_occurred(node, _('Account'),
388 _('Trying to get account info for %s') % path)388 _('Trying to get account info for %s') % path)
389 if result_code == 404 and autocreate:
390 if len(account) > MAX_ACCOUNT_NAME_LENGTH:
391 return None, None
392 headers = {'X-Timestamp': normalize_timestamp(time.time()),
393 'X-Trans-Id': self.trans_id}
394 resp = self.make_requests(Request.blank('/v1' + path),
395 self.app.account_ring, partition, 'PUT',
396 path, [headers] * len(nodes))
397 if resp.status_int // 100 != 2:
398 raise Exception('Could not autocreate account %r' % path)
399 result_code = 200
389 if self.app.memcache and result_code in (200, 404):400 if self.app.memcache and result_code in (200, 404):
390 if result_code == 200:401 if result_code == 200:
391 cache_timeout = self.app.recheck_account_existence402 cache_timeout = self.app.recheck_account_existence
@@ -397,7 +408,7 @@
397 return partition, nodes408 return partition, nodes
398 return None, None409 return None, None
399410
400 def container_info(self, account, container):411 def container_info(self, account, container, account_autocreate=False):
401 """412 """
402 Get container information and thusly verify container existance.413 Get container information and thusly verify container existance.
403 This will also make a call to account_info to verify that the414 This will also make a call to account_info to verify that the
@@ -423,7 +434,7 @@
423 return partition, nodes, read_acl, write_acl434 return partition, nodes, read_acl, write_acl
424 elif status == 404:435 elif status == 404:
425 return None, None, None, None436 return None, None, None, None
426 if not self.account_info(account)[1]:437 if not self.account_info(account, autocreate=account_autocreate)[1]:
427 return None, None, None, None438 return None, None, None, None
428 result_code = 0439 result_code = 0
429 read_acl = None440 read_acl = None
@@ -854,7 +865,8 @@
854 if error_response:865 if error_response:
855 return error_response866 return error_response
856 container_partition, containers, _junk, req.acl = \867 container_partition, containers, _junk, req.acl = \
857 self.container_info(self.account_name, self.container_name)868 self.container_info(self.account_name, self.container_name,
869 account_autocreate=self.app.account_autocreate)
858 if 'swift.authorize' in req.environ:870 if 'swift.authorize' in req.environ:
859 aresp = req.environ['swift.authorize'](req)871 aresp = req.environ['swift.authorize'](req)
860 if aresp:872 if aresp:
@@ -911,7 +923,8 @@
911 def PUT(self, req):923 def PUT(self, req):
912 """HTTP PUT request handler."""924 """HTTP PUT request handler."""
913 container_partition, containers, _junk, req.acl = \925 container_partition, containers, _junk, req.acl = \
914 self.container_info(self.account_name, self.container_name)926 self.container_info(self.account_name, self.container_name,
927 account_autocreate=self.app.account_autocreate)
915 if 'swift.authorize' in req.environ:928 if 'swift.authorize' in req.environ:
916 aresp = req.environ['swift.authorize'](req)929 aresp = req.environ['swift.authorize'](req)
917 if aresp:930 if aresp:
@@ -1219,7 +1232,8 @@
1219 resp.body = 'Container name length of %d longer than %d' % \1232 resp.body = 'Container name length of %d longer than %d' % \
1220 (len(self.container_name), MAX_CONTAINER_NAME_LENGTH)1233 (len(self.container_name), MAX_CONTAINER_NAME_LENGTH)
1221 return resp1234 return resp
1222 account_partition, accounts = self.account_info(self.account_name)1235 account_partition, accounts = self.account_info(self.account_name,
1236 autocreate=self.app.account_autocreate)
1223 if not accounts:1237 if not accounts:
1224 return HTTPNotFound(request=req)1238 return HTTPNotFound(request=req)
1225 container_partition, containers = self.app.container_ring.get_nodes(1239 container_partition, containers = self.app.container_ring.get_nodes(
@@ -1249,7 +1263,8 @@
1249 self.clean_acls(req) or check_metadata(req, 'container')1263 self.clean_acls(req) or check_metadata(req, 'container')
1250 if error_response:1264 if error_response:
1251 return error_response1265 return error_response
1252 account_partition, accounts = self.account_info(self.account_name)1266 account_partition, accounts = self.account_info(self.account_name,
1267 autocreate=self.app.account_autocreate)
1253 if not accounts:1268 if not accounts:
1254 return HTTPNotFound(request=req)1269 return HTTPNotFound(request=req)
1255 container_partition, containers = self.app.container_ring.get_nodes(1270 container_partition, containers = self.app.container_ring.get_nodes(
@@ -1391,7 +1406,7 @@
1391 self.put_queue_depth = int(conf.get('put_queue_depth', 10))1406 self.put_queue_depth = int(conf.get('put_queue_depth', 10))
1392 self.object_chunk_size = int(conf.get('object_chunk_size', 65536))1407 self.object_chunk_size = int(conf.get('object_chunk_size', 65536))
1393 self.client_chunk_size = int(conf.get('client_chunk_size', 65536))1408 self.client_chunk_size = int(conf.get('client_chunk_size', 65536))
1394 self.log_headers = conf.get('log_headers') == 'True'1409 self.log_headers = conf.get('log_headers', 'no').lower() in TRUE_VALUES
1395 self.error_suppression_interval = \1410 self.error_suppression_interval = \
1396 int(conf.get('error_suppression_interval', 60))1411 int(conf.get('error_suppression_interval', 60))
1397 self.error_suppression_limit = \1412 self.error_suppression_limit = \
@@ -1401,7 +1416,7 @@
1401 self.recheck_account_existence = \1416 self.recheck_account_existence = \
1402 int(conf.get('recheck_account_existence', 60))1417 int(conf.get('recheck_account_existence', 60))
1403 self.allow_account_management = \1418 self.allow_account_management = \
1404 conf.get('allow_account_management', 'false').lower() == 'true'1419 conf.get('allow_account_management', 'no').lower() in TRUE_VALUES
1405 self.resellers_conf = ConfigParser()1420 self.resellers_conf = ConfigParser()
1406 self.resellers_conf.read(os.path.join(swift_dir, 'resellers.conf'))1421 self.resellers_conf.read(os.path.join(swift_dir, 'resellers.conf'))
1407 self.object_ring = object_ring or \1422 self.object_ring = object_ring or \
@@ -1413,6 +1428,8 @@
1413 self.memcache = memcache1428 self.memcache = memcache
1414 mimetypes.init(mimetypes.knownfiles +1429 mimetypes.init(mimetypes.knownfiles +
1415 [os.path.join(swift_dir, 'mime.types')])1430 [os.path.join(swift_dir, 'mime.types')])
1431 self.account_autocreate = \
1432 conf.get('account_autocreate', 'no').lower() in TRUE_VALUES
14161433
1417 def get_controller(self, path):1434 def get_controller(self, path):
1418 """1435 """
14191436
=== modified file 'swift/stats/log_uploader.py'
--- swift/stats/log_uploader.py 2011-05-24 20:51:07 +0000
+++ swift/stats/log_uploader.py 2011-06-10 16:57:25 +0000
@@ -69,7 +69,7 @@
69 self.internal_proxy = InternalProxy(proxy_server_conf)69 self.internal_proxy = InternalProxy(proxy_server_conf)
70 self.new_log_cutoff = int(cutoff or70 self.new_log_cutoff = int(cutoff or
71 uploader_conf.get('new_log_cutoff', '7200'))71 uploader_conf.get('new_log_cutoff', '7200'))
72 self.unlink_log = uploader_conf.get('unlink_log', 'True').lower() in \72 self.unlink_log = uploader_conf.get('unlink_log', 'true').lower() in \
73 utils.TRUE_VALUES73 utils.TRUE_VALUES
74 self.filename_pattern = regex or \74 self.filename_pattern = regex or \
75 uploader_conf.get('source_filename_pattern',75 uploader_conf.get('source_filename_pattern',
7676
=== modified file 'test/unit/common/test_utils.py'
--- test/unit/common/test_utils.py 2011-03-30 20:04:15 +0000
+++ test/unit/common/test_utils.py 2011-06-10 16:57:25 +0000
@@ -768,6 +768,10 @@
768 self.assertEquals(utils.human_readable(1237940039285380274899124224),768 self.assertEquals(utils.human_readable(1237940039285380274899124224),
769 '1024Yi')769 '1024Yi')
770770
771 def test_TRUE_VALUES(self):
772 for v in utils.TRUE_VALUES:
773 self.assertEquals(v, v.lower())
774
771775
772if __name__ == '__main__':776if __name__ == '__main__':
773 unittest.main()777 unittest.main()
774778
=== modified file 'test/unit/proxy/test_server.py'
--- test/unit/proxy/test_server.py 2011-05-09 20:21:34 +0000
+++ test/unit/proxy/test_server.py 2011-06-10 16:57:25 +0000
@@ -393,6 +393,48 @@
393 test(404, 507, 503)393 test(404, 507, 503)
394 test(503, 503, 503)394 test(503, 503, 503)
395395
396 def test_account_info_account_autocreate(self):
397 with save_globals():
398 self.memcache.store = {}
399 proxy_server.http_connect = \
400 fake_http_connect(404, 404, 404, 201, 201, 201)
401 partition, nodes = \
402 self.controller.account_info(self.account, autocreate=False)
403 self.check_account_info_return(partition, nodes, is_none=True)
404
405 self.memcache.store = {}
406 proxy_server.http_connect = \
407 fake_http_connect(404, 404, 404, 201, 201, 201)
408 partition, nodes = \
409 self.controller.account_info(self.account)
410 self.check_account_info_return(partition, nodes, is_none=True)
411
412 self.memcache.store = {}
413 proxy_server.http_connect = \
414 fake_http_connect(404, 404, 404, 201, 201, 201)
415 partition, nodes = \
416 self.controller.account_info(self.account, autocreate=True)
417 self.check_account_info_return(partition, nodes)
418
419 self.memcache.store = {}
420 proxy_server.http_connect = \
421 fake_http_connect(404, 404, 404, 503, 201, 201)
422 partition, nodes = \
423 self.controller.account_info(self.account, autocreate=True)
424 self.check_account_info_return(partition, nodes)
425
426 self.memcache.store = {}
427 proxy_server.http_connect = \
428 fake_http_connect(404, 404, 404, 503, 201, 503)
429 exc = None
430 try:
431 partition, nodes = \
432 self.controller.account_info(self.account, autocreate=True)
433 except Exception, err:
434 exc = err
435 self.assertEquals(str(exc),
436 "Could not autocreate account '/some_account'")
437
396 def check_container_info_return(self, ret, is_none=False):438 def check_container_info_return(self, ret, is_none=False):
397 if is_none:439 if is_none:
398 partition, nodes, read_acl, write_acl = None, None, None, None440 partition, nodes, read_acl, write_acl = None, None, None, None
@@ -406,7 +448,7 @@
406 self.assertEqual(write_acl, ret[3])448 self.assertEqual(write_acl, ret[3])
407449
408 def test_container_info_invalid_account(self):450 def test_container_info_invalid_account(self):
409 def account_info(self, account):451 def account_info(self, account, autocreate=False):
410 return None, None452 return None, None
411453
412 with save_globals():454 with save_globals():
@@ -417,7 +459,7 @@
417459
418 # tests if 200 is cached and used460 # tests if 200 is cached and used
419 def test_container_info_200(self):461 def test_container_info_200(self):
420 def account_info(self, account):462 def account_info(self, account, autocreate=False):
421 return True, True463 return True, True
422464
423 with save_globals():465 with save_globals():
@@ -443,7 +485,7 @@
443485
444 # tests if 404 is cached and used486 # tests if 404 is cached and used
445 def test_container_info_404(self):487 def test_container_info_404(self):
446 def account_info(self, account):488 def account_info(self, account, autocreate=False):
447 return True, True489 return True, True
448490
449 with save_globals():491 with save_globals():