From a4f371439bbe3195da22daac621ca331e02a9ae9 Mon Sep 17 00:00:00 2001 From: Samuel Merritt Date: Wed, 14 Aug 2013 11:55:15 -0700 Subject: [PATCH] Refactor how we pick listings' content type. There were a few different places where we had some repeated code to figure out what format an account or container listing response should be in (text, JSON, or XML). Now that's been pulled into a single function. As part of this, you can now raise HTTPException subclasses in proxy controllers instead of laboriously plumbing error responses all the way back up to swift.proxy.server.Application.handle_request(). This lets us avoid certain ugly patterns, like the one where a method returns a tuple of (x, y, z, error) and the caller has to see if it got (value, value, value, None) or (None, None, None, errorvalue). Now we can just raise the error. Change-Id: I316873df289160d526487ad116f6fbb9a575e3de --- swift/account/server.py | 22 ++--- swift/account/utils.py | 23 +----- swift/common/request_helpers.py | 26 +++++- swift/container/server.py | 24 ++---- swift/proxy/controllers/account.py | 9 +-- swift/proxy/server.py | 4 +- test/unit/account/test_server.py | 34 ++++---- test/unit/container/test_server.py | 124 +++++++++++++++++------------ test/unit/proxy/test_server.py | 67 +++++++++++----- 9 files changed, 176 insertions(+), 157 deletions(-) diff --git a/swift/account/server.py b/swift/account/server.py index 757fa6d904..ef469b0a60 100644 --- a/swift/account/server.py +++ b/swift/account/server.py @@ -23,21 +23,20 @@ from gettext import gettext as _ from eventlet import Timeout import swift.common.db -from swift.account.utils import account_listing_response, \ - account_listing_content_type +from swift.account.utils import account_listing_response from swift.common.db import AccountBroker, DatabaseConnectionError -from swift.common.request_helpers import get_param +from swift.common.request_helpers import get_param, get_listing_content_type from swift.common.utils import get_logger, hash_path, public, \ normalize_timestamp, storage_directory, config_true_value, \ validate_device_partition, json, timing_stats, replication from swift.common.constraints import ACCOUNT_LISTING_LIMIT, \ - check_mount, check_float, check_utf8, FORMAT2CONTENT_TYPE + check_mount, check_float, check_utf8 from swift.common.db_replicator import ReplicatorRpc from swift.common.swob import HTTPAccepted, HTTPBadRequest, \ HTTPCreated, HTTPForbidden, HTTPInternalServerError, \ HTTPMethodNotAllowed, HTTPNoContent, HTTPNotFound, \ HTTPPreconditionFailed, HTTPConflict, Request, \ - HTTPInsufficientStorage, HTTPNotAcceptable, HTTPException + HTTPInsufficientStorage, HTTPException DATADIR = 'accounts' @@ -181,14 +180,7 @@ class AccountController(object): except ValueError, err: return HTTPBadRequest(body=str(err), content_type='text/plain', request=req) - query_format = get_param(req, 'format') - if query_format: - req.accept = FORMAT2CONTENT_TYPE.get( - query_format.lower(), FORMAT2CONTENT_TYPE['plain']) - out_content_type = req.accept.best_match( - ['text/plain', 'application/json', 'application/xml', 'text/xml']) - if not out_content_type: - return HTTPNotAcceptable(request=req) + out_content_type = get_listing_content_type(req) if self.mount_check and not check_mount(self.root, drive): return HTTPInsufficientStorage(drive=drive, request=req) broker = self._get_account_broker(drive, part, account, @@ -234,9 +226,7 @@ class AccountController(object): ACCOUNT_LISTING_LIMIT) marker = get_param(req, 'marker', '') end_marker = get_param(req, 'end_marker') - out_content_type, error = account_listing_content_type(req) - if error: - return error + out_content_type = get_listing_content_type(req) if self.mount_check and not check_mount(self.root, drive): return HTTPInsufficientStorage(drive=drive, request=req) diff --git a/swift/account/utils.py b/swift/account/utils.py index e4c3b053b8..5565b46166 100644 --- a/swift/account/utils.py +++ b/swift/account/utils.py @@ -16,9 +16,7 @@ import time from xml.sax import saxutils -from swift.common.constraints import FORMAT2CONTENT_TYPE -from swift.common.swob import HTTPOk, HTTPNoContent, HTTPNotAcceptable -from swift.common.request_helpers import get_param +from swift.common.swob import HTTPOk, HTTPNoContent from swift.common.utils import json, normalize_timestamp @@ -43,25 +41,6 @@ class FakeAccountBroker(object): 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. - """ - query_format = get_param(req, 'format') - 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=''): diff --git a/swift/common/request_helpers.py b/swift/common/request_helpers.py index 5dd8b940fa..283a7be98a 100644 --- a/swift/common/request_helpers.py +++ b/swift/common/request_helpers.py @@ -20,8 +20,8 @@ Why not swift.common.utils, you ask? Because this way we can import things from swob in here without creating circular imports. """ - -from swift.common.swob import HTTPBadRequest +from swift.common.constraints import FORMAT2CONTENT_TYPE +from swift.common.swob import HTTPBadRequest, HTTPNotAcceptable def get_param(req, name, default=None): @@ -45,3 +45,25 @@ def get_param(req, name, default=None): request=req, content_type='text/plain', body='"%s" parameter not valid UTF-8' % name) return value + + +def get_listing_content_type(req): + """ + Determine the content type to use for an account or container listing + response. + + :param req: request object + :returns: content type as a string (e.g. text/plain, application/json) + :raises: HTTPNotAcceptable if the requested content type is not acceptable + :raises: HTTPBadRequest if the 'format' query param is provided and + not valid UTF-8 + """ + query_format = get_param(req, 'format') + if query_format: + req.accept = FORMAT2CONTENT_TYPE.get( + query_format.lower(), FORMAT2CONTENT_TYPE['plain']) + out_content_type = req.accept.best_match( + ['text/plain', 'application/json', 'application/xml', 'text/xml']) + if not out_content_type: + raise HTTPNotAcceptable(request=req) + return out_content_type diff --git a/swift/container/server.py b/swift/container/server.py index 0da4a0c4d3..c2f75d8fae 100644 --- a/swift/container/server.py +++ b/swift/container/server.py @@ -26,13 +26,13 @@ from eventlet import Timeout import swift.common.db from swift.common.db import ContainerBroker -from swift.common.request_helpers import get_param +from swift.common.request_helpers import get_param, get_listing_content_type from swift.common.utils import get_logger, hash_path, public, \ normalize_timestamp, storage_directory, validate_sync_to, \ config_true_value, validate_device_partition, json, timing_stats, \ replication, parse_content_type from swift.common.constraints import CONTAINER_LISTING_LIMIT, \ - check_mount, check_float, check_utf8, FORMAT2CONTENT_TYPE + check_mount, check_float, check_utf8 from swift.common.bufferedhttp import http_connect from swift.common.exceptions import ConnectionTimeout from swift.common.db_replicator import ReplicatorRpc @@ -40,7 +40,7 @@ from swift.common.http import HTTP_NOT_FOUND, is_success from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPConflict, \ HTTPCreated, HTTPInternalServerError, HTTPNoContent, HTTPNotFound, \ HTTPPreconditionFailed, HTTPMethodNotAllowed, Request, Response, \ - HTTPInsufficientStorage, HTTPNotAcceptable, HTTPException, HeaderKeyDict + HTTPInsufficientStorage, HTTPException, HeaderKeyDict DATADIR = 'containers' @@ -300,14 +300,7 @@ class ContainerController(object): except ValueError, err: return HTTPBadRequest(body=str(err), content_type='text/plain', request=req) - query_format = get_param(req, 'format') - if query_format: - req.accept = FORMAT2CONTENT_TYPE.get( - query_format.lower(), FORMAT2CONTENT_TYPE['plain']) - out_content_type = req.accept.best_match( - ['text/plain', 'application/json', 'application/xml', 'text/xml']) - if not out_content_type: - return HTTPNotAcceptable(request=req) + out_content_type = get_listing_content_type(req) if self.mount_check and not check_mount(self.root, drive): return HTTPInsufficientStorage(drive=drive, request=req) broker = self._get_container_broker(drive, part, account, container, @@ -388,14 +381,7 @@ class ContainerController(object): return HTTPPreconditionFailed( request=req, body='Maximum limit is %d' % CONTAINER_LISTING_LIMIT) - query_format = get_param(req, 'format') - if query_format: - req.accept = FORMAT2CONTENT_TYPE.get(query_format.lower(), - FORMAT2CONTENT_TYPE['plain']) - out_content_type = req.accept.best_match( - ['text/plain', 'application/json', 'application/xml', 'text/xml']) - if not out_content_type: - return HTTPNotAcceptable(request=req) + out_content_type = get_listing_content_type(req) if self.mount_check and not check_mount(self.root, drive): return HTTPInsufficientStorage(drive=drive, request=req) broker = self._get_container_broker(drive, part, account, container, diff --git a/swift/proxy/controllers/account.py b/swift/proxy/controllers/account.py index 04af4d1cd9..d0cffddc4f 100644 --- a/swift/proxy/controllers/account.py +++ b/swift/proxy/controllers/account.py @@ -27,8 +27,8 @@ from gettext import gettext as _ from urllib import unquote -from swift.account.utils import account_listing_response, \ - account_listing_content_type +from swift.account.utils import account_listing_response +from swift.common.request_helpers import get_listing_content_type from swift.common.utils import public from swift.common.constraints import check_metadata, MAX_ACCOUNT_NAME_LENGTH from swift.common.http import HTTP_NOT_FOUND @@ -60,11 +60,8 @@ class AccountController(Controller): req, _('Account'), self.app.account_ring, partition, req.path_info.rstrip('/')) if resp.status_int == HTTP_NOT_FOUND and self.app.account_autocreate: - content_type, error = account_listing_content_type(req) - if error: - return error resp = account_listing_response(self.account_name, req, - content_type) + get_listing_content_type(req)) if not req.environ.get('swift_owner', False): for key in self.app.swift_owner_headers: if key in resp.headers: diff --git a/swift/proxy/server.py b/swift/proxy/server.py index 4601a72a9e..15814d5b9c 100644 --- a/swift/proxy/server.py +++ b/swift/proxy/server.py @@ -42,7 +42,7 @@ from swift.proxy.controllers import AccountController, ObjectController, \ ContainerController from swift.common.swob import HTTPBadRequest, HTTPForbidden, \ HTTPMethodNotAllowed, HTTPNotFound, HTTPPreconditionFailed, \ - HTTPServerError, Request + HTTPServerError, HTTPException, Request class Application(object): @@ -293,6 +293,8 @@ class Application(object): # method the client actually sent. req.environ['swift.orig_req_method'] = req.method return handler(req) + except HTTPException as error_response: + return error_response except (Exception, Timeout): self.logger.exception(_('ERROR Unhandled exception in request')) return HTTPServerError(request=req) diff --git a/test/unit/account/test_server.py b/test/unit/account/test_server.py index 8f2afb22f3..94a7094c99 100644 --- a/test/unit/account/test_server.py +++ b/test/unit/account/test_server.py @@ -129,7 +129,7 @@ class TestAccountController(unittest.TestCase): def test_HEAD_not_found(self): # Test the case in which account does not exist (can be recreated) req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 404) self.assertTrue('X-Account-Status' not in resp.headers) @@ -156,7 +156,7 @@ class TestAccountController(unittest.TestCase): 'HTTP_X_TIMESTAMP': '0'}) self.controller.PUT(req) req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assertEquals(resp.headers['x-account-container-count'], '0') self.assertEquals(resp.headers['x-account-object-count'], '0') @@ -181,7 +181,7 @@ class TestAccountController(unittest.TestCase): 'X-Timestamp': normalize_timestamp(0)}) self.controller.PUT(req) req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assertEquals(resp.headers['x-account-container-count'], '2') self.assertEquals(resp.headers['x-account-object-count'], '0') @@ -202,7 +202,7 @@ class TestAccountController(unittest.TestCase): self.controller.PUT(req) req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'HEAD', 'HTTP_X_TIMESTAMP': '5'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assertEquals(resp.headers['x-account-container-count'], '2') self.assertEquals(resp.headers['x-account-object-count'], '4') @@ -211,20 +211,20 @@ class TestAccountController(unittest.TestCase): def test_HEAD_invalid_partition(self): req = Request.blank('/sda1/./a', environ={'REQUEST_METHOD': 'HEAD', 'HTTP_X_TIMESTAMP': '1'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 400) def test_HEAD_invalid_content_type(self): req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'HEAD'}, headers={'Accept': 'application/plain'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 406) def test_HEAD_insufficient_storage(self): self.controller = AccountController({'devices': self.testdir}) req = Request.blank('/sda-null/p/a', environ={'REQUEST_METHOD': 'HEAD', 'HTTP_X_TIMESTAMP': '1'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 507) def test_HEAD_invalid_format(self): @@ -349,7 +349,7 @@ class TestAccountController(unittest.TestCase): resp = self.controller.POST(req) self.assertEquals(resp.status_int, 204) req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assertEquals(resp.headers.get('x-account-meta-test'), 'Value') # Update metadata header @@ -359,7 +359,7 @@ class TestAccountController(unittest.TestCase): resp = self.controller.POST(req) self.assertEquals(resp.status_int, 204) req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assertEquals(resp.headers.get('x-account-meta-test'), 'New Value') # Send old update to metadata header @@ -369,7 +369,7 @@ class TestAccountController(unittest.TestCase): resp = self.controller.POST(req) self.assertEquals(resp.status_int, 204) req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assertEquals(resp.headers.get('x-account-meta-test'), 'New Value') # Remove metadata header (by setting it to empty) @@ -379,7 +379,7 @@ class TestAccountController(unittest.TestCase): resp = self.controller.POST(req) self.assertEquals(resp.status_int, 204) req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assert_('x-account-meta-test' not in resp.headers) @@ -974,14 +974,14 @@ class TestAccountController(unittest.TestCase): self.controller.PUT(req) req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'GET'}) req.accept = 'application/xml*' - resp = self.controller.GET(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 406) def test_GET_delimiter_too_long(self): req = Request.blank('/sda1/p/a?delimiter=xx', environ={'REQUEST_METHOD': 'GET', 'HTTP_X_TIMESTAMP': '0'}) - resp = self.controller.GET(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 412) def test_GET_prefix_delimiter_plain(self): @@ -1306,22 +1306,22 @@ class TestAccountController(unittest.TestCase): env = {'REQUEST_METHOD': 'HEAD'} req = Request.blank('/sda1/p/a?format=xml', environ=env) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/xml') req = Request.blank('/sda1/p/a?format=json', environ=env) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/json') self.assertEquals(resp.charset, 'utf-8') req = Request.blank('/sda1/p/a', environ=env) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'text/plain') self.assertEquals(resp.charset, 'utf-8') req = Request.blank( '/sda1/p/a', headers={'Accept': 'application/json'}, environ=env) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/json') self.assertEquals(resp.charset, 'utf-8') diff --git a/test/unit/container/test_server.py b/test/unit/container/test_server.py index 6a3b8b1b10..75d1e74564 100644 --- a/test/unit/container/test_server.py +++ b/test/unit/container/test_server.py @@ -64,9 +64,9 @@ class TestContainerController(unittest.TestCase): # Ensure no acl by default req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'PUT'}, headers={'X-Timestamp': '0'}) - self.controller.PUT(req) + req.get_response(self.controller) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) - response = self.controller.HEAD(req) + response = req.get_response(self.controller) self.assert_(response.status.startswith('204')) self.assert_('x-container-read' not in response.headers) self.assert_('x-container-write' not in response.headers) @@ -74,9 +74,9 @@ class TestContainerController(unittest.TestCase): req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'POST'}, headers={'X-Timestamp': '1', 'X-Container-Read': '.r:*', 'X-Container-Write': 'account:user'}) - self.controller.POST(req) + resp = req.get_response(self.controller) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) - response = self.controller.HEAD(req) + response = req.get_response(self.controller) self.assert_(response.status.startswith('204')) self.assertEquals(response.headers.get('x-container-read'), '.r:*') self.assertEquals(response.headers.get('x-container-write'), @@ -85,9 +85,9 @@ class TestContainerController(unittest.TestCase): req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'POST'}, headers={'X-Timestamp': '3', 'X-Container-Read': '', 'X-Container-Write': ''}) - self.controller.POST(req) + resp = req.get_response(self.controller) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) - response = self.controller.HEAD(req) + response = req.get_response(self.controller) self.assert_(response.status.startswith('204')) self.assert_('x-container-read' not in response.headers) self.assert_('x-container-write' not in response.headers) @@ -95,9 +95,9 @@ class TestContainerController(unittest.TestCase): req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'}, headers={'X-Timestamp': '4', 'X-Container-Read': '.r:*', 'X-Container-Write': 'account:user'}) - self.controller.PUT(req) + resp = req.get_response(self.controller) req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'HEAD'}) - response = self.controller.HEAD(req) + response = req.get_response(self.controller) self.assert_(response.status.startswith('204')) self.assertEquals(response.headers.get('x-container-read'), '.r:*') self.assertEquals(response.headers.get('x-container-write'), @@ -106,28 +106,31 @@ class TestContainerController(unittest.TestCase): def test_HEAD(self): req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '0'}) - self.controller.PUT(req) - response = self.controller.HEAD(req) + req.get_response(self.controller) + req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD', + 'HTTP_X_TIMESTAMP': '0'}) + response = req.get_response(self.controller) self.assert_(response.status.startswith('204')) self.assertEquals(int(response.headers['x-container-bytes-used']), 0) self.assertEquals(int(response.headers['x-container-object-count']), 0) req2 = Request.blank('/sda1/p/a/c/o', environ={ + 'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '1', 'HTTP_X_SIZE': 42, 'HTTP_X_CONTENT_TYPE': 'text/plain', 'HTTP_X_ETAG': 'x'}) - self.controller.PUT(req2) - response = self.controller.HEAD(req) + req2.get_response(self.controller) + response = req.get_response(self.controller) self.assertEquals(int(response.headers['x-container-bytes-used']), 42) self.assertEquals(int(response.headers['x-container-object-count']), 1) def test_HEAD_not_found(self): req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 404) def test_HEAD_invalid_partition(self): req = Request.blank('/sda1/./a/c', environ={'REQUEST_METHOD': 'HEAD', 'HTTP_X_TIMESTAMP': '1'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 400) def test_HEAD_insufficient_storage(self): @@ -135,13 +138,13 @@ class TestContainerController(unittest.TestCase): {'devices': self.testdir}) req = Request.blank('/sda-null/p/a/c', environ={'REQUEST_METHOD': 'HEAD', 'HTTP_X_TIMESTAMP': '1'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 507) def test_HEAD_invalid_content_type(self): req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}, headers={'Accept': 'application/plain'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 406) def test_HEAD_invalid_format(self): @@ -258,7 +261,7 @@ class TestContainerController(unittest.TestCase): resp = self.controller.POST(req) self.assertEquals(resp.status_int, 204) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assertEquals(resp.headers.get('x-container-meta-test'), 'Value') # Update metadata header @@ -268,7 +271,7 @@ class TestContainerController(unittest.TestCase): resp = self.controller.POST(req) self.assertEquals(resp.status_int, 204) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assertEquals(resp.headers.get('x-container-meta-test'), 'New Value') @@ -279,7 +282,7 @@ class TestContainerController(unittest.TestCase): resp = self.controller.POST(req) self.assertEquals(resp.status_int, 204) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assertEquals(resp.headers.get('x-container-meta-test'), 'New Value') @@ -290,14 +293,14 @@ class TestContainerController(unittest.TestCase): resp = self.controller.POST(req) self.assertEquals(resp.status_int, 204) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 204) self.assert_('x-container-meta-test' not in resp.headers) def test_POST_invalid_partition(self): req = Request.blank('/sda1/./a/c', environ={'REQUEST_METHOD': 'POST', 'HTTP_X_TIMESTAMP': '1'}) - resp = self.controller.POST(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 400) def test_POST_timestamp_not_float(self): @@ -306,7 +309,7 @@ class TestContainerController(unittest.TestCase): self.controller.PUT(req) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'POST'}, headers={'X-Timestamp': 'not-float'}) - resp = self.controller.POST(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 400) def test_POST_insufficient_storage(self): @@ -314,7 +317,7 @@ class TestContainerController(unittest.TestCase): {'devices': self.testdir}) req = Request.blank('/sda-null/p/a/c', environ={'REQUEST_METHOD': 'POST', 'HTTP_X_TIMESTAMP': '1'}) - resp = self.controller.POST(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 507) def test_POST_invalid_container_sync_to(self): @@ -323,22 +326,22 @@ class TestContainerController(unittest.TestCase): req = Request.blank('/sda-null/p/a/c', environ={'REQUEST_METHOD': 'POST', 'HTTP_X_TIMESTAMP': '1'}, headers={'x-container-sync-to': '192.168.0.1'}) - resp = self.controller.POST(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 400) def test_POST_after_DELETE_not_found(self): req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'PUT'}, headers={'X-Timestamp': '1'}) - self.controller.PUT(req) + resp = req.get_response(self.controller) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'DELETE'}, headers={'X-Timestamp': '2'}) - self.controller.DELETE(req) + resp = req.get_response(self.controller) req = Request.blank('/sda1/p/a/c/', environ={'REQUEST_METHOD': 'POST'}, headers={'X-Timestamp': '3'}) - resp = self.controller.POST(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 404) def test_DELETE_obj_not_found(self): @@ -392,7 +395,7 @@ class TestContainerController(unittest.TestCase): with save_globals(): new_connect = fake_http_connect(200, count=123) swift.container.server.http_connect = new_connect - resp = self.controller.PUT(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 201) def test_PUT_account_update(self): @@ -758,7 +761,9 @@ class TestContainerController(unittest.TestCase): self.assertEquals(simplejson.loads(resp.body), json_body) self.assertEquals(resp.charset, 'utf-8') - resp = self.controller.HEAD(req) + req = Request.blank('/sda1/p/a/jsonc?format=json', + environ={'REQUEST_METHOD': 'HEAD'}) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/json') for accept in ('application/json', 'application/json;q=1.0,*/*;q=0.9', @@ -766,13 +771,16 @@ class TestContainerController(unittest.TestCase): req = Request.blank('/sda1/p/a/jsonc', environ={'REQUEST_METHOD': 'GET'}) req.accept = accept - resp = self.controller.GET(req) + resp = req.get_response(self.controller) self.assertEquals(simplejson.loads(resp.body), json_body, 'Invalid body for Accept: %s' % accept) self.assertEquals(resp.content_type, 'application/json', 'Invalid content_type for Accept: %s' % accept) - resp = self.controller.HEAD(req) + req = Request.blank('/sda1/p/a/jsonc', + environ={'REQUEST_METHOD': 'HEAD'}) + req.accept = accept + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/json', 'Invalid content_type for Accept: %s' % accept) @@ -804,7 +812,9 @@ class TestContainerController(unittest.TestCase): self.assertEquals(resp.body, plain_body) self.assertEquals(resp.charset, 'utf-8') - resp = self.controller.HEAD(req) + req = Request.blank('/sda1/p/a/plainc', + environ={'REQUEST_METHOD': 'HEAD'}) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'text/plain') for accept in ('', 'text/plain', 'application/xml;q=0.8,*/*;q=0.9', @@ -819,7 +829,10 @@ class TestContainerController(unittest.TestCase): self.assertEquals(resp.content_type, 'text/plain', 'Invalid content_type for Accept: %s' % accept) - resp = self.controller.HEAD(req) + req = Request.blank('/sda1/p/a/plainc', + environ={'REQUEST_METHOD': 'GET'}) + req.accept = accept + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'text/plain', 'Invalid content_type for Accept: %s' % accept) @@ -915,7 +928,9 @@ class TestContainerController(unittest.TestCase): self.assertEquals(resp.body, xml_body) self.assertEquals(resp.charset, 'utf-8') - resp = self.controller.HEAD(req) + req = Request.blank('/sda1/p/a/xmlc?format=xml', + environ={'REQUEST_METHOD': 'HEAD'}) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/xml') for xml_accept in ('application/xml', 'application/xml;q=1.0,*/*;q=0.9', @@ -929,7 +944,10 @@ class TestContainerController(unittest.TestCase): self.assertEquals(resp.content_type, 'application/xml', 'Invalid content_type for Accept: %s' % xml_accept) - resp = self.controller.HEAD(req) + req = Request.blank('/sda1/p/a/xmlc', + environ={'REQUEST_METHOD': 'HEAD'}) + req.accept = xml_accept + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/xml', 'Invalid content_type for Accept: %s' % xml_accept) @@ -944,13 +962,13 @@ class TestContainerController(unittest.TestCase): # make a container req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '0'}) - resp = self.controller.PUT(req) + resp = req.get_response(self.controller) # fill the container for i in range(3): req = Request.blank('/sda1/p/a/c/%s' % i, environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '1', 'HTTP_X_CONTENT_TYPE': 'text/plain', 'HTTP_X_ETAG': 'x', 'HTTP_X_SIZE': 0}) - resp = self.controller.PUT(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 201) # test limit with marker req = Request.blank('/sda1/p/a/c?limit=2&marker=1', environ={'REQUEST_METHOD': 'GET'}) @@ -962,7 +980,7 @@ class TestContainerController(unittest.TestCase): snowman = u'\u2603' req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '0'}) - resp = self.controller.PUT(req) + resp = req.get_response(self.controller) for i, ctype in enumerate((snowman.encode('utf-8'), 'text/plain; charset="utf-8"')): req = Request.blank('/sda1/p/a/c/%s' % i, environ={ @@ -986,17 +1004,17 @@ class TestContainerController(unittest.TestCase): 'X-Object-Count': '0', 'X-Bytes-Used': '0', 'X-Timestamp': normalize_timestamp(0)}) - self.controller.PUT(req) + req.get_response(self.controller) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'GET'}) req.accept = 'application/xml*' - resp = self.controller.GET(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 406) def test_GET_limit(self): # make a container req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '0'}) - resp = self.controller.PUT(req) + resp = req.get_response(self.controller) # fill the container for i in range(3): req = Request.blank('/sda1/p/a/c/%s' % i, @@ -1006,11 +1024,11 @@ class TestContainerController(unittest.TestCase): 'HTTP_X_CONTENT_TYPE': 'text/plain', 'HTTP_X_ETAG': 'x', 'HTTP_X_SIZE': 0}) - resp = self.controller.PUT(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 201) # test limit req = Request.blank('/sda1/p/a/c?limit=2', environ={'REQUEST_METHOD': 'GET'}) - resp = self.controller.GET(req) + resp = req.get_response(self.controller) result = resp.body.split() self.assertEquals(result, ['0', '1']) @@ -1026,17 +1044,17 @@ class TestContainerController(unittest.TestCase): 'HTTP_X_CONTENT_TYPE': 'text/plain', 'HTTP_X_ETAG': 'x', 'HTTP_X_SIZE': 0}) - resp = self.controller.PUT(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 201) req = Request.blank('/sda1/p/a/c?prefix=a', environ={'REQUEST_METHOD': 'GET'}) - resp = self.controller.GET(req) + resp = req.get_response(self.controller) self.assertEquals(resp.body.split(), ['a1', 'a2', 'a3']) def test_GET_delimiter_too_long(self): req = Request.blank('/sda1/p/a/c?delimiter=xx', environ={'REQUEST_METHOD': 'GET', 'HTTP_X_TIMESTAMP': '0'}) - resp = self.controller.GET(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 412) def test_GET_delimiter(self): @@ -1049,11 +1067,11 @@ class TestContainerController(unittest.TestCase): 'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '1', 'HTTP_X_CONTENT_TYPE': 'text/plain', 'HTTP_X_ETAG': 'x', 'HTTP_X_SIZE': 0}) - resp = self.controller.PUT(req) + resp = req.get_response(self.controller) self.assertEquals(resp.status_int, 201) req = Request.blank('/sda1/p/a/c?prefix=US-&delimiter=-&format=json', environ={'REQUEST_METHOD': 'GET'}) - resp = self.controller.GET(req) + resp = req.get_response(self.controller) self.assertEquals(simplejson.loads(resp.body), [{"subdir": "US-OK-"}, {"subdir": "US-TX-"}, @@ -1324,29 +1342,29 @@ class TestContainerController(unittest.TestCase): env = {'REQUEST_METHOD': 'HEAD'} req = Request.blank('/sda1/p/a/o?format=xml', environ=env) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/xml') self.assertEquals(resp.charset, 'utf-8') req = Request.blank('/sda1/p/a/o?format=json', environ=env) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/json') self.assertEquals(resp.charset, 'utf-8') req = Request.blank('/sda1/p/a/o', environ=env) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'text/plain') self.assertEquals(resp.charset, 'utf-8') req = Request.blank( '/sda1/p/a/o', headers={'Accept': 'application/json'}, environ=env) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/json') self.assertEquals(resp.charset, 'utf-8') req = Request.blank( '/sda1/p/a/o', headers={'Accept': 'application/xml'}, environ=env) - resp = self.controller.HEAD(req) + resp = req.get_response(self.controller) self.assertEquals(resp.content_type, 'application/xml') self.assertEquals(resp.charset, 'utf-8') diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index c3202a9059..1d913d2f9c 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -5725,7 +5725,6 @@ class TestAccountController(unittest.TestCase): req.content_length = 0 resp = controller.OPTIONS(req) self.assertEquals(200, resp.status_int) - print resp.headers['Allow'] for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split(): self.assertTrue( verb in resp.headers['Allow']) @@ -6093,20 +6092,20 @@ class TestAccountControllerFakeGetResponse(unittest.TestCase): have to match the responses for empty accounts that really exist. """ def setUp(self): - self.app = proxy_server.Application(None, FakeMemcache(), + conf = {'account_autocreate': 'yes'} + self.app = proxy_server.Application(conf, FakeMemcache(), account_ring=FakeRing(), container_ring=FakeRing(), object_ring=FakeRing) self.app.memcache = FakeMemcacheReturnsNone() - self.controller = proxy_server.AccountController(self.app, 'acc') - self.controller.app.account_autocreate = True def test_GET_autocreate_accept_json(self): with save_globals(): - set_http_connect(404) # however many backends we ask, they all 404 - req = Request.blank('/a', headers={'Accept': 'application/json'}) - - resp = self.controller.GET(req) + set_http_connect(*([404] * 100)) # nonexistent: all backends 404 + req = Request.blank('/v1/a', headers={'Accept': 'application/json'}, + environ={'REQUEST_METHOD': 'GET', + 'PATH_INFO': '/v1/a'}) + resp = req.get_response(self.app) self.assertEqual(200, resp.status_int) self.assertEqual('application/json; charset=utf-8', resp.headers['Content-Type']) @@ -6114,10 +6113,12 @@ class TestAccountControllerFakeGetResponse(unittest.TestCase): def test_GET_autocreate_format_json(self): with save_globals(): - set_http_connect(404) # however many backends we ask, they all 404 - req = Request.blank('/a?format=json') - - resp = self.controller.GET(req) + set_http_connect(*([404] * 100)) # nonexistent: all backends 404 + req = Request.blank('/v1/a?format=json', + environ={'REQUEST_METHOD': 'GET', + 'PATH_INFO': '/v1/a', + 'QUERY_STRING': 'format=json'}) + resp = req.get_response(self.app) self.assertEqual(200, resp.status_int) self.assertEqual('application/json; charset=utf-8', resp.headers['Content-Type']) @@ -6125,30 +6126,54 @@ class TestAccountControllerFakeGetResponse(unittest.TestCase): def test_GET_autocreate_accept_xml(self): with save_globals(): - set_http_connect(404) # however many backends we ask, they all 404 - req = Request.blank('/a', headers={"Accept": "text/xml"}) + set_http_connect(*([404] * 100)) # nonexistent: all backends 404 + req = Request.blank('/v1/a', headers={"Accept": "text/xml"}, + environ={'REQUEST_METHOD': 'GET', + 'PATH_INFO': '/v1/a'}) - resp = self.controller.GET(req) + resp = req.get_response(self.app) self.assertEqual(200, resp.status_int) + self.assertEqual('text/xml; charset=utf-8', resp.headers['Content-Type']) empty_xml_listing = ('\n' - '\n') + '\n') self.assertEqual(empty_xml_listing, resp.body) def test_GET_autocreate_format_xml(self): with save_globals(): - set_http_connect(404) # however many backends we ask, they all 404 - req = Request.blank('/a?format=xml') - - resp = self.controller.GET(req) + set_http_connect(*([404] * 100)) # nonexistent: all backends 404 + req = Request.blank('/v1/a?format=xml', + environ={'REQUEST_METHOD': 'GET', + 'PATH_INFO': '/v1/a', + 'QUERY_STRING': 'format=xml'}) + resp = req.get_response(self.app) self.assertEqual(200, resp.status_int) self.assertEqual('application/xml; charset=utf-8', resp.headers['Content-Type']) empty_xml_listing = ('\n' - '\n') + '\n') self.assertEqual(empty_xml_listing, resp.body) + def test_GET_autocreate_accept_unknown(self): + with save_globals(): + set_http_connect(*([404] * 100)) # nonexistent: all backends 404 + req = Request.blank('/v1/a', headers={"Accept": "mystery/meat"}, + environ={'REQUEST_METHOD': 'GET', + 'PATH_INFO': '/v1/a'}) + resp = req.get_response(self.app) + self.assertEqual(406, resp.status_int) + + def test_GET_autocreate_format_invalid_utf8(self): + with save_globals(): + set_http_connect(*([404] * 100)) # nonexistent: all backends 404 + req = Request.blank('/v1/a?format=\xff\xfe', + environ={'REQUEST_METHOD': 'GET', + 'PATH_INFO': '/v1/a', + 'QUERY_STRING': 'format=\xff\xfe'}) + resp = req.get_response(self.app) + self.assertEqual(400, resp.status_int) + class FakeObjectController(object):