Add additional headers for HEAD/GET/DELETE requests.

Change-Id: I69276ba711057c122f97deac412e492e313c34dd
Closes-Bug: 1615830
This commit is contained in:
Charles Hsu 2016-09-19 23:18:18 +08:00
parent 0ec6b7b162
commit 6cf2bd6626
8 changed files with 426 additions and 97 deletions

View File

@ -696,7 +696,7 @@ def store_response(resp, response_dict):
def get_account(url, token, marker=None, limit=None, prefix=None, def get_account(url, token, marker=None, limit=None, prefix=None,
end_marker=None, http_conn=None, full_listing=False, end_marker=None, http_conn=None, full_listing=False,
service_token=None): service_token=None, headers=None):
""" """
Get a listing of containers for the account. Get a listing of containers for the account.
@ -711,20 +711,28 @@ def get_account(url, token, marker=None, limit=None, prefix=None,
:param full_listing: if True, return a full listing, else returns a max :param full_listing: if True, return a full listing, else returns a max
of 10000 listings of 10000 listings
:param service_token: service auth token :param service_token: service auth token
:param headers: additional headers to include in the request
:returns: a tuple of (response headers, a list of containers) The response :returns: a tuple of (response headers, a list of containers) The response
headers will be a dict and all header names will be lowercase. headers will be a dict and all header names will be lowercase.
:raises ClientException: HTTP GET request failed :raises ClientException: HTTP GET request failed
""" """
req_headers = {'X-Auth-Token': token, 'Accept-Encoding': 'gzip'}
if service_token:
req_headers['X-Service-Token'] = service_token
if headers:
req_headers.update(headers)
if not http_conn: if not http_conn:
http_conn = http_connection(url) http_conn = http_connection(url)
if full_listing: if full_listing:
rv = get_account(url, token, marker, limit, prefix, rv = get_account(url, token, marker, limit, prefix,
end_marker, http_conn) end_marker, http_conn, headers=req_headers)
listing = rv[1] listing = rv[1]
while listing: while listing:
marker = listing[-1]['name'] marker = listing[-1]['name']
listing = get_account(url, token, marker, limit, prefix, listing = get_account(url, token, marker, limit, prefix,
end_marker, http_conn)[1] end_marker, http_conn,
headers=req_headers)[1]
if listing: if listing:
rv[1].extend(listing) rv[1].extend(listing)
return rv return rv
@ -739,14 +747,12 @@ def get_account(url, token, marker=None, limit=None, prefix=None,
if end_marker: if end_marker:
qs += '&end_marker=%s' % quote(end_marker) qs += '&end_marker=%s' % quote(end_marker)
full_path = '%s?%s' % (parsed.path, qs) full_path = '%s?%s' % (parsed.path, qs)
headers = {'X-Auth-Token': token, 'Accept-Encoding': 'gzip'}
if service_token:
headers['X-Service-Token'] = service_token
method = 'GET' method = 'GET'
conn.request(method, full_path, '', headers) conn.request(method, full_path, '', req_headers)
resp = conn.getresponse() resp = conn.getresponse()
body = resp.read() body = resp.read()
http_log(("%s?%s" % (url, qs), method,), {'headers': headers}, resp, body) http_log(("%s?%s" % (url, qs), method,), {'headers': req_headers},
resp, body)
resp_headers = resp_header_dict(resp) resp_headers = resp_header_dict(resp)
if resp.status < 200 or resp.status >= 300: if resp.status < 200 or resp.status >= 300:
@ -756,7 +762,8 @@ def get_account(url, token, marker=None, limit=None, prefix=None,
return resp_headers, parse_api_response(resp_headers, body) return resp_headers, parse_api_response(resp_headers, body)
def head_account(url, token, http_conn=None, service_token=None): def head_account(url, token, http_conn=None, headers=None,
service_token=None):
""" """
Get account stats. Get account stats.
@ -764,6 +771,7 @@ def head_account(url, token, http_conn=None, service_token=None):
:param token: auth token :param token: auth token
:param http_conn: a tuple of (parsed url, HTTPConnection object), :param http_conn: a tuple of (parsed url, HTTPConnection object),
(If None, it will create the conn object) (If None, it will create the conn object)
:param headers: additional headers to include in the request
:param service_token: service auth token :param service_token: service auth token
:returns: a dict containing the response's headers (all header names will :returns: a dict containing the response's headers (all header names will
be lowercase) be lowercase)
@ -774,13 +782,16 @@ def head_account(url, token, http_conn=None, service_token=None):
else: else:
parsed, conn = http_connection(url) parsed, conn = http_connection(url)
method = "HEAD" method = "HEAD"
headers = {'X-Auth-Token': token} req_headers = {'X-Auth-Token': token}
if service_token: if service_token:
headers['X-Service-Token'] = service_token req_headers['X-Service-Token'] = service_token
conn.request(method, parsed.path, '', headers) if headers:
req_headers.update(headers)
conn.request(method, parsed.path, '', req_headers)
resp = conn.getresponse() resp = conn.getresponse()
body = resp.read() body = resp.read()
http_log((url, method,), {'headers': headers}, resp, body) http_log((url, method,), {'headers': req_headers}, resp, body)
if resp.status < 200 or resp.status >= 300: if resp.status < 200 or resp.status >= 300:
raise ClientException.from_response(resp, 'Account HEAD failed', body) raise ClientException.from_response(resp, 'Account HEAD failed', body)
resp_headers = resp_header_dict(resp) resp_headers = resp_header_dict(resp)
@ -1047,7 +1058,7 @@ def post_container(url, token, container, headers, http_conn=None,
def delete_container(url, token, container, http_conn=None, def delete_container(url, token, container, http_conn=None,
response_dict=None, service_token=None, response_dict=None, service_token=None,
query_string=None): query_string=None, headers=None):
""" """
Delete a container Delete a container
@ -1060,6 +1071,7 @@ def delete_container(url, token, container, http_conn=None,
the response - status, reason and headers the response - status, reason and headers
:param service_token: service auth token :param service_token: service auth token
:param query_string: if set will be appended with '?' to generated path :param query_string: if set will be appended with '?' to generated path
:param headers: additional headers to include in the request
:raises ClientException: HTTP DELETE request failed :raises ClientException: HTTP DELETE request failed
""" """
if http_conn: if http_conn:
@ -1067,7 +1079,12 @@ def delete_container(url, token, container, http_conn=None,
else: else:
parsed, conn = http_connection(url) parsed, conn = http_connection(url)
path = '%s/%s' % (parsed.path, quote(container)) path = '%s/%s' % (parsed.path, quote(container))
headers = {'X-Auth-Token': token} if headers:
headers = dict(headers)
else:
headers = {}
headers['X-Auth-Token'] = token
if service_token: if service_token:
headers['X-Service-Token'] = service_token headers['X-Service-Token'] = service_token
if query_string: if query_string:
@ -1682,19 +1699,19 @@ class Connection(object):
if reset_func: if reset_func:
reset_func(func, *args, **kwargs) reset_func(func, *args, **kwargs)
def head_account(self): def head_account(self, headers=None):
"""Wrapper for :func:`head_account`""" """Wrapper for :func:`head_account`"""
return self._retry(None, head_account) return self._retry(None, head_account, headers=headers)
def get_account(self, marker=None, limit=None, prefix=None, def get_account(self, marker=None, limit=None, prefix=None,
end_marker=None, full_listing=False): end_marker=None, full_listing=False, headers=None):
"""Wrapper for :func:`get_account`""" """Wrapper for :func:`get_account`"""
# TODO(unknown): With full_listing=True this will restart the entire # TODO(unknown): With full_listing=True this will restart the entire
# listing with each retry. Need to make a better version that just # listing with each retry. Need to make a better version that just
# retries where it left off. # retries where it left off.
return self._retry(None, get_account, marker=marker, limit=limit, return self._retry(None, get_account, marker=marker, limit=limit,
prefix=prefix, end_marker=end_marker, prefix=prefix, end_marker=end_marker,
full_listing=full_listing) full_listing=full_listing, headers=headers)
def post_account(self, headers, response_dict=None, def post_account(self, headers, response_dict=None,
query_string=None, data=None): query_string=None, data=None):
@ -1733,11 +1750,12 @@ class Connection(object):
response_dict=response_dict) response_dict=response_dict)
def delete_container(self, container, response_dict=None, def delete_container(self, container, response_dict=None,
query_string=None): query_string=None, headers={}):
"""Wrapper for :func:`delete_container`""" """Wrapper for :func:`delete_container`"""
return self._retry(None, delete_container, container, return self._retry(None, delete_container, container,
response_dict=response_dict, response_dict=response_dict,
query_string=query_string) query_string=query_string,
headers=headers)
def head_object(self, container, obj, headers=None): def head_object(self, container, obj, headers=None):
"""Wrapper for :func:`head_object`""" """Wrapper for :func:`head_object`"""
@ -1808,11 +1826,12 @@ class Connection(object):
response_dict=response_dict) response_dict=response_dict)
def delete_object(self, container, obj, query_string=None, def delete_object(self, container, obj, query_string=None,
response_dict=None): response_dict=None, headers=None):
"""Wrapper for :func:`delete_object`""" """Wrapper for :func:`delete_object`"""
return self._retry(None, delete_object, container, obj, return self._retry(None, delete_object, container, obj,
query_string=query_string, query_string=query_string,
response_dict=response_dict) response_dict=response_dict,
headers=headers)
def get_capabilities(self, url=None): def get_capabilities(self, url=None):
url = url or self.url url = url or self.url

View File

@ -11,7 +11,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from swiftclient.utils import prt_bytes from swiftclient.utils import prt_bytes, split_request_headers
POLICY_HEADER_PREFIX = 'x-account-storage-policy-' POLICY_HEADER_PREFIX = 'x-account-storage-policy-'
@ -19,8 +19,9 @@ POLICY_HEADER_PREFIX = 'x-account-storage-policy-'
def stat_account(conn, options): def stat_account(conn, options):
items = [] items = []
req_headers = split_request_headers(options.get('header', []))
headers = conn.head_account() headers = conn.head_account(headers=req_headers)
if options['verbose'] > 1: if options['verbose'] > 1:
items.extend([ items.extend([
('StorageURL', conn.url), ('StorageURL', conn.url),
@ -91,8 +92,11 @@ def print_account_stats(items, headers, output_manager):
def stat_container(conn, options, container): def stat_container(conn, options, container):
headers = conn.head_container(container) req_headers = split_request_headers(options.get('header', []))
headers = conn.head_container(container, headers=req_headers)
items = [] items = []
if options['verbose'] > 1: if options['verbose'] > 1:
path = '%s/%s' % (conn.url, container) path = '%s/%s' % (conn.url, container)
items.extend([ items.extend([
@ -137,7 +141,9 @@ def print_container_stats(items, headers, output_manager):
def stat_object(conn, options, container, obj): def stat_object(conn, options, container, obj):
headers = conn.head_object(container, obj) req_headers = split_request_headers(options.get('header', []))
headers = conn.head_object(container, obj, headers=req_headers)
items = [] items = []
if options['verbose'] > 1: if options['verbose'] > 1:
path = '%s/%s/%s' % (conn.url, container, obj) path = '%s/%s/%s' % (conn.url, container, obj)

View File

@ -44,7 +44,7 @@ from swiftclient.command_helpers import (
) )
from swiftclient.utils import ( from swiftclient.utils import (
config_true_value, ReadableToIterable, LengthWrapper, EMPTY_ETAG, config_true_value, ReadableToIterable, LengthWrapper, EMPTY_ETAG,
parse_api_response, report_traceback, n_groups parse_api_response, report_traceback, n_groups, split_request_headers
) )
from swiftclient.exceptions import ClientException from swiftclient.exceptions import ClientException
from swiftclient.multithreading import MultiThreadingManager from swiftclient.multithreading import MultiThreadingManager
@ -279,15 +279,11 @@ def split_headers(options, prefix=''):
reporting. reporting.
""" """
headers = {} headers = {}
for item in options: try:
split_item = item.split(':', 1) headers = split_request_headers(options, prefix)
if len(split_item) == 2: except ValueError as e:
headers[(prefix + split_item[0]).title()] = split_item[1].strip() raise SwiftError(e)
else:
raise SwiftError(
"Metadata parameter %s must contain a ':'.\n%s"
% (item, "Example: 'Color:Blue' or 'Size:Large'")
)
return headers return headers
@ -467,7 +463,8 @@ class SwiftService(object):
performed by this call:: performed by this call::
{ {
'human': False 'human': False,
'header': []
} }
:returns: Either a single dictionary containing stats about an account :returns: Either a single dictionary containing stats about an account
@ -849,6 +846,7 @@ class SwiftService(object):
'long': False, 'long': False,
'prefix': None, 'prefix': None,
'delimiter': None, 'delimiter': None,
'header': []
} }
:returns: A generator for returning the results of the list operation :returns: A generator for returning the results of the list operation
@ -884,10 +882,12 @@ class SwiftService(object):
def _list_account_job(conn, options, result_queue): def _list_account_job(conn, options, result_queue):
marker = '' marker = ''
error = None error = None
req_headers = split_headers(options.get('header', []))
try: try:
while True: while True:
_, items = conn.get_account( _, items = conn.get_account(
marker=marker, prefix=options['prefix'] marker=marker, prefix=options['prefix'],
headers=req_headers
) )
if not items: if not items:
@ -943,11 +943,12 @@ class SwiftService(object):
def _list_container_job(conn, container, options, result_queue): def _list_container_job(conn, container, options, result_queue):
marker = options.get('marker', '') marker = options.get('marker', '')
error = None error = None
req_headers = split_headers(options.get('header', []))
try: try:
while True: while True:
_, items = conn.get_container( _, items = conn.get_container(
container, marker=marker, prefix=options['prefix'], container, marker=marker, prefix=options['prefix'],
delimiter=options['delimiter'] delimiter=options['delimiter'], headers=req_headers
) )
if not items: if not items:
@ -2112,6 +2113,7 @@ class SwiftService(object):
'yes_all': False, 'yes_all': False,
'leave_segments': False, 'leave_segments': False,
'prefix': None, 'prefix': None,
'header': [],
} }
:returns: A generator for returning the results of the delete :returns: A generator for returning the results of the delete
@ -2250,6 +2252,8 @@ class SwiftService(object):
def _delete_object(self, conn, container, obj, options, def _delete_object(self, conn, container, obj, options,
results_queue=None): results_queue=None):
_headers = {}
_headers = split_headers(options.get('header', []))
res = { res = {
'action': 'delete_object', 'action': 'delete_object',
'container': container, 'container': container,
@ -2261,7 +2265,8 @@ class SwiftService(object):
if not options['leave_segments']: if not options['leave_segments']:
try: try:
headers = conn.head_object(container, obj) headers = conn.head_object(container, obj,
headers=_headers)
old_manifest = headers.get('x-object-manifest') old_manifest = headers.get('x-object-manifest')
if config_true_value(headers.get('x-static-large-object')): if config_true_value(headers.get('x-static-large-object')):
query_string = 'multipart-manifest=delete' query_string = 'multipart-manifest=delete'
@ -2270,7 +2275,9 @@ class SwiftService(object):
raise raise
results_dict = {} results_dict = {}
conn.delete_object(container, obj, query_string=query_string, conn.delete_object(container, obj,
headers=_headers,
query_string=query_string,
response_dict=results_dict) response_dict=results_dict)
if old_manifest: if old_manifest:
@ -2322,10 +2329,13 @@ class SwiftService(object):
return res return res
@staticmethod @staticmethod
def _delete_empty_container(conn, container): def _delete_empty_container(conn, container, options):
results_dict = {} results_dict = {}
_headers = {}
_headers = split_headers(options.get('header', []))
try: try:
conn.delete_container(container, response_dict=results_dict) conn.delete_container(container, headers=_headers,
response_dict=results_dict)
res = {'success': True} res = {'success': True}
except Exception as err: except Exception as err:
traceback, err_time = report_traceback() traceback, err_time = report_traceback()
@ -2363,7 +2373,7 @@ class SwiftService(object):
return return
con_del = self.thread_manager.container_pool.submit( con_del = self.thread_manager.container_pool.submit(
self._delete_empty_container, container self._delete_empty_container, container, options
) )
con_del_res = get_future_result(con_del) con_del_res = get_future_result(con_del)

View File

@ -58,6 +58,7 @@ def immediate_exit(signum, frame):
st_delete_options = '''[--all] [--leave-segments] st_delete_options = '''[--all] [--leave-segments]
[--object-threads <threads>] [--object-threads <threads>]
[--container-threads <threads>] [--container-threads <threads>]
[--header <header:value>]
[<container> [<object>] [...]] [<container> [<object>] [...]]
''' '''
@ -72,6 +73,9 @@ Positional arguments:
Optional arguments: Optional arguments:
-a, --all Delete all containers and objects. -a, --all Delete all containers and objects.
--leave-segments Do not delete segments of manifest objects. --leave-segments Do not delete segments of manifest objects.
-H, --header <header:value>
Adds a custom request header to use for deleting
objects or an entire container .
--object-threads <threads> --object-threads <threads>
Number of threads to use for deleting objects. Number of threads to use for deleting objects.
Default is 10. Default is 10.
@ -88,6 +92,11 @@ def st_delete(parser, args, output_manager):
parser.add_argument( parser.add_argument(
'-p', '--prefix', dest='prefix', '-p', '--prefix', dest='prefix',
help='Only delete items beginning with the <prefix>.') help='Only delete items beginning with the <prefix>.')
parser.add_argument(
'-H', '--header', action='append', dest='header',
default=[],
help='Adds a custom request header to use for deleting objects '
'or an entire container.')
parser.add_argument( parser.add_argument(
'--leave-segments', action='store_true', '--leave-segments', action='store_true',
dest='leave_segments', default=False, dest='leave_segments', default=False,
@ -445,7 +454,8 @@ def st_download(parser, args, output_manager):
st_list_options = '''[--long] [--lh] [--totals] [--prefix <prefix>] st_list_options = '''[--long] [--lh] [--totals] [--prefix <prefix>]
[--delimiter <delimiter>] [<container>] [--delimiter <delimiter>] [--header <header:value>]
[<container>]
''' '''
st_list_help = ''' st_list_help = '''
@ -465,6 +475,8 @@ Optional arguments:
Roll up items with the given delimiter. For containers Roll up items with the given delimiter. For containers
only. See OpenStack Swift API documentation for what only. See OpenStack Swift API documentation for what
this means. this means.
-H, --header <header:value>
Adds a custom request header to use for listing.
'''.strip('\n') '''.strip('\n')
@ -541,6 +553,10 @@ def st_list(parser, args, output_manager):
help='Roll up items with the given delimiter. For containers ' help='Roll up items with the given delimiter. For containers '
'only. See OpenStack Swift API documentation for ' 'only. See OpenStack Swift API documentation for '
'what this means.') 'what this means.')
parser.add_argument(
'-H', '--header', action='append', dest='header',
default=[],
help='Adds a custom request header to use for listing.')
options, args = parse_args(parser, args) options, args = parse_args(parser, args)
args = args[1:] args = args[1:]
if options['delimiter'] and not args: if options['delimiter'] and not args:
@ -580,7 +596,7 @@ def st_list(parser, args, output_manager):
output_manager.error(e.value) output_manager.error(e.value)
st_stat_options = '''[--lh] st_stat_options = '''[--lh] [--header <header:value>]
[<container> [<object>]] [<container> [<object>]]
''' '''
@ -594,6 +610,8 @@ Positional arguments:
Optional arguments: Optional arguments:
--lh Report sizes in human readable format similar to --lh Report sizes in human readable format similar to
ls -lh. ls -lh.
-H, --header <header:value>
Adds a custom request header to use for stat.
'''.strip('\n') '''.strip('\n')
@ -601,6 +619,11 @@ def st_stat(parser, args, output_manager):
parser.add_argument( parser.add_argument(
'--lh', dest='human', action='store_true', default=False, '--lh', dest='human', action='store_true', default=False,
help='Report sizes in human readable format similar to ls -lh.') help='Report sizes in human readable format similar to ls -lh.')
parser.add_argument(
'-H', '--header', action='append', dest='header',
default=[],
help='Adds a custom request header to use for stat.')
options, args = parse_args(parser, args) options, args = parse_args(parser, args)
args = args[1:] args = args[1:]

View File

@ -146,6 +146,20 @@ def parse_api_response(headers, body):
return json.loads(body.decode(charset)) return json.loads(body.decode(charset))
def split_request_headers(options, prefix=''):
headers = {}
for item in options:
split_item = item.split(':', 1)
if len(split_item) == 2:
headers[(prefix + split_item[0]).title()] = split_item[1].strip()
else:
raise ValueError(
"Metadata parameter %s must contain a ':'.\n%s"
% (item, "Example: 'Color:Blue' or 'Size:Large'")
)
return headers
def report_traceback(): def report_traceback():
""" """
Reports a timestamp and full traceback for a given exception. Reports a timestamp and full traceback for a given exception.

View File

@ -291,9 +291,33 @@ class TestServiceDelete(_TestServiceBase):
s = SwiftService() s = SwiftService()
r = s._delete_object(mock_conn, 'test_c', 'test_o', self.opts, mock_q) r = s._delete_object(mock_conn, 'test_c', 'test_o', self.opts, mock_q)
mock_conn.head_object.assert_called_once_with('test_c', 'test_o') mock_conn.head_object.assert_called_once_with('test_c', 'test_o',
headers={})
mock_conn.delete_object.assert_called_once_with( mock_conn.delete_object.assert_called_once_with(
'test_c', 'test_o', query_string=None, response_dict={} 'test_c', 'test_o', query_string=None, response_dict={},
headers={}
)
self.assertEqual(expected_r, r)
def test_delete_object_with_headers(self):
mock_q = Queue()
mock_conn = self._get_mock_connection()
mock_conn.head_object = Mock(return_value={})
expected_r = self._get_expected({
'action': 'delete_object',
'success': True
})
opt_c = self.opts.copy()
opt_c['header'] = ['Skip-Middleware: Test']
s = SwiftService()
r = s._delete_object(mock_conn, 'test_c', 'test_o', opt_c, mock_q)
mock_conn.head_object.assert_called_once_with(
'test_c', 'test_o', headers={'Skip-Middleware': 'Test'})
mock_conn.delete_object.assert_called_once_with(
'test_c', 'test_o', query_string=None, response_dict={},
headers={'Skip-Middleware': 'Test'}
) )
self.assertEqual(expected_r, r) self.assertEqual(expected_r, r)
@ -317,9 +341,11 @@ class TestServiceDelete(_TestServiceBase):
r = s._delete_object(mock_conn, 'test_c', 'test_o', self.opts, mock_q) r = s._delete_object(mock_conn, 'test_c', 'test_o', self.opts, mock_q)
after = time.time() after = time.time()
mock_conn.head_object.assert_called_once_with('test_c', 'test_o') mock_conn.head_object.assert_called_once_with('test_c', 'test_o',
headers={})
mock_conn.delete_object.assert_called_once_with( mock_conn.delete_object.assert_called_once_with(
'test_c', 'test_o', query_string=None, response_dict={} 'test_c', 'test_o', query_string=None, response_dict={},
headers={}
) )
self.assertEqual(expected_r, r) self.assertEqual(expected_r, r)
self.assertGreaterEqual(r['error_timestamp'], before) self.assertGreaterEqual(r['error_timestamp'], before)
@ -342,11 +368,13 @@ class TestServiceDelete(_TestServiceBase):
s = SwiftService() s = SwiftService()
r = s._delete_object(mock_conn, 'test_c', 'test_o', self.opts, mock_q) r = s._delete_object(mock_conn, 'test_c', 'test_o', self.opts, mock_q)
mock_conn.head_object.assert_called_once_with('test_c', 'test_o') mock_conn.head_object.assert_called_once_with('test_c', 'test_o',
headers={})
mock_conn.delete_object.assert_called_once_with( mock_conn.delete_object.assert_called_once_with(
'test_c', 'test_o', 'test_c', 'test_o',
query_string='multipart-manifest=delete', query_string='multipart-manifest=delete',
response_dict={} response_dict={},
headers={}
) )
self.assertEqual(expected_r, r) self.assertEqual(expected_r, r)
@ -381,7 +409,8 @@ class TestServiceDelete(_TestServiceBase):
self.assertEqual(expected_r, r) self.assertEqual(expected_r, r)
expected = [ expected = [
mock.call('test_c', 'test_o', query_string=None, response_dict={}), mock.call('test_c', 'test_o', query_string=None, response_dict={},
headers={}),
mock.call('manifest_c', 'test_seg_1', response_dict={}), mock.call('manifest_c', 'test_seg_1', response_dict={}),
mock.call('manifest_c', 'test_seg_2', response_dict={})] mock.call('manifest_c', 'test_seg_2', response_dict={})]
mock_conn.delete_object.assert_has_calls(expected, any_order=True) mock_conn.delete_object.assert_has_calls(expected, any_order=True)
@ -394,10 +423,28 @@ class TestServiceDelete(_TestServiceBase):
'object': None 'object': None
}) })
r = SwiftService._delete_empty_container(mock_conn, 'test_c') r = SwiftService._delete_empty_container(mock_conn, 'test_c',
self.opts)
mock_conn.delete_container.assert_called_once_with( mock_conn.delete_container.assert_called_once_with(
'test_c', response_dict={} 'test_c', response_dict={}, headers={}
)
self.assertEqual(expected_r, r)
def test_delete_empty_container_with_headers(self):
mock_conn = self._get_mock_connection()
expected_r = self._get_expected({
'action': 'delete_container',
'success': True,
'object': None
})
opt_c = self.opts.copy()
opt_c['header'] = ['Skip-Middleware: Test']
r = SwiftService._delete_empty_container(mock_conn, 'test_c', opt_c)
mock_conn.delete_container.assert_called_once_with(
'test_c', response_dict={}, headers={'Skip-Middleware': 'Test'}
) )
self.assertEqual(expected_r, r) self.assertEqual(expected_r, r)
@ -415,11 +462,11 @@ class TestServiceDelete(_TestServiceBase):
before = time.time() before = time.time()
s = SwiftService() s = SwiftService()
r = s._delete_empty_container(mock_conn, 'test_c') r = s._delete_empty_container(mock_conn, 'test_c', {})
after = time.time() after = time.time()
mock_conn.delete_container.assert_called_once_with( mock_conn.delete_container.assert_called_once_with(
'test_c', response_dict={} 'test_c', response_dict={}, headers={}
) )
self.assertEqual(expected_r, r) self.assertEqual(expected_r, r)
self.assertGreaterEqual(r['error_timestamp'], before) self.assertGreaterEqual(r['error_timestamp'], before)
@ -665,6 +712,34 @@ class TestServiceList(_TestServiceBase):
self.assertEqual(expected_r_long, self._get_queue(mock_q)) self.assertEqual(expected_r_long, self._get_queue(mock_q))
self.assertIsNone(self._get_queue(mock_q)) self.assertIsNone(self._get_queue(mock_q))
def test_list_account_with_headers(self):
mock_q = Queue()
mock_conn = self._get_mock_connection()
get_account_returns = [
(None, [{'name': 'test_c'}]),
(None, [])
]
mock_conn.get_account = Mock(side_effect=get_account_returns)
expected_r = self._get_expected({
'action': 'list_account_part',
'success': True,
'listing': [{'name': 'test_c'}],
'marker': ''
})
opt_c = self.opts.copy()
opt_c['header'] = ['Skip-Middleware: True']
SwiftService._list_account_job(
mock_conn, opt_c, mock_q
)
self.assertEqual(expected_r, self._get_queue(mock_q))
self.assertIsNone(self._get_queue(mock_q))
self.assertEqual(mock_conn.get_account.mock_calls, [
mock.call(headers={'Skip-Middleware': 'True'}, marker='',
prefix=None),
mock.call(headers={'Skip-Middleware': 'True'}, marker='test_c',
prefix=None)])
def test_list_account_exception(self): def test_list_account_exception(self):
mock_q = Queue() mock_q = Queue()
mock_conn = self._get_mock_connection() mock_conn = self._get_mock_connection()
@ -682,7 +757,7 @@ class TestServiceList(_TestServiceBase):
mock_conn, self.opts, mock_q) mock_conn, self.opts, mock_q)
mock_conn.get_account.assert_called_once_with( mock_conn.get_account.assert_called_once_with(
marker='', prefix=None marker='', prefix=None, headers={}
) )
self.assertEqual(expected_r, self._get_queue(mock_q)) self.assertEqual(expected_r, self._get_queue(mock_q))
self.assertIsNone(self._get_queue(mock_q)) self.assertIsNone(self._get_queue(mock_q))
@ -767,6 +842,37 @@ class TestServiceList(_TestServiceBase):
self.assertIsNone(self._get_queue(mock_q)) self.assertIsNone(self._get_queue(mock_q))
def test_list_container_with_headers(self):
mock_q = Queue()
mock_conn = self._get_mock_connection()
get_container_returns = [
(None, [{'name': 'test_o'}]),
(None, [])
]
mock_conn.get_container = Mock(side_effect=get_container_returns)
expected_r = self._get_expected({
'action': 'list_container_part',
'container': 'test_c',
'success': True,
'listing': [{'name': 'test_o'}],
'marker': ''
})
opt_c = self.opts.copy()
opt_c['header'] = ['Skip-Middleware: Test']
SwiftService._list_container_job(
mock_conn, 'test_c', opt_c, mock_q
)
self.assertEqual(expected_r, self._get_queue(mock_q))
self.assertIsNone(self._get_queue(mock_q))
self.assertEqual(mock_conn.get_container.mock_calls, [
mock.call('test_c', headers={'Skip-Middleware': 'Test'},
delimiter='', marker='', prefix=None),
mock.call('test_c', headers={'Skip-Middleware': 'Test'},
delimiter='', marker='test_o', prefix=None)])
def test_list_container_exception(self): def test_list_container_exception(self):
mock_q = Queue() mock_q = Queue()
mock_conn = self._get_mock_connection() mock_conn = self._get_mock_connection()
@ -786,7 +892,7 @@ class TestServiceList(_TestServiceBase):
) )
mock_conn.get_container.assert_called_once_with( mock_conn.get_container.assert_called_once_with(
'test_c', marker='', delimiter='', prefix=None 'test_c', marker='', delimiter='', prefix=None, headers={}
) )
self.assertEqual(expected_r, self._get_queue(mock_q)) self.assertEqual(expected_r, self._get_queue(mock_q))
self.assertIsNone(self._get_queue(mock_q)) self.assertIsNone(self._get_queue(mock_q))
@ -1397,11 +1503,13 @@ class TestServiceUpload(_TestServiceBase):
mock_conn.head_object.assert_called_with('test_c', 'test_o') mock_conn.head_object.assert_called_with('test_c', 'test_o')
expected = [ expected = [
mock.call('test_c_segments', prefix='test_o/prefix', mock.call('test_c_segments', prefix='test_o/prefix',
marker='', delimiter=None), marker='', delimiter=None, headers={}),
mock.call('test_c_segments', prefix='test_o/prefix', mock.call('test_c_segments', prefix='test_o/prefix',
marker="test_o/prefix/01", delimiter=None), marker="test_o/prefix/01", delimiter=None,
headers={}),
mock.call('test_c_segments', prefix='test_o/prefix', mock.call('test_c_segments', prefix='test_o/prefix',
marker="test_o/prefix/02", delimiter=None), marker="test_o/prefix/02", delimiter=None,
headers={}),
] ]
mock_conn.get_container.assert_has_calls(expected) mock_conn.get_container.assert_has_calls(expected)
@ -2102,15 +2210,18 @@ class TestServiceDownload(_TestServiceBase):
mock.call('test_c_segments', mock.call('test_c_segments',
delimiter=None, delimiter=None,
prefix='test_o/prefix', prefix='test_o/prefix',
marker=''), marker='',
headers={}),
mock.call('test_c_segments', mock.call('test_c_segments',
delimiter=None, delimiter=None,
prefix='test_o/prefix', prefix='test_o/prefix',
marker='test_o/prefix/2'), marker='test_o/prefix/2',
headers={}),
mock.call('test_c_segments', mock.call('test_c_segments',
delimiter=None, delimiter=None,
prefix='test_o/prefix', prefix='test_o/prefix',
marker='test_o/prefix/3')]) marker='test_o/prefix/3',
headers={})])
def test_download_object_job_skip_identical_nested_slo(self): def test_download_object_job_skip_identical_nested_slo(self):
with tempfile.NamedTemporaryFile() as f: with tempfile.NamedTemporaryFile() as f:
@ -2243,15 +2354,18 @@ class TestServiceDownload(_TestServiceBase):
mock.call('test_c_segments', mock.call('test_c_segments',
delimiter=None, delimiter=None,
prefix='test_o/prefix', prefix='test_o/prefix',
marker=''), marker='',
headers={}),
mock.call('test_c_segments', mock.call('test_c_segments',
delimiter=None, delimiter=None,
prefix='test_o/prefix', prefix='test_o/prefix',
marker='test_o/prefix/2'), marker='test_o/prefix/2',
headers={}),
mock.call('test_c_segments', mock.call('test_c_segments',
delimiter=None, delimiter=None,
prefix='test_o/prefix', prefix='test_o/prefix',
marker='test_o/prefix/3')]) marker='test_o/prefix/3',
headers={})])
self.assertEqual(mock_conn.get_object.mock_calls, [ self.assertEqual(mock_conn.get_object.mock_calls, [
mock.call('test_c', mock.call('test_c',
'test_o', 'test_o',

View File

@ -142,6 +142,28 @@ class TestShell(unittest.TestCase):
' Objects: 2\n' ' Objects: 2\n'
' Bytes: 3\n') ' Bytes: 3\n')
@mock.patch('swiftclient.service.Connection')
def test_stat_account_with_headers(self, connection):
argv = ["", "stat", "-H", "Skip-Middleware: Test"]
return_headers = {
'x-account-container-count': '1',
'x-account-object-count': '2',
'x-account-bytes-used': '3',
'content-length': 0,
'date': ''}
connection.return_value.head_account.return_value = return_headers
connection.return_value.url = 'http://127.0.0.1/v1/AUTH_account'
with CaptureOutput() as output:
swiftclient.shell.main(argv)
self.assertEqual(output.out,
' Account: AUTH_account\n'
'Containers: 1\n'
' Objects: 2\n'
' Bytes: 3\n')
self.assertEqual(connection.return_value.head_account.mock_calls, [
mock.call(headers={'Skip-Middleware': 'Test'})])
@mock.patch('swiftclient.service.Connection') @mock.patch('swiftclient.service.Connection')
def test_stat_container(self, connection): def test_stat_container(self, connection):
return_headers = { return_headers = {
@ -168,6 +190,34 @@ class TestShell(unittest.TestCase):
' Sync To: other\n' ' Sync To: other\n'
' Sync Key: secret\n') ' Sync Key: secret\n')
@mock.patch('swiftclient.service.Connection')
def test_stat_container_with_headers(self, connection):
return_headers = {
'x-container-object-count': '1',
'x-container-bytes-used': '2',
'x-container-read': 'test2:tester2',
'x-container-write': 'test3:tester3',
'x-container-sync-to': 'other',
'x-container-sync-key': 'secret',
}
argv = ["", "stat", "container", "-H", "Skip-Middleware: Test"]
connection.return_value.head_container.return_value = return_headers
connection.return_value.url = 'http://127.0.0.1/v1/AUTH_account'
with CaptureOutput() as output:
swiftclient.shell.main(argv)
self.assertEqual(output.out,
' Account: AUTH_account\n'
'Container: container\n'
' Objects: 1\n'
' Bytes: 2\n'
' Read ACL: test2:tester2\n'
'Write ACL: test3:tester3\n'
' Sync To: other\n'
' Sync Key: secret\n')
self.assertEqual(connection.return_value.head_container.mock_calls, [
mock.call('container', headers={'Skip-Middleware': 'Test'})])
@mock.patch('swiftclient.service.Connection') @mock.patch('swiftclient.service.Connection')
def test_stat_object(self, connection): def test_stat_object(self, connection):
return_headers = { return_headers = {
@ -194,6 +244,36 @@ class TestShell(unittest.TestCase):
' ETag: md5\n' ' ETag: md5\n'
' Manifest: manifest\n') ' Manifest: manifest\n')
@mock.patch('swiftclient.service.Connection')
def test_stat_object_with_headers(self, connection):
return_headers = {
'x-object-manifest': 'manifest',
'etag': 'md5',
'last-modified': 'yesterday',
'content-type': 'text/plain',
'content-length': 42,
}
argv = ["", "stat", "container", "object",
"-H", "Skip-Middleware: Test"]
connection.return_value.head_object.return_value = return_headers
connection.return_value.url = 'http://127.0.0.1/v1/AUTH_account'
with CaptureOutput() as output:
swiftclient.shell.main(argv)
self.assertEqual(output.out,
' Account: AUTH_account\n'
' Container: container\n'
' Object: object\n'
' Content Type: text/plain\n'
'Content Length: 42\n'
' Last Modified: yesterday\n'
' ETag: md5\n'
' Manifest: manifest\n')
self.assertEqual(connection.return_value.head_object.mock_calls, [
mock.call('container', 'object',
headers={'Skip-Middleware': 'Test'})])
@mock.patch('swiftclient.service.Connection') @mock.patch('swiftclient.service.Connection')
def test_list_account(self, connection): def test_list_account(self, connection):
# Test account listing # Test account listing
@ -206,8 +286,28 @@ class TestShell(unittest.TestCase):
with CaptureOutput() as output: with CaptureOutput() as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
calls = [mock.call(marker='', prefix=None), calls = [mock.call(marker='', prefix=None, headers={}),
mock.call(marker='container', prefix=None)] mock.call(marker='container', prefix=None, headers={})]
connection.return_value.get_account.assert_has_calls(calls)
self.assertEqual(output.out, 'container\n')
@mock.patch('swiftclient.service.Connection')
def test_list_account_with_headers(self, connection):
# Test account listing
connection.return_value.get_account.side_effect = [
[None, [{'name': 'container'}]],
[None, []],
]
argv = ["", "list", '-H', 'Skip-Custom-Middleware: True']
with CaptureOutput() as output:
swiftclient.shell.main(argv)
calls = [mock.call(marker='', prefix=None,
headers={'Skip-Custom-Middleware': 'True'}),
mock.call(marker='container', prefix=None,
headers={'Skip-Custom-Middleware': 'True'})]
connection.return_value.get_account.assert_has_calls(calls) connection.return_value.get_account.assert_has_calls(calls)
self.assertEqual(output.out, 'container\n') self.assertEqual(output.out, 'container\n')
@ -223,8 +323,8 @@ class TestShell(unittest.TestCase):
argv = ["", "list", "--lh"] argv = ["", "list", "--lh"]
with CaptureOutput() as output: with CaptureOutput() as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
calls = [mock.call(marker='', prefix=None), calls = [mock.call(marker='', prefix=None, headers={}),
mock.call(marker='container', prefix=None)] mock.call(marker='container', prefix=None, headers={})]
connection.return_value.get_account.assert_has_calls(calls) connection.return_value.get_account.assert_has_calls(calls)
self.assertEqual(output.out, self.assertEqual(output.out,
@ -243,8 +343,8 @@ class TestShell(unittest.TestCase):
argv = ["", "list", "--lh"] argv = ["", "list", "--lh"]
with CaptureOutput() as output: with CaptureOutput() as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
calls = [mock.call(marker='', prefix=None), calls = [mock.call(marker='', prefix=None, headers={}),
mock.call(marker='container', prefix=None)] mock.call(marker='container', prefix=None, headers={})]
connection.return_value.get_account.assert_has_calls(calls) connection.return_value.get_account.assert_has_calls(calls)
self.assertEqual(output.out, self.assertEqual(output.out,
@ -273,7 +373,7 @@ class TestShell(unittest.TestCase):
argv = ["", "list", "--lh", "--totals"] argv = ["", "list", "--lh", "--totals"]
with CaptureOutput() as output: with CaptureOutput() as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
calls = [mock.call(marker='', prefix=None)] calls = [mock.call(marker='', prefix=None, headers={})]
connection.return_value.get_account.assert_has_calls(calls) connection.return_value.get_account.assert_has_calls(calls)
self.assertEqual(output.out, ' 6 3\n') self.assertEqual(output.out, ' 6 3\n')
@ -287,9 +387,10 @@ class TestShell(unittest.TestCase):
with CaptureOutput() as output: with CaptureOutput() as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
calls = [ calls = [
mock.call('container', marker='', delimiter=None, prefix=None), mock.call('container', marker='',
delimiter=None, prefix=None, headers={}),
mock.call('container', marker='object_a', mock.call('container', marker='object_a',
delimiter=None, prefix=None)] delimiter=None, prefix=None, headers={})]
connection.return_value.get_container.assert_has_calls(calls) connection.return_value.get_container.assert_has_calls(calls)
self.assertEqual(output.out, 'object_a\n') self.assertEqual(output.out, 'object_a\n')
@ -305,9 +406,10 @@ class TestShell(unittest.TestCase):
with CaptureOutput() as output: with CaptureOutput() as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
calls = [ calls = [
mock.call('container', marker='', delimiter=None, prefix=None), mock.call('container', marker='',
delimiter=None, prefix=None, headers={}),
mock.call('container', marker='object_a', mock.call('container', marker='object_a',
delimiter=None, prefix=None)] delimiter=None, prefix=None, headers={})]
connection.return_value.get_container.assert_has_calls(calls) connection.return_value.get_container.assert_has_calls(calls)
self.assertEqual(output.out, self.assertEqual(output.out,
@ -315,6 +417,26 @@ class TestShell(unittest.TestCase):
' type/content object_a\n' ' type/content object_a\n'
' 0\n') ' 0\n')
@mock.patch('swiftclient.service.Connection')
def test_list_container_with_headers(self, connection):
connection.return_value.get_container.side_effect = [
[None, [{'name': 'object_a'}]],
[None, []],
]
argv = ["", "list", "container", "-H", "Skip-Middleware: Test"]
with CaptureOutput() as output:
swiftclient.shell.main(argv)
calls = [
mock.call('container', marker='',
delimiter=None, prefix=None,
headers={'Skip-Middleware': 'Test'}),
mock.call('container', marker='object_a',
delimiter=None, prefix=None,
headers={'Skip-Middleware': 'Test'})]
connection.return_value.get_container.assert_has_calls(calls)
self.assertEqual(output.out, 'object_a\n')
@mock.patch('swiftclient.service.makedirs') @mock.patch('swiftclient.service.makedirs')
@mock.patch('swiftclient.service.Connection') @mock.patch('swiftclient.service.Connection')
def test_download(self, connection, makedirs): def test_download(self, connection, makedirs):
@ -835,19 +957,19 @@ class TestShell(unittest.TestCase):
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
connection.return_value.delete_object.assert_has_calls([ connection.return_value.delete_object.assert_has_calls([
mock.call('container', 'object', query_string=None, mock.call('container', 'object', query_string=None,
response_dict={}), response_dict={}, headers={}),
mock.call('container', 'obj\xe9ct2', query_string=None, mock.call('container', 'obj\xe9ct2', query_string=None,
response_dict={}), response_dict={}, headers={}),
mock.call('container2', 'object', query_string=None, mock.call('container2', 'object', query_string=None,
response_dict={})], any_order=True) response_dict={}, headers={})], any_order=True)
self.assertEqual(3, connection.return_value.delete_object.call_count, self.assertEqual(3, connection.return_value.delete_object.call_count,
'Expected 3 calls but found\n%r' 'Expected 3 calls but found\n%r'
% connection.return_value.delete_object.mock_calls) % connection.return_value.delete_object.mock_calls)
self.assertEqual( self.assertEqual(
connection.return_value.delete_container.mock_calls, [ connection.return_value.delete_container.mock_calls, [
mock.call('container', response_dict={}), mock.call('container', response_dict={}, headers={}),
mock.call('container2', response_dict={}), mock.call('container2', response_dict={}, headers={}),
mock.call('empty_container', response_dict={})]) mock.call('empty_container', response_dict={}, headers={})])
@mock.patch.object(swiftclient.service.SwiftService, '_should_bulk_delete', @mock.patch.object(swiftclient.service.SwiftService, '_should_bulk_delete',
lambda *a: True) lambda *a: True)
@ -900,9 +1022,9 @@ class TestShell(unittest.TestCase):
connection.return_value.post_account.mock_calls[2]) connection.return_value.post_account.mock_calls[2])
self.assertEqual( self.assertEqual(
connection.return_value.delete_container.mock_calls, [ connection.return_value.delete_container.mock_calls, [
mock.call('container', response_dict={}), mock.call('container', response_dict={}, headers={}),
mock.call('container2', response_dict={}), mock.call('container2', response_dict={}, headers={}),
mock.call('empty_container', response_dict={})]) mock.call('empty_container', response_dict={}, headers={})])
@mock.patch('swiftclient.service.Connection') @mock.patch('swiftclient.service.Connection')
def test_delete_bulk_account_with_capabilities(self, connection): def test_delete_bulk_account_with_capabilities(self, connection):
@ -957,9 +1079,9 @@ class TestShell(unittest.TestCase):
response_dict={})]) response_dict={})])
self.assertEqual( self.assertEqual(
connection.return_value.delete_container.mock_calls, [ connection.return_value.delete_container.mock_calls, [
mock.call('container', response_dict={}), mock.call('container', response_dict={}, headers={}),
mock.call('container2', response_dict={}), mock.call('container2', response_dict={}, headers={}),
mock.call('empty_container', response_dict={})]) mock.call('empty_container', response_dict={}, headers={})])
self.assertEqual(connection.return_value.get_capabilities.mock_calls, self.assertEqual(connection.return_value.get_capabilities.mock_calls,
[mock.call(None)]) # only one /info request [mock.call(None)]) # only one /info request
@ -976,9 +1098,29 @@ class TestShell(unittest.TestCase):
connection.return_value.head_object.return_value = {} connection.return_value.head_object.return_value = {}
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
connection.return_value.delete_container.assert_called_with( connection.return_value.delete_container.assert_called_with(
'container', response_dict={}) 'container', response_dict={}, headers={})
connection.return_value.delete_object.assert_called_with( connection.return_value.delete_object.assert_called_with(
'container', 'object', query_string=None, response_dict={}) 'container', 'object', query_string=None, response_dict={},
headers={})
@mock.patch.object(swiftclient.service.SwiftService, '_should_bulk_delete',
lambda *a: False)
@mock.patch('swiftclient.service.Connection')
def test_delete_container_headers(self, connection):
connection.return_value.get_container.side_effect = [
[None, [{'name': 'object'}]],
[None, []],
]
connection.return_value.attempts = 0
argv = ["", "delete", "container", "-H", "Skip-Middleware: Test"]
connection.return_value.head_object.return_value = {}
swiftclient.shell.main(argv)
connection.return_value.delete_container.assert_called_with(
'container', response_dict={},
headers={'Skip-Middleware': 'Test'})
connection.return_value.delete_object.assert_called_with(
'container', 'object', query_string=None, response_dict={},
headers={'Skip-Middleware': 'Test'})
@mock.patch.object(swiftclient.service.SwiftService, '_should_bulk_delete', @mock.patch.object(swiftclient.service.SwiftService, '_should_bulk_delete',
lambda *a: True) lambda *a: True)
@ -1000,7 +1142,7 @@ class TestShell(unittest.TestCase):
'Accept': 'application/json'}, 'Accept': 'application/json'},
response_dict={}) response_dict={})
connection.return_value.delete_container.assert_called_with( connection.return_value.delete_container.assert_called_with(
'container', response_dict={}) 'container', response_dict={}, headers={})
def test_delete_verbose_output_utf8(self): def test_delete_verbose_output_utf8(self):
container = 't\u00e9st_c' container = 't\u00e9st_c'
@ -1043,7 +1185,8 @@ class TestShell(unittest.TestCase):
connection.return_value.attempts = 0 connection.return_value.attempts = 0
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
connection.return_value.delete_object.assert_called_with( connection.return_value.delete_object.assert_called_with(
'container', 'object', query_string=None, response_dict={}) 'container', 'object', query_string=None, response_dict={},
headers={})
@mock.patch.object(swiftclient.service.SwiftService, '_should_bulk_delete', @mock.patch.object(swiftclient.service.SwiftService, '_should_bulk_delete',
lambda *a: True) lambda *a: True)

View File

@ -3003,7 +3003,7 @@ class TestServiceToken(MockHttpTest):
with mock.patch('swiftclient.client.http_connection', with mock.patch('swiftclient.client.http_connection',
self.fake_http_connection(202)): self.fake_http_connection(202)):
conn = self.get_connection() conn = self.get_connection()
conn.delete_object('container1', 'obj1', 'a_string') conn.delete_object('container1', 'obj1', query_string='a_string')
self.assertEqual(1, len(self.request_log), self.request_log) self.assertEqual(1, len(self.request_log), self.request_log)
for actual in self.iter_request_log(): for actual in self.iter_request_log():
self.assertEqual('DELETE', actual['method']) self.assertEqual('DELETE', actual['method'])