From af031138bee0a367f7bdb5339e927160030642d7 Mon Sep 17 00:00:00 2001 From: Michael Barton Date: Thu, 1 Nov 2012 16:14:58 -0700 Subject: [PATCH] re-use headers_to_container_info on container GET Currently, a container's info can be cached without cors data intact after a container GET. I made headers_to_container_info a function instead of a method and I crammed all container metadata into container_info. This is so e.g. staticweb can eventually re-use the same container info cache. Fix pep8 in swift/proxy/controllers/container.py Change-Id: I4bbb042dde79afac48395efc38bd80f0ff240e1f --- swift/proxy/controllers/base.py | 49 +++++++++++++---------- swift/proxy/controllers/container.py | 38 ++++++++---------- test/unit/proxy/controllers/__init__.py | 0 test/unit/proxy/controllers/test_base.py | 50 ++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 42 deletions(-) create mode 100644 test/unit/proxy/controllers/__init__.py create mode 100644 test/unit/proxy/controllers/test_base.py diff --git a/swift/proxy/controllers/base.py b/swift/proxy/controllers/base.py index 73f7353ea7..793c90e3cb 100644 --- a/swift/proxy/controllers/base.py +++ b/swift/proxy/controllers/base.py @@ -94,6 +94,33 @@ def get_container_memcache_key(account, container): return 'container/%s/%s' % (account, container) +def headers_to_container_info(headers, status_int=HTTP_OK): + """ + Construct a cacheable dict of container info based on response headers. + """ + headers = dict(headers) + return { + 'status': status_int, + 'read_acl': headers.get('x-container-read'), + 'write_acl': headers.get('x-container-write'), + 'sync_key': headers.get('x-container-sync-key'), + 'count': headers.get('x-container-object-count'), + 'bytes': headers.get('x-container-bytes-used'), + 'versions': headers.get('x-versions-location'), + 'cors': { + 'allow_origin': headers.get( + 'x-container-meta-access-control-allow-origin'), + 'allow_headers': headers.get( + 'x-container-meta-access-control-allow-headers'), + 'max_age': headers.get( + 'x-container-meta-access-control-max-age') + }, + 'meta': dict((key.lower()[17:], value) + for key, value in headers.iteritems() + if key.lower().startswith('x-container-meta-')) + } + + class Controller(object): """Base WSGI controller class for the proxy""" server_type = 'Base' @@ -278,25 +305,6 @@ class Controller(object): return partition, nodes, container_count return None, None, None - def headers_to_container_info(self, headers): - headers = dict(headers) - return { - 'read_acl': headers.get('x-container-read'), - 'write_acl': headers.get('x-container-write'), - 'sync_key': headers.get('x-container-sync-key'), - 'count': headers.get('x-container-object-count'), - 'bytes': headers.get('x-container-bytes-used'), - 'versions': headers.get('x-versions-location'), - 'cors': { - 'allow_origin': headers.get( - 'x-container-meta-access-control-allow-origin'), - 'allow_headers': headers.get( - 'x-container-meta-access-control-allow-headers'), - 'max_age': headers.get( - 'x-container-meta-access-control-max-age') - } - } - def container_info(self, account, container, account_autocreate=False): """ Get container information and thusly verify container existance. @@ -344,8 +352,7 @@ class Controller(object): body = resp.read() if is_success(resp.status): container_info.update( - self.headers_to_container_info(resp.getheaders())) - container_info['status'] = HTTP_OK + headers_to_container_info(resp.getheaders())) break elif resp.status == HTTP_NOT_FOUND: container_info['status'] = HTTP_NOT_FOUND diff --git a/swift/proxy/controllers/container.py b/swift/proxy/controllers/container.py index 77da9950de..915e47627d 100644 --- a/swift/proxy/controllers/container.py +++ b/swift/proxy/controllers/container.py @@ -32,7 +32,7 @@ from swift.common.utils import normalize_timestamp, public from swift.common.constraints import check_metadata, MAX_CONTAINER_NAME_LENGTH from swift.common.http import HTTP_ACCEPTED from swift.proxy.controllers.base import Controller, delay_denial, \ - get_container_memcache_key + get_container_memcache_key, headers_to_container_info from swift.common.swob import HTTPBadRequest, HTTPForbidden, \ HTTPNotFound @@ -68,24 +68,18 @@ class ContainerController(Controller): if not self.account_info(self.account_name)[1]: return HTTPNotFound(request=req) part, nodes = self.app.container_ring.get_nodes( - self.account_name, self.container_name) + self.account_name, self.container_name) shuffle(nodes) - resp = self.GETorHEAD_base(req, _('Container'), part, nodes, - req.path_info, len(nodes)) - + resp = self.GETorHEAD_base( + req, _('Container'), part, nodes, req.path_info, len(nodes)) if self.app.memcache: # set the memcache container size for ratelimiting cache_key = get_container_memcache_key(self.account_name, self.container_name) - self.app.memcache.set(cache_key, - {'status': resp.status_int, - 'read_acl': resp.headers.get('x-container-read'), - 'write_acl': resp.headers.get('x-container-write'), - 'sync_key': resp.headers.get('x-container-sync-key'), - 'count': resp.headers.get('x-container-object-count'), - 'bytes': resp.headers.get('x-container-bytes-used'), - 'versions': resp.headers.get('x-versions-location')}, - timeout=self.app.recheck_container_existence) + self.app.memcache.set( + cache_key, + headers_to_container_info(resp.headers, resp.status_int), + timeout=self.app.recheck_container_existence) if 'swift.authorize' in req.environ: req.acl = resp.headers.get('x-container-read') @@ -151,8 +145,9 @@ class ContainerController(Controller): cache_key = get_container_memcache_key(self.account_name, self.container_name) self.app.memcache.delete(cache_key) - resp = self.make_requests(req, self.app.container_ring, - container_partition, 'PUT', req.path_info, headers) + resp = self.make_requests( + req, self.app.container_ring, + container_partition, 'PUT', req.path_info, headers) return resp @public @@ -176,9 +171,9 @@ class ContainerController(Controller): if self.app.memcache: self.app.memcache.delete(get_container_memcache_key( self.account_name, self.container_name)) - resp = self.make_requests(req, self.app.container_ring, - container_partition, 'POST', req.path_info, - [headers] * len(containers)) + resp = self.make_requests( + req, self.app.container_ring, container_partition, 'POST', + req.path_info, [headers] * len(containers)) return resp @public @@ -202,8 +197,9 @@ class ContainerController(Controller): cache_key = get_container_memcache_key(self.account_name, self.container_name) self.app.memcache.delete(cache_key) - resp = self.make_requests(req, self.app.container_ring, - container_partition, 'DELETE', req.path_info, headers) + resp = self.make_requests( + req, self.app.container_ring, container_partition, 'DELETE', + req.path_info, headers) # Indicates no server had the container if resp.status_int == HTTP_ACCEPTED: return HTTPNotFound(request=req) diff --git a/test/unit/proxy/controllers/__init__.py b/test/unit/proxy/controllers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/unit/proxy/controllers/test_base.py b/test/unit/proxy/controllers/test_base.py new file mode 100644 index 0000000000..13bb11e3c1 --- /dev/null +++ b/test/unit/proxy/controllers/test_base.py @@ -0,0 +1,50 @@ +# Copyright (c) 2010-2012 OpenStack, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +from swift.proxy.controllers.base import headers_to_container_info + + +class TestFuncs(unittest.TestCase): + def test_headers_to_container_info_missing(self): + resp = headers_to_container_info({}, 404) + self.assertEquals(resp['status'], 404) + self.assertEquals(resp['read_acl'], None) + self.assertEquals(resp['write_acl'], None) + + def test_headers_to_container_info_meta(self): + headers = {'X-Container-Meta-Whatevs': 14, + 'x-container-meta-somethingelse': 0} + resp = headers_to_container_info(headers.items(), 200) + self.assertEquals(len(resp['meta']), 2) + self.assertEquals(resp['meta']['whatevs'], 14) + self.assertEquals(resp['meta']['somethingelse'], 0) + + def test_headers_to_container_info_values(self): + headers = { + 'x-container-read': 'readvalue', + 'x-container-write': 'writevalue', + 'x-container-sync-key': 'keyvalue', + 'x-container-meta-access-control-allow-origin': 'here', + } + resp = headers_to_container_info(headers.items(), 200) + self.assertEquals(resp['read_acl'], 'readvalue') + self.assertEquals(resp['write_acl'], 'writevalue') + self.assertEquals(resp['cors']['allow_origin'], 'here') + + headers['x-unused-header'] = 'blahblahblah' + self.assertEquals( + resp, + headers_to_container_info(headers.items(), 200))