diff options
author | Samuel Merritt <sam@swiftstack.com> | 2013-05-23 18:53:51 -0700 |
---|---|---|
committer | Samuel Merritt <sam@swiftstack.com> | 2013-05-30 17:43:03 -0700 |
commit | 15c2ca55f02e1785f999a6790d9a5ff033449bcb (patch) | |
tree | 4ff638e4b854d637f5d257476a14266aaad3981d /swift/account/utils.py | |
parent | 5501a4031f786ccf7645fdb90b0e54b27e1242d4 (diff) | |
download | swift-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.py | 121 |
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 |