diff --git a/doc/source/deployment_guide.rst b/doc/source/deployment_guide.rst index 52a4f80f6e..04b99fa1f6 100644 --- a/doc/source/deployment_guide.rst +++ b/doc/source/deployment_guide.rst @@ -547,6 +547,10 @@ error_suppression_limit 10 Error count to consider a node error limited allow_account_management false Whether account PUTs and DELETEs are even callable +account_autocreate false If set to 'true' authorized + accounts that do not yet exist + within the Swift cluster will + be automatically created. ============================ =============== ============================= [tempauth] diff --git a/etc/proxy-server.conf-sample b/etc/proxy-server.conf-sample index fef0e81fa0..496eb4aea1 100644 --- a/etc/proxy-server.conf-sample +++ b/etc/proxy-server.conf-sample @@ -40,6 +40,9 @@ 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 +# If set to 'true' authorized accounts that do not yet exist within the Swift +# cluster will be automatically created. +# account_autocreate = false [filter:tempauth] use = egg:swift#tempauth diff --git a/swift/common/bench.py b/swift/common/bench.py index 28d8c7e8d9..51e39f793d 100644 --- a/swift/common/bench.py +++ b/swift/common/bench.py @@ -43,7 +43,7 @@ class Bench(object): self.user = conf.user self.key = conf.key self.auth_url = conf.auth - self.use_proxy = conf.use_proxy in TRUE_VALUES + self.use_proxy = conf.use_proxy.lower() in TRUE_VALUES if self.use_proxy: url, token = client.get_auth(self.auth_url, self.user, self.key) self.token = token @@ -125,7 +125,7 @@ class BenchController(object): self.logger = logger self.conf = conf self.names = [] - self.delete = conf.delete in TRUE_VALUES + self.delete = conf.delete.lower() in TRUE_VALUES self.gets = int(conf.num_gets) def run(self): diff --git a/swift/common/daemon.py b/swift/common/daemon.py index 96914d9510..abcc8dea1e 100644 --- a/swift/common/daemon.py +++ b/swift/common/daemon.py @@ -75,7 +75,8 @@ def run_daemon(klass, conf_file, section_name='', once=False, **kwargs): log_name=kwargs.get('log_name')) # once on command line (i.e. daemonize=false) will over-ride config - once = once or conf.get('daemonize', 'true') not in utils.TRUE_VALUES + once = once or \ + conf.get('daemonize', 'true').lower() not in utils.TRUE_VALUES # pre-configure logger if 'logger' in kwargs: diff --git a/swift/common/middleware/staticweb.py b/swift/common/middleware/staticweb.py index 8e58ad5068..81225a90ea 100644 --- a/swift/common/middleware/staticweb.py +++ b/swift/common/middleware/staticweb.py @@ -270,7 +270,7 @@ class StaticWeb(object): :param start_response: The original WSGI start_response hook. :param prefix: Any prefix desired for the container listing. """ - if self._listings not in TRUE_VALUES: + if self._listings.lower() not in TRUE_VALUES: resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) tmp_env = self._get_escalated_env(env) diff --git a/swift/common/utils.py b/swift/common/utils.py index 4ee57db8f7..ac18331703 100644 --- a/swift/common/utils.py +++ b/swift/common/utils.py @@ -72,7 +72,7 @@ if hash_conf.read('/etc/swift/swift.conf'): pass # Used when reading config values -TRUE_VALUES = set(('true', '1', 'yes', 'True', 'Yes', 'on', 'On', 't', 'y')) +TRUE_VALUES = set(('true', '1', 'yes', 'on', 't', 'y')) def validate_configuration(): diff --git a/swift/proxy/server.py b/swift/proxy/server.py index 3300e7a384..8a451200cf 100644 --- a/swift/proxy/server.py +++ b/swift/proxy/server.py @@ -41,8 +41,8 @@ from webob.exc import HTTPBadRequest, HTTPMethodNotAllowed, \ from webob import Request, Response from swift.common.ring import Ring -from swift.common.utils import get_logger, normalize_timestamp, split_path, \ - cache_from_env, ContextPool +from swift.common.utils import cache_from_env, ContextPool, get_logger, \ + normalize_timestamp, split_path, TRUE_VALUES from swift.common.bufferedhttp import http_connect from swift.common.constraints import check_metadata, check_object_creation, \ check_utf8, CONTAINER_LISTING_LIMIT, MAX_ACCOUNT_NAME_LENGTH, \ @@ -338,7 +338,7 @@ class Controller(object): node['errors'] = self.app.error_suppression_limit + 1 node['last_error'] = time.time() - def account_info(self, account): + def account_info(self, account, autocreate=False): """ Get account information, and also verify that the account exists. @@ -353,7 +353,7 @@ class Controller(object): result_code = self.app.memcache.get(cache_key) if result_code == 200: return partition, nodes - elif result_code == 404: + elif result_code == 404 and not autocreate: return None, None result_code = 0 attempts_left = self.app.account_ring.replica_count @@ -386,6 +386,17 @@ class Controller(object): except (Exception, TimeoutError): self.exception_occurred(node, _('Account'), _('Trying to get account info for %s') % path) + if result_code == 404 and autocreate: + if len(account) > MAX_ACCOUNT_NAME_LENGTH: + return None, None + headers = {'X-Timestamp': normalize_timestamp(time.time()), + 'X-Trans-Id': self.trans_id} + resp = self.make_requests(Request.blank('/v1' + path), + self.app.account_ring, partition, 'PUT', + path, [headers] * len(nodes)) + if resp.status_int // 100 != 2: + raise Exception('Could not autocreate account %r' % path) + result_code = 200 if self.app.memcache and result_code in (200, 404): if result_code == 200: cache_timeout = self.app.recheck_account_existence @@ -397,7 +408,7 @@ class Controller(object): return partition, nodes return None, None - def container_info(self, account, container): + def container_info(self, account, container, account_autocreate=False): """ Get container information and thusly verify container existance. This will also make a call to account_info to verify that the @@ -423,7 +434,7 @@ class Controller(object): return partition, nodes, read_acl, write_acl elif status == 404: return None, None, None, None - if not self.account_info(account)[1]: + if not self.account_info(account, autocreate=account_autocreate)[1]: return None, None, None, None result_code = 0 read_acl = None @@ -854,7 +865,8 @@ class ObjectController(Controller): if error_response: return error_response container_partition, containers, _junk, req.acl = \ - self.container_info(self.account_name, self.container_name) + self.container_info(self.account_name, self.container_name, + account_autocreate=self.app.account_autocreate) if 'swift.authorize' in req.environ: aresp = req.environ['swift.authorize'](req) if aresp: @@ -911,7 +923,8 @@ class ObjectController(Controller): def PUT(self, req): """HTTP PUT request handler.""" container_partition, containers, _junk, req.acl = \ - self.container_info(self.account_name, self.container_name) + self.container_info(self.account_name, self.container_name, + account_autocreate=self.app.account_autocreate) if 'swift.authorize' in req.environ: aresp = req.environ['swift.authorize'](req) if aresp: @@ -1219,7 +1232,8 @@ class ContainerController(Controller): resp.body = 'Container name length of %d longer than %d' % \ (len(self.container_name), MAX_CONTAINER_NAME_LENGTH) return resp - account_partition, accounts = self.account_info(self.account_name) + account_partition, accounts = self.account_info(self.account_name, + autocreate=self.app.account_autocreate) if not accounts: return HTTPNotFound(request=req) container_partition, containers = self.app.container_ring.get_nodes( @@ -1249,7 +1263,8 @@ class ContainerController(Controller): self.clean_acls(req) or check_metadata(req, 'container') if error_response: return error_response - account_partition, accounts = self.account_info(self.account_name) + account_partition, accounts = self.account_info(self.account_name, + autocreate=self.app.account_autocreate) if not accounts: return HTTPNotFound(request=req) container_partition, containers = self.app.container_ring.get_nodes( @@ -1391,7 +1406,7 @@ class BaseApplication(object): self.put_queue_depth = int(conf.get('put_queue_depth', 10)) self.object_chunk_size = int(conf.get('object_chunk_size', 65536)) self.client_chunk_size = int(conf.get('client_chunk_size', 65536)) - self.log_headers = conf.get('log_headers') == 'True' + self.log_headers = conf.get('log_headers', 'no').lower() in TRUE_VALUES self.error_suppression_interval = \ int(conf.get('error_suppression_interval', 60)) self.error_suppression_limit = \ @@ -1401,7 +1416,7 @@ class BaseApplication(object): self.recheck_account_existence = \ int(conf.get('recheck_account_existence', 60)) self.allow_account_management = \ - conf.get('allow_account_management', 'false').lower() == 'true' + conf.get('allow_account_management', 'no').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 \ @@ -1413,6 +1428,8 @@ class BaseApplication(object): self.memcache = memcache mimetypes.init(mimetypes.knownfiles + [os.path.join(swift_dir, 'mime.types')]) + self.account_autocreate = \ + conf.get('account_autocreate', 'no').lower() in TRUE_VALUES def get_controller(self, path): """ diff --git a/swift/stats/log_uploader.py b/swift/stats/log_uploader.py index 6051107a86..ea51061d54 100644 --- a/swift/stats/log_uploader.py +++ b/swift/stats/log_uploader.py @@ -69,7 +69,7 @@ class LogUploader(Daemon): self.internal_proxy = InternalProxy(proxy_server_conf) self.new_log_cutoff = int(cutoff or uploader_conf.get('new_log_cutoff', '7200')) - self.unlink_log = uploader_conf.get('unlink_log', 'True').lower() in \ + self.unlink_log = uploader_conf.get('unlink_log', 'true').lower() in \ utils.TRUE_VALUES self.filename_pattern = regex or \ uploader_conf.get('source_filename_pattern', diff --git a/test/unit/common/test_utils.py b/test/unit/common/test_utils.py index 323f60672e..67a6a1fbf2 100644 --- a/test/unit/common/test_utils.py +++ b/test/unit/common/test_utils.py @@ -768,6 +768,10 @@ log_name = yarr''' self.assertEquals(utils.human_readable(1237940039285380274899124224), '1024Yi') + def test_TRUE_VALUES(self): + for v in utils.TRUE_VALUES: + self.assertEquals(v, v.lower()) + if __name__ == '__main__': unittest.main() diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index fe2d1ca01e..eb09ad34dc 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -393,6 +393,48 @@ class TestController(unittest.TestCase): test(404, 507, 503) test(503, 503, 503) + def test_account_info_account_autocreate(self): + with save_globals(): + self.memcache.store = {} + proxy_server.http_connect = \ + fake_http_connect(404, 404, 404, 201, 201, 201) + partition, nodes = \ + self.controller.account_info(self.account, autocreate=False) + self.check_account_info_return(partition, nodes, is_none=True) + + self.memcache.store = {} + proxy_server.http_connect = \ + fake_http_connect(404, 404, 404, 201, 201, 201) + partition, nodes = \ + self.controller.account_info(self.account) + self.check_account_info_return(partition, nodes, is_none=True) + + self.memcache.store = {} + proxy_server.http_connect = \ + fake_http_connect(404, 404, 404, 201, 201, 201) + partition, nodes = \ + self.controller.account_info(self.account, autocreate=True) + self.check_account_info_return(partition, nodes) + + self.memcache.store = {} + proxy_server.http_connect = \ + fake_http_connect(404, 404, 404, 503, 201, 201) + partition, nodes = \ + self.controller.account_info(self.account, autocreate=True) + self.check_account_info_return(partition, nodes) + + self.memcache.store = {} + proxy_server.http_connect = \ + fake_http_connect(404, 404, 404, 503, 201, 503) + exc = None + try: + partition, nodes = \ + self.controller.account_info(self.account, autocreate=True) + except Exception, err: + exc = err + self.assertEquals(str(exc), + "Could not autocreate account '/some_account'") + def check_container_info_return(self, ret, is_none=False): if is_none: partition, nodes, read_acl, write_acl = None, None, None, None @@ -406,7 +448,7 @@ class TestController(unittest.TestCase): self.assertEqual(write_acl, ret[3]) def test_container_info_invalid_account(self): - def account_info(self, account): + def account_info(self, account, autocreate=False): return None, None with save_globals(): @@ -417,7 +459,7 @@ class TestController(unittest.TestCase): # tests if 200 is cached and used def test_container_info_200(self): - def account_info(self, account): + def account_info(self, account, autocreate=False): return True, True with save_globals(): @@ -443,7 +485,7 @@ class TestController(unittest.TestCase): # tests if 404 is cached and used def test_container_info_404(self): - def account_info(self, account): + def account_info(self, account, autocreate=False): return True, True with save_globals():