FakeSwift: use HTTPMethodNotAllowed not HTTPNotImplemented

If a method is not allowed, real swift proxy server app will return an
HTTPMethodNotAllowed response, whereas FakeSwift would previously
*raise* HTTPNotImplemented. S3Api deliberately sends requests with
method 'TEST' which is not allowed/implemented. To workaround the
difference in real and fake swift behaviour, FakeSwift was configured
to allow the 'TEST' method, and then in some tests an
HTTPMethodNotAllowed response was registered for 'TEST' requests!

This patch modifies FakeSwift to return an HTTPMethodNotAllowed
response to the incoming request when the request method is not
allowed.

It is no longer necessary for FakeSwift to support extending the
default list of allowed methods.

Change-Id: I550d0174e14a5d5a05d26e5cbe9d3353f5da4e8a
This commit is contained in:
Alistair Coles 2023-12-14 12:32:28 +00:00
parent b07d87c4be
commit 365db20275
4 changed files with 16 additions and 35 deletions

View File

@ -23,7 +23,7 @@ from swift.common.request_helpers import is_user_meta, \
is_object_transient_sysmeta, resolve_etag_is_at_header, \ is_object_transient_sysmeta, resolve_etag_is_at_header, \
resolve_ignore_range_header resolve_ignore_range_header
from swift.common.storage_policy import POLICIES from swift.common.storage_policy import POLICIES
from swift.common.swob import HTTPNotImplemented from swift.common.swob import HTTPMethodNotAllowed
from swift.common.utils import split_path, md5 from swift.common.utils import split_path, md5
from test.debug_logger import debug_logger from test.debug_logger import debug_logger
@ -119,16 +119,13 @@ class FakeSwift(object):
* received ``POST /v1/a/c/o?x=y``, if it matches a registered ``POST``, * received ``POST /v1/a/c/o?x=y``, if it matches a registered ``POST``,
will update uploaded ``/v1/a/c/o`` will update uploaded ``/v1/a/c/o``
""" """
DEFAULT_ALLOWED_METHODS = [ ALLOWED_METHODS = [
'PUT', 'POST', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'REPLICATE', 'PUT', 'POST', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'REPLICATE',
'SSYNC', 'UPDATE'] 'SSYNC', 'UPDATE']
container_existence_skip_cache = 0.0 container_existence_skip_cache = 0.0
account_existence_skip_cache = 0.0 account_existence_skip_cache = 0.0
def __init__(self, allowed_methods=None): def __init__(self):
self.allowed_methods = set(self.DEFAULT_ALLOWED_METHODS)
if allowed_methods:
self.allowed_methods.update(allowed_methods)
self._calls = [] self._calls = []
self.req_bodies = [] self.req_bodies = []
self._unclosed_req_keys = defaultdict(int) self._unclosed_req_keys = defaultdict(int)
@ -237,8 +234,8 @@ class FakeSwift(object):
def __call__(self, env, start_response): def __call__(self, env, start_response):
method = env['REQUEST_METHOD'] method = env['REQUEST_METHOD']
if method not in self.allowed_methods: if method not in self.ALLOWED_METHODS:
raise HTTPNotImplemented() return HTTPMethodNotAllowed()(env, start_response)
path, acc, cont, obj = self._parse_path(env) path, acc, cont, obj = self._parse_path(env)

View File

@ -39,7 +39,7 @@ class FakeApp(object):
def __init__(self): def __init__(self):
self.remote_user = 'authorized' self.remote_user = 'authorized'
self._pipeline_final_app = self self._pipeline_final_app = self
self.swift = FakeSwift(allowed_methods=['TEST']) self.swift = FakeSwift()
self.logger = debug_logger() self.logger = debug_logger()
def _update_s3_path_info(self, env): def _update_s3_path_info(self, env):
@ -281,10 +281,6 @@ class S3ApiTestCaseAcl(S3ApiTestCase):
self.swift.register('HEAD', path, swob.HTTPNoContent, {}, None), self.swift.register('HEAD', path, swob.HTTPNoContent, {}, None),
self.swift.register('GET', path, swob.HTTPOk, {}, json.dumps([])), self.swift.register('GET', path, swob.HTTPOk, {}, json.dumps([])),
for account in ('AUTH_test', 'AUTH_X'):
self.swift.register('TEST', '/v1/' + account,
swob.HTTPMethodNotAllowed, {}, None)
# setup sticky ACL headers... # setup sticky ACL headers...
grants = [_gen_grant(perm) for perm in PERMISSIONS] grants = [_gen_grant(perm) for perm in PERMISSIONS]
self.default_owner = Owner('test:tester', 'test:tester') self.default_owner = Owner('test:tester', 'test:tester')

View File

