summaryrefslogtreecommitdiff
path: root/swift/account/utils.py
diff options
context:
space:
mode:
authorSamuel Merritt <sam@swiftstack.com>2013-05-23 18:53:51 -0700
committerSamuel Merritt <sam@swiftstack.com>2013-05-30 17:43:03 -0700
commit15c2ca55f02e1785f999a6790d9a5ff033449bcb (patch)
tree4ff638e4b854d637f5d257476a14266aaad3981d /swift/account/utils.py
parent5501a4031f786ccf7645fdb90b0e54b27e1242d4 (diff)
downloadswift-15c2ca55f02e1785f999a6790d9a5ff033449bcb.tar.gz
Fix faked-out account GET for JSON and XML.
If account autocreation is on and the proxy receives a GET request for a nonexistent account, it'll fake up a response that makes it look as if the account exists, but without reifying that account into sqlite DB files. That faked-out response was just fine as long as you wanted a text/plain response, but it didn't handle the case of format=json or format=xml; in those cases, the response would still be text/plain. This can break clients, and certainly causes crashes in swift3. Now, those responses match as closely as possible. The code for generating an account-listing response has been pulled into (the new) swift.account.utils module, and both the fake response and the real response use it, thus ensuring that they can't accidentally diverge. There's also a new probe test for that non-divergence. Also, cleaned up a redundant matching of the Accept header in the code for generating the account listing. Note that some of the added tests here pass with or without this code change; they were added because the code I was changing (parts of the real account GET) wasn't covered by tests. Bug 1183169 Change-Id: I2a3b8e5d9053e4d0280a320f31efa7c90c94bb06
Diffstat (limited to 'swift/account/utils.py')
-rw-r--r--swift/account/utils.py121
1 files changed, 121 insertions, 0 deletions
diff --git a/swift/account/utils.py b/swift/account/utils.py
new file mode 100644
index 000000000..20dbc0aa6
--- /dev/null
+++ b/swift/account/utils.py
@@ -0,0 +1,121 @@
+# Copyright (c) 2010-2013 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 time
+from xml.sax import saxutils
+
+from swift.common.constraints import FORMAT2CONTENT_TYPE
+from swift.common.swob import HTTPOk, HTTPNoContent, HTTPNotAcceptable, \
+ HTTPBadRequest
+from swift.common.utils import get_param, json, normalize_timestamp
+
+
+class FakeAccountBroker(object):
+ """
+ Quacks like an account broker, but doesn't actually do anything. Responds
+ like an account broker would for a real, empty account with no metadata.
+ """
+ def get_info(self):
+ now = normalize_timestamp(time.time())
+ return {'container_count': 0,
+ 'object_count': 0,
+ 'bytes_used': 0,
+ 'created_at': now,
+ 'put_timestamp': now}
+
+ def list_containers_iter(self, *_, **__):
+ return []
+
+ @property
+ def metadata(self):
+ return {}
+
+
+def account_listing_content_type(req):
+ """
+ Figure out the content type of an account-listing response.
+
+ Returns a 2-tuple: (content_type, error). Only one of them will be set;
+ the other will be None.
+ """
+ try:
+ query_format = get_param(req, 'format')
+ except UnicodeDecodeError:
+ return (None, HTTPBadRequest(body='parameters not utf8',
+ content_type='text/plain', request=req))
+ if query_format:
+ req.accept = FORMAT2CONTENT_TYPE.get(query_format.lower(),
+ FORMAT2CONTENT_TYPE['plain'])
+ content_type = req.accept.best_match(
+ ['text/plain', 'application/json', 'application/xml', 'text/xml'])
+ if not content_type:
+ return (None, HTTPNotAcceptable(request=req))
+ else:
+ return (content_type, None)
+
+
+def account_listing_response(account, req, response_content_type, broker=None,
+ limit='', marker='', end_marker='', prefix='',
+ delimiter=''):
+ if broker is None:
+ broker = FakeAccountBroker()
+
+ info = broker.get_info()
+ resp_headers = {
+ 'X-Account-Container-Count': info['container_count'],
+ 'X-Account-Object-Count': info['object_count'],
+ 'X-Account-Bytes-Used': info['bytes_used'],
+ 'X-Timestamp': info['created_at'],
+ 'X-PUT-Timestamp': info['put_timestamp']}
+ resp_headers.update((key, value)
+ for key, (value, timestamp) in
+ broker.metadata.iteritems() if value != '')
+
+ account_list = broker.list_containers_iter(limit, marker, end_marker,
+ prefix, delimiter)
+ if response_content_type == 'application/json':
+ data = []
+ for (name, object_count, bytes_used, is_subdir) in account_list:
+ if is_subdir:
+ data.append({'subdir': name})
+ else:
+ data.append({'name': name, 'count': object_count,
+ 'bytes': bytes_used})
+ account_list = json.dumps(data)
+ elif response_content_type.endswith('/xml'):
+ output_list = ['<?xml version="1.0" encoding="UTF-8"?>',
+ '<account name="%s">' % account]
+ for (name, object_count, bytes_used, is_subdir) in account_list:
+ name = saxutils.escape(name)
+ if is_subdir:
+ output_list.append('<subdir name="%s" />' % name)
+ else:
+ item = '<container><name>%s</name><count>%s</count>' \
+ '<bytes>%s</bytes></container>' % \
+ (name, object_count, bytes_used)
+ output_list.append(item)
+ output_list.append('</account>')
+ account_list = '\n'.join(output_list)
+ else:
+ if not account_list:
+ resp = HTTPNoContent(request=req, headers=resp_headers)
+ resp.content_type = response_content_type
+ resp.charset = 'utf-8'
+ return resp
+ account_list = '\n'.join(r[0] for r in account_list) + '\n'
+ ret = HTTPOk(body=account_list, request=req, headers=resp_headers)
+ ret.content_type = response_content_type
+ ret.charset = 'utf-8'
+ return ret