365db20275
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
824 lines
35 KiB
Python
824 lines
35 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (c) 2023 OpenStack Foundation
|
|
#
|
|
# 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.common.storage_policy import POLICIES
|
|
from swift.common.swob import Request, HTTPOk, HTTPNotFound, \
|
|
HTTPCreated, HeaderKeyDict
|
|
from swift.common import request_helpers as rh
|
|
from swift.common.middleware.s3api.utils import sysmeta_header
|
|
from test.unit.common.middleware.helpers import FakeSwift
|
|
|
|
|
|
class TestFakeSwift(unittest.TestCase):
|
|
def test_allowed_methods(self):
|
|
|
|
def do_test(swift, method, exp_status):
|
|
path = '/v1/a/c/o'
|
|
swift.register(method, path, HTTPOk, {}, None)
|
|
req = Request.blank(path)
|
|
req.method = method
|
|
self.assertEqual(exp_status, req.get_response(swift).status_int)
|
|
|
|
for method in ('PUT', 'POST', 'DELETE', 'GET', 'HEAD', 'OPTIONS',
|
|
'REPLICATE', 'SSYNC', 'UPDATE'):
|
|
do_test(FakeSwift(), method, 200)
|
|
|
|
do_test(FakeSwift(), 'TEST', 405)
|
|
do_test(FakeSwift(), 'get', 405)
|
|
|
|
def test_not_registered(self):
|
|
swift = FakeSwift()
|
|
|
|
def do_test(method):
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = method
|
|
with self.assertRaises(KeyError):
|
|
req.get_response(swift)
|
|
|
|
do_test('GET')
|
|
do_test('HEAD')
|
|
do_test('POST')
|
|
do_test('PUT')
|
|
do_test('DELETE')
|
|
|
|
def test_GET_registered(self):
|
|
# verify that a single registered GET response is sufficient to handle
|
|
# GETs and HEADS, with and without query strings
|
|
swift = FakeSwift()
|
|
swift.register('GET', '/v1/a/c/o', HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'X-Foo': 'Bar'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(1, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
req.query_string = 'p=q'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'X-Foo': 'Bar'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(2, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o?p=q'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'HEAD'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8',
|
|
'X-Foo': 'Bar'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(3, swift.call_count)
|
|
self.assertEqual(('HEAD', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'HEAD'
|
|
req.query_string = 'p=q'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8',
|
|
'X-Foo': 'Bar'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(4, swift.call_count)
|
|
self.assertEqual(('HEAD', '/v1/a/c/o?p=q'), swift.calls[-1])
|
|
|
|
def test_GET_registered_with_query_string(self):
|
|
# verify that a single registered GET response is sufficient to handle
|
|
# GETs and HEADS, with and without query strings
|
|
swift = FakeSwift()
|
|
swift.register('GET', '/v1/a/c/o?p=q', HTTPOk,
|
|
{'X-Foo': 'Bar'}, b'stuff')
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
with self.assertRaises(KeyError):
|
|
resp = req.get_response(swift)
|
|
|
|
req.query_string = 'p=q'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'X-Foo': 'Bar'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(1, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o?p=q'), swift.calls[-1])
|
|
|
|
def test_GET_and_HEAD_registered(self):
|
|
# verify that a registered HEAD response will be preferred over GET for
|
|
# HEAD request
|
|
swift = FakeSwift()
|
|
swift.register('GET', '/v1/a/c/o', HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
|
|
swift.register('HEAD', '/v1/a/c/o', HTTPNotFound, {}, b'')
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'X-Foo': 'Bar'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(1, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'HEAD'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(404, resp.status_int)
|
|
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(2, swift.call_count)
|
|
self.assertEqual(('HEAD', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'HEAD'
|
|
req.query_string = 'p=q'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(404, resp.status_int)
|
|
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(3, swift.call_count)
|
|
self.assertEqual(('HEAD', '/v1/a/c/o?p=q'), swift.calls[-1])
|
|
|
|
def test_PUT_uploaded(self):
|
|
# verify an uploaded object is sufficient to handle GETs and HEADS,
|
|
# with and without query strings
|
|
swift = FakeSwift()
|
|
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
|
|
req = Request.blank('/v1/a/c/o', body=b'stuff')
|
|
req.method = 'PUT'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(201, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
|
|
'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(1, swift.call_count)
|
|
self.assertEqual(('PUT', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'Host': 'localhost:80'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(2, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
req.query_string = 'p=q'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'Host': 'localhost:80'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(3, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o?p=q'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'HEAD'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'Host': 'localhost:80'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(4, swift.call_count)
|
|
self.assertEqual(('HEAD', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'HEAD'
|
|
req.query_string = 'p=q'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'Host': 'localhost:80'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(5, swift.call_count)
|
|
self.assertEqual(('HEAD', '/v1/a/c/o?p=q'), swift.calls[-1])
|
|
|
|
def test_PUT_uploaded_with_query_string(self):
|
|
# verify an uploaded object with query string is sufficient to handle
|
|
# GETs and HEADS, with and without query strings
|
|
swift = FakeSwift()
|
|
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
|
|
req = Request.blank('/v1/a/c/o', body=b'stuff')
|
|
req.method = 'PUT'
|
|
req.query_string = 'multipart-manifest=put'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(201, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
|
|
'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(1, swift.call_count)
|
|
self.assertEqual(('PUT', '/v1/a/c/o?multipart-manifest=put'),
|
|
swift.calls[-1])
|
|
# note: query string is not included in uploaded key
|
|
self.assertEqual(
|
|
{'/v1/a/c/o': ({'Host': 'localhost:80',
|
|
'Content-Length': '5'},
|
|
b'stuff')},
|
|
swift.uploaded)
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'Host': 'localhost:80'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(2, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
req.query_string = 'p=q' # note: differs from PUT query string
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'Host': 'localhost:80'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(3, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o?p=q'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'HEAD'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'Host': 'localhost:80'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(4, swift.call_count)
|
|
self.assertEqual(('HEAD', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'HEAD'
|
|
req.query_string = 'p=q'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'Host': 'localhost:80'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(5, swift.call_count)
|
|
self.assertEqual(('HEAD', '/v1/a/c/o?p=q'), swift.calls[-1])
|
|
|
|
def test_PUT_POST(self):
|
|
# verify an uploaded object is updated by a POST
|
|
swift = FakeSwift()
|
|
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
|
|
# Note: the POST must be registered
|
|
swift.register('POST', '/v1/a/c/o', HTTPCreated, {}, None)
|
|
req = Request.blank('/v1/a/c/o', body=b'stuff',
|
|
headers={'X-Object-Meta-Foo': 'Bar'})
|
|
req.method = 'PUT'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(201, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
|
|
'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(1, swift.call_count)
|
|
self.assertEqual(('PUT', '/v1/a/c/o'), swift.calls[-1])
|
|
self.assertEqual(
|
|
{'/v1/a/c/o': ({'Host': 'localhost:80',
|
|
'Content-Length': '5',
|
|
'X-Object-Meta-Foo': 'Bar'},
|
|
b'stuff')},
|
|
swift.uploaded)
|
|
|
|
# POST should update the uploaded object
|
|
req = Request.blank('/v1/a/c/o', body=b'stuff',
|
|
headers={'X-Object-Meta-Foo': 'Baz'})
|
|
req.method = 'POST'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(201, resp.status_int)
|
|
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(2, swift.call_count)
|
|
self.assertEqual(('POST', '/v1/a/c/o'), swift.calls[-1])
|
|
self.assertEqual(
|
|
{'/v1/a/c/o': ({'Host': 'localhost:80',
|
|
'Content-Length': '5',
|
|
'X-Object-Meta-Foo': 'Baz'},
|
|
b'stuff')},
|
|
swift.uploaded)
|
|
|
|
def test_PUT_with_query_string_POST(self):
|
|
# verify an uploaded object with query string is updated by a POST
|
|
swift = FakeSwift()
|
|
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
|
|
# Note: the POST must be registered
|
|
swift.register('POST', '/v1/a/c/o', HTTPCreated, {}, None)
|
|
req = Request.blank('/v1/a/c/o', body=b'stuff',
|
|
headers={'X-Object-Meta-Foo': 'Bar'})
|
|
req.method = 'PUT'
|
|
req.query_string = 'p=q'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(201, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
|
|
'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(1, swift.call_count)
|
|
self.assertEqual(('PUT', '/v1/a/c/o?p=q'), swift.calls[-1])
|
|
# note: query string is not included in uploaded key
|
|
self.assertEqual(
|
|
{'/v1/a/c/o': ({'Host': 'localhost:80',
|
|
'Content-Length': '5',
|
|
'X-Object-Meta-Foo': 'Bar'},
|
|
b'stuff')},
|
|
swift.uploaded)
|
|
|
|
# POST without query string should update the uploaded object
|
|
req = Request.blank('/v1/a/c/o', body=b'stuff',
|
|
headers={'X-Object-Meta-Foo': 'Baz'})
|
|
req.method = 'POST'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(201, resp.status_int)
|
|
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(2, swift.call_count)
|
|
self.assertEqual(('POST', '/v1/a/c/o'), swift.calls[-1])
|
|
self.assertEqual(
|
|
{'/v1/a/c/o': ({'Host': 'localhost:80',
|
|
'Content-Length': '5',
|
|
'X-Object-Meta-Foo': 'Baz'},
|
|
b'stuff')},
|
|
swift.uploaded)
|
|
|
|
# POST with different query string should update the uploaded object
|
|
req = Request.blank('/v1/a/c/o', body=b'stuff',
|
|
headers={'X-Object-Meta-Foo': 'Bof'})
|
|
req.method = 'POST'
|
|
req.query_string = 'x=y'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(201, resp.status_int)
|
|
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(3, swift.call_count)
|
|
self.assertEqual(('POST', '/v1/a/c/o?x=y'), swift.calls[-1])
|
|
self.assertEqual(
|
|
{'/v1/a/c/o': ({'Host': 'localhost:80',
|
|
'Content-Length': '5',
|
|
'X-Object-Meta-Foo': 'Bof'},
|
|
b'stuff')},
|
|
swift.uploaded)
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8',
|
|
'Host': 'localhost:80',
|
|
'X-Object-Meta-Foo': 'Bof'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(4, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
def test_GET_registered_overrides_uploaded(self):
|
|
swift = FakeSwift()
|
|
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
|
|
swift.register('GET', '/v1/a/c/o', HTTPOk, {}, b'not stuff')
|
|
|
|
req = Request.blank('/v1/a/c/o', body=b'stuff',
|
|
headers={'X-Object-Meta-Foo': 'Bar'})
|
|
req.method = 'PUT'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(201, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
|
|
'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'', resp.body)
|
|
self.assertEqual(1, swift.call_count)
|
|
self.assertEqual(('PUT', '/v1/a/c/o'), swift.calls[-1])
|
|
self.assertEqual(
|
|
{'/v1/a/c/o': ({'Host': 'localhost:80',
|
|
'Content-Length': '5',
|
|
'X-Object-Meta-Foo': 'Bar'},
|
|
b'stuff')},
|
|
swift.uploaded)
|
|
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '9',
|
|
'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'not stuff', resp.body)
|
|
self.assertEqual(2, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
|
|
|
|
def test_range(self):
|
|
swift = FakeSwift()
|
|
swift.register('GET', '/v1/a/c/o', HTTPOk, {}, b'stuff')
|
|
req = Request.blank('/v1/a/c/o', headers={'Range': 'bytes=0-2'})
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(206, resp.status_int)
|
|
self.assertEqual(b'stu', resp.body)
|
|
self.assertEqual('bytes 0-2/5', resp.headers['Content-Range'])
|
|
self.assertEqual('bytes=0-2', req.headers.get('Range'))
|
|
self.assertEqual('bytes=0-2',
|
|
swift.calls_with_headers[-1].headers.get('Range'))
|
|
|
|
def test_range_ignore_range_header(self):
|
|
swift = FakeSwift()
|
|
swift.register('GET', '/v1/a/c/o', HTTPOk, {
|
|
# the value of the matching header doesn't matter
|
|
'X-Object-Sysmeta-Magic': 'False'
|
|
}, b'stuff')
|
|
req = Request.blank('/v1/a/c/o', headers={'Range': 'bytes=0-2'})
|
|
rh.update_ignore_range_header(req, 'X-Object-Sysmeta-Magic')
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertNotIn('Content-Range', resp.headers)
|
|
self.assertEqual('bytes=0-2', req.headers.get('Range'))
|
|
self.assertEqual('bytes=0-2',
|
|
swift.calls_with_headers[-1].headers.get('Range'))
|
|
|
|
def test_range_ignore_range_header_old_swift(self):
|
|
swift = FakeSwift()
|
|
swift.can_ignore_range = False
|
|
swift.register('GET', '/v1/a/c/o', HTTPOk, {
|
|
# the value of the matching header doesn't matter
|
|
'X-Object-Sysmeta-Magic': 'False'
|
|
}, b'stuff')
|
|
req = Request.blank('/v1/a/c/o', headers={'Range': 'bytes=0-2'})
|
|
rh.update_ignore_range_header(req, 'X-Object-Sysmeta-Magic')
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(206, resp.status_int)
|
|
self.assertEqual(b'stu', resp.body)
|
|
self.assertEqual('bytes 0-2/5', resp.headers['Content-Range'])
|
|
self.assertEqual('bytes=0-2', req.headers.get('Range'))
|
|
self.assertEqual('bytes=0-2',
|
|
swift.calls_with_headers[-1].headers.get('Range'))
|
|
|
|
def test_range_ignore_range_header_ignored(self):
|
|
swift = FakeSwift()
|
|
# range is only ignored if registered response has matching metadata
|
|
swift.register('GET', '/v1/a/c/o', HTTPOk, {}, b'stuff')
|
|
req = Request.blank('/v1/a/c/o', headers={'Range': 'bytes=0-2'})
|
|
rh.update_ignore_range_header(req, 'X-Object-Sysmeta-Magic')
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(206, resp.status_int)
|
|
self.assertEqual(b'stu', resp.body)
|
|
self.assertEqual('bytes 0-2/5', resp.headers['Content-Range'])
|
|
self.assertEqual('bytes=0-2', req.headers.get('Range'))
|
|
self.assertEqual('bytes=0-2',
|
|
swift.calls_with_headers[-1].headers.get('Range'))
|
|
|
|
def test_object_GET_updated_with_storage_policy(self):
|
|
swift = FakeSwift()
|
|
swift.register('GET', '/v1/a/c/o', HTTPOk, {}, body=b'stuff')
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
self.assertNotIn('X-Backend-Storage-Policy-Index', req.headers)
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(1, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[0])
|
|
self.assertEqual(('GET', '/v1/a/c/o',
|
|
{'Host': 'localhost:80'}), # from swob
|
|
swift.calls_with_headers[0])
|
|
# default storage policy is applied...
|
|
self.assertEqual(str(int(POLICIES.default)),
|
|
req.headers.get('X-Backend-Storage-Policy-Index'))
|
|
|
|
# register a container with storage policy 99...
|
|
swift.register('HEAD', '/v1/a/c', HTTPOk,
|
|
{'X-Backend-Storage-Policy-Index': '99'}, None)
|
|
req = Request.blank('/v1/a/c/o')
|
|
req.method = 'GET'
|
|
self.assertNotIn('X-Backend-Storage-Policy-Index', req.headers)
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual({'Content-Length': '5',
|
|
'Content-Type': 'text/html; charset=UTF-8'},
|
|
resp.headers)
|
|
self.assertEqual(b'stuff', resp.body)
|
|
self.assertEqual(2, swift.call_count)
|
|
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[1])
|
|
self.assertEqual(('GET', '/v1/a/c/o',
|
|
{'Host': 'localhost:80'}), # from swob
|
|
swift.calls_with_headers[1])
|
|
self.assertEqual(
|
|
'99', req.headers.get('X-Backend-Storage-Policy-Index'))
|
|
|
|
|
|
class TestFakeSwiftMultipleResponses(unittest.TestCase):
|
|
|
|
def test_register_response_is_forever(self):
|
|
swift = FakeSwift()
|
|
swift.register('GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
|
|
req = Request.blank('/v1/a/c/o')
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Bar', resp.headers['X-Foo'])
|
|
# you can get this response as much as you want
|
|
for i in range(10):
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Bar', resp.headers['X-Foo'])
|
|
|
|
def test_register_response_is_last_response_wins(self):
|
|
swift = FakeSwift()
|
|
swift.register('GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
|
|
req = Request.blank('/v1/a/c/o')
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Bar', resp.headers['X-Foo'])
|
|
|
|
swift.register('GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Baz'}, b'other')
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Baz', resp.headers['X-Foo'])
|
|
# you can get this new response as much as you want
|
|
for i in range(10):
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Baz', resp.headers['X-Foo'])
|
|
|
|
def test_register_next_response_is_last_response_wins(self):
|
|
swift = FakeSwift()
|
|
swift.register(
|
|
'GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
|
|
swift.register_next_response(
|
|
'GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Baz'}, b'other')
|
|
req = Request.blank('/v1/a/c/o')
|
|
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Bar', resp.headers['X-Foo'])
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Baz', resp.headers['X-Foo'])
|
|
# you can get this new response as much as you want
|
|
for i in range(10):
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Baz', resp.headers['X-Foo'])
|
|
|
|
def test_register_next_response_keeps_current_registered_response(self):
|
|
# we expect test authors will typically 'd register ALL their responses
|
|
# before you start calling FakeSwift
|
|
swift = FakeSwift()
|
|
swift.register(
|
|
'GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
|
|
req = Request.blank('/v1/a/c/o')
|
|
|
|
# we get the registered response, obviously
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Bar', resp.headers['X-Foo'])
|
|
|
|
# because before calling register_next_response, no resp are consumed
|
|
swift.register_next_response(
|
|
'GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Baz'}, b'other')
|
|
|
|
# so, this is the "current" response, not the *next* response
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Bar', resp.headers['X-Foo'])
|
|
|
|
# the *next* response is the next response
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Baz', resp.headers['X-Foo'])
|
|
|
|
def test_register_next_response_first(self):
|
|
# you can just use register_next_response
|
|
swift = FakeSwift()
|
|
swift.register_next_response(
|
|
'GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
|
|
swift.register_next_response(
|
|
'GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Baz'}, b'other')
|
|
req = Request.blank('/v1/a/c/o')
|
|
|
|
# it works just like you'd called register
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Bar', resp.headers['X-Foo'])
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Baz', resp.headers['X-Foo'])
|
|
# you can get this new response as much as you want
|
|
for i in range(10):
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Baz', resp.headers['X-Foo'])
|
|
|
|
def test_register_resets(self):
|
|
swift = FakeSwift()
|
|
swift.register_next_response(
|
|
'GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
|
|
req = Request.blank('/v1/a/c/o')
|
|
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Bar', resp.headers['X-Foo'])
|
|
# you can get this response as much as you want
|
|
for i in range(10):
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Bar', resp.headers['X-Foo'])
|
|
|
|
# if you call register mid test you immediately reset the resp
|
|
swift.register(
|
|
'GET', '/v1/a/c/o',
|
|
HTTPOk, {'X-Foo': 'Baz'}, b'other')
|
|
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Baz', resp.headers['X-Foo'])
|
|
# you can get this new response as much as you want
|
|
for i in range(10):
|
|
resp = req.get_response(swift)
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual('Baz', resp.headers['X-Foo'])
|
|
|
|
|
|
class TestFakeSwiftStickyHeaders(unittest.TestCase):
|
|
def setUp(self):
|
|
self.swift = FakeSwift()
|
|
self.path = '/v1/AUTH_test/bucket'
|
|
|
|
def _check_headers(self, method, path, exp_headers):
|
|
captured_headers = {}
|
|
|
|
def start_response(status, resp_headers):
|
|
self.assertEqual(status, '200 OK')
|
|
captured_headers.update(resp_headers)
|
|
|
|
env = {'REQUEST_METHOD': method, 'PATH_INFO': path}
|
|
body_iter = self.swift(env, start_response)
|
|
b''.join(body_iter)
|
|
captured_headers.pop('Content-Type')
|
|
self.assertEqual(exp_headers, captured_headers)
|
|
|
|
def test_sticky_headers(self):
|
|
sticky_headers = HeaderKeyDict({
|
|
sysmeta_header('container', 'acl'): 'test',
|
|
'x-container-meta-foo': 'bar',
|
|
})
|
|
self.swift.update_sticky_response_headers(self.path, sticky_headers)
|
|
# register a response for this path with no headers
|
|
self.swift.register('GET', self.path, HTTPOk, {}, None)
|
|
self._check_headers('HEAD', self.path, sticky_headers)
|
|
self._check_headers('GET', self.path, sticky_headers)
|
|
|
|
# sticky headers are not applied to PUT, POST, DELETE
|
|
self.swift.register('PUT', self.path, HTTPOk, {}, None)
|
|
self._check_headers('PUT', self.path, {})
|
|
self.swift.register('POST', self.path, HTTPOk, {}, None)
|
|
self._check_headers('POST', self.path, {})
|
|
self.swift.register('DELETE', self.path, HTTPOk, {}, None)
|
|
self._check_headers('DELETE', self.path, {})
|
|
|
|
def test_sticky_headers_match_path(self):
|
|
other_path = self.path + '-other'
|
|
sticky_headers = HeaderKeyDict({
|
|
sysmeta_header('container', 'acl'): 'test',
|
|
'x-container-meta-foo': 'bar',
|
|
})
|
|
sticky_headers_other = HeaderKeyDict({
|
|
'x-container-meta-foo': 'other',
|
|
})
|
|
self.swift.update_sticky_response_headers(self.path, sticky_headers)
|
|
self.swift.update_sticky_response_headers(other_path,
|
|
sticky_headers_other)
|
|
self.swift.register('GET', self.path, HTTPOk, {}, None)
|
|
self.swift.register('GET', other_path, HTTPOk, {}, None)
|
|
self._check_headers('HEAD', self.path, sticky_headers)
|
|
self._check_headers('GET', other_path, sticky_headers_other)
|
|
|
|
def test_sticky_headers_update(self):
|
|
sticky_headers = HeaderKeyDict({
|
|
sysmeta_header('container', 'acl'): 'test',
|
|
'x-container-meta-foo': 'bar'
|
|
})
|
|
exp_headers = sticky_headers.copy()
|
|
self.swift.update_sticky_response_headers(self.path, sticky_headers)
|
|
self.swift.register('HEAD', self.path, HTTPOk, {}, None)
|
|
self._check_headers('HEAD', self.path, exp_headers)
|
|
|
|
# check that FakeSwift made a *copy*
|
|
sticky_headers['x-container-meta-foo'] = 'changed'
|
|
self._check_headers('HEAD', self.path, exp_headers)
|
|
|
|
# check existing are updated not replaced
|
|
sticky_headers = HeaderKeyDict({
|
|
sysmeta_header('container', 'acl'): 'test-modified',
|
|
'x-container-meta-bar': 'foo'
|
|
})
|
|
exp_headers.update(sticky_headers)
|
|
self.swift.update_sticky_response_headers(self.path, sticky_headers)
|
|
self._check_headers('HEAD', self.path, exp_headers)
|
|
|
|
def test_sticky_headers_add_to_response_headers(self):
|
|
sticky_headers = HeaderKeyDict({
|
|
'x-container-meta-foo': 'bar',
|
|
})
|
|
self.swift.update_sticky_response_headers(self.path, sticky_headers)
|
|
# register a response with another header
|
|
self.swift.register('HEAD', self.path, HTTPOk, {
|
|
'x-backend-storage-policy-index': '1',
|
|
}, None)
|
|
self._check_headers('HEAD', self.path, HeaderKeyDict({
|
|
'x-container-meta-foo': 'bar',
|
|
'x-backend-storage-policy-index': '1',
|
|
}))
|
|
|
|
def test_sticky_headers_overwritten_by_response_header(self):
|
|
sticky_headers = HeaderKeyDict({
|
|
'x-container-meta-foo': 'bar',
|
|
'x-backend-storage-policy-index': '0',
|
|
})
|
|
self.swift.update_sticky_response_headers(self.path, sticky_headers)
|
|
# register a response with a different value for a sticky header
|
|
self.swift.register('HEAD', self.path, HTTPOk, {
|
|
'x-container-meta-foo': 'different',
|
|
}, None)
|
|
self._check_headers('HEAD', self.path, HeaderKeyDict({
|
|
'x-container-meta-foo': 'different',
|
|
'x-backend-storage-policy-index': '0',
|
|
}))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|