@ -1440,7 +1440,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
def test_s3api_with_only_s3_token(self): def test_s3api_with_only_s3_token(self):
self.swift = FakeSwift(allowed_methods=['TEST']) self.swift = FakeSwift()
self.keystone_auth = KeystoneAuth( self.keystone_auth = KeystoneAuth(
self.swift, {'operator_roles': 'swift-user'}) self.swift, {'operator_roles': 'swift-user'})
self.s3_token = S3Token( self.s3_token = S3Token(
@ -1470,7 +1470,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
req.environ['swift.backend_path']) req.environ['swift.backend_path'])
def test_s3api_with_only_s3_token_v3(self): def test_s3api_with_only_s3_token_v3(self):
self.swift = FakeSwift(allowed_methods=['TEST']) self.swift = FakeSwift()
self.keystone_auth = KeystoneAuth( self.keystone_auth = KeystoneAuth(
self.swift, {'operator_roles': 'swift-user'}) self.swift, {'operator_roles': 'swift-user'})
self.s3_token = S3Token( self.s3_token = S3Token(
@ -1500,7 +1500,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
req.environ['swift.backend_path']) req.environ['swift.backend_path'])
def test_s3api_with_s3_token_and_auth_token(self): def test_s3api_with_s3_token_and_auth_token(self):
self.swift = FakeSwift(allowed_methods=['TEST']) self.swift = FakeSwift()
self.keystone_auth = KeystoneAuth( self.keystone_auth = KeystoneAuth(
self.swift, {'operator_roles': 'swift-user'}) self.swift, {'operator_roles': 'swift-user'})
self.auth_token = AuthProtocol( self.auth_token = AuthProtocol(
@ -1555,7 +1555,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
statsd_client.get_increment_counts()) statsd_client.get_increment_counts())
def test_s3api_with_only_s3_token_in_s3acl(self): def test_s3api_with_only_s3_token_in_s3acl(self):
self.swift = FakeSwift(allowed_methods=['TEST']) self.swift = FakeSwift()
self.keystone_auth = KeystoneAuth( self.keystone_auth = KeystoneAuth(
self.swift, {'operator_roles': 'swift-user'}) self.swift, {'operator_roles': 'swift-user'})
self.s3_token = S3Token( self.s3_token = S3Token(
@ -1575,8 +1575,6 @@ class TestS3ApiMiddleware(S3ApiTestCase):
# after PUT container so we need to register the resposne here # after PUT container so we need to register the resposne here
self.swift.register('POST', '/v1/AUTH_TENANT_ID/bucket', self.swift.register('POST', '/v1/AUTH_TENANT_ID/bucket',
swob.HTTPNoContent, {}, None) swob.HTTPNoContent, {}, None)
self.swift.register('TEST', '/v1/AUTH_TENANT_ID',
swob.HTTPMethodNotAllowed, {}, None)
with patch.object(self.s3_token, '_json_request') as mock_req: with patch.object(self.s3_token, '_json_request') as mock_req:
mock_resp = requests.Response() mock_resp = requests.Response()
mock_resp._content = json.dumps(GOOD_RESPONSE_V2).encode('ascii') mock_resp._content = json.dumps(GOOD_RESPONSE_V2).encode('ascii')

View File

@ -17,7 +17,7 @@ import unittest
from swift.common.storage_policy import POLICIES from swift.common.storage_policy import POLICIES
from swift.common.swob import Request, HTTPOk, HTTPNotFound, \ from swift.common.swob import Request, HTTPOk, HTTPNotFound, \
HTTPCreated, HeaderKeyDict, HTTPException HTTPCreated, HeaderKeyDict
from swift.common import request_helpers as rh from swift.common import request_helpers as rh
from swift.common.middleware.s3api.utils import sysmeta_header from swift.common.middleware.s3api.utils import sysmeta_header
from test.unit.common.middleware.helpers import FakeSwift from test.unit.common.middleware.helpers import FakeSwift
@ -26,29 +26,19 @@ from test.unit.common.middleware.helpers import FakeSwift
class TestFakeSwift(unittest.TestCase): class TestFakeSwift(unittest.TestCase):
def test_allowed_methods(self): def test_allowed_methods(self):
def assert_allowed(swift, method): def do_test(swift, method, exp_status):
path = '/v1/a/c/o' path = '/v1/a/c/o'
swift.register(method, path, HTTPOk, {}, None) swift.register(method, path, HTTPOk, {}, None)
req = Request.blank(path) req = Request.blank(path)
req.method = method req.method = method
self.assertEqual(200, req.get_response(swift).status_int) self.assertEqual(exp_status, req.get_response(swift).status_int)
def assert_disallowed(swift, method):
path = '/v1/a/c/o'
swift.register(method, path, HTTPOk, {}, None)
req = Request.blank(path)
req.method = method
with self.assertRaises(HTTPException) as cm:
req.get_response(swift)
self.assertEqual(501, cm.exception.status_int)
for method in ('PUT', 'POST', 'DELETE', 'GET', 'HEAD', 'OPTIONS', for method in ('PUT', 'POST', 'DELETE', 'GET', 'HEAD', 'OPTIONS',
'REPLICATE', 'SSYNC', 'UPDATE'): 'REPLICATE', 'SSYNC', 'UPDATE'):
assert_allowed(FakeSwift(), method) do_test(FakeSwift(), method, 200)
assert_allowed(FakeSwift(allowed_methods=['TEST']), 'TEST')
assert_disallowed(FakeSwift(), 'TEST') do_test(FakeSwift(), 'TEST', 405)
assert_allowed(FakeSwift(allowed_methods=['TEST']), 'TEST') do_test(FakeSwift(), 'get', 405)
def test_not_registered(self): def test_not_registered(self):
swift = FakeSwift() swift = FakeSwift()