Merge "test-expirer: call tracking & exception stubs"

This commit is contained in:
Zuul 2024-11-01 16:59:02 +00:00 committed by Gerrit Code Review
commit 6221e5d8c1

View File

@ -61,6 +61,9 @@ class FakeInternalClient(object):
'container2: [], 'container2: [],
}, },
'account2': {}, 'account2': {},
'account3': {
'some_bad_container': UnexpectedResponse(),
},
} }
N.B. the objects entries should be the container-server JSON style N.B. the objects entries should be the container-server JSON style
db rows, but this fake will dynamically detect when names are given db rows, but this fake will dynamically detect when names are given
@ -68,11 +71,16 @@ class FakeInternalClient(object):
""" """
self.aco_dict = defaultdict(dict) self.aco_dict = defaultdict(dict)
self.aco_dict.update(aco_dict) self.aco_dict.update(aco_dict)
self._calls = []
def get_account_info(self, account): def get_account_info(self, account):
acc_dict = self.aco_dict[account] acc_dict = self.aco_dict[account]
container_count = len(acc_dict) container_count = len(acc_dict)
obj_count = sum(len(objs) for objs in acc_dict.values()) obj_count = 0
for obj_list_or_err in acc_dict.values():
if isinstance(obj_list_or_err, Exception):
continue
obj_count += len(obj_list_or_err)
return container_count, obj_count return container_count, obj_count
def iter_containers(self, account, prefix=''): def iter_containers(self, account, prefix=''):
@ -81,12 +89,19 @@ class FakeInternalClient(object):
for container in sorted(acc_dict) for container in sorted(acc_dict)
if container.startswith(prefix)] if container.startswith(prefix)]
def delete_container(*a, **kw): def delete_container(self, account, container, **kwargs):
pass self._calls.append(
('delete_container', '/'.join((account, container)), kwargs)
)
def iter_objects(self, account, container, **kwargs): def iter_objects(self, account, container, **kwargs):
self._calls.append(
('iter_objects', '/'.join((account, container)), kwargs)
)
acc_dict = self.aco_dict[account] acc_dict = self.aco_dict[account]
obj_iter = acc_dict.get(container, []) obj_iter = acc_dict.get(container, [])
if isinstance(obj_iter, Exception):
raise obj_iter
resp = [] resp = []
for obj in obj_iter: for obj in obj_iter:
if not isinstance(obj, dict): if not isinstance(obj, dict):
@ -94,8 +109,10 @@ class FakeInternalClient(object):
resp.append(obj) resp.append(obj)
return resp return resp
def delete_object(*a, **kw): def delete_object(self, account, container, obj, **kwargs):
pass self._calls.append(
('delete_object', '/'.join((account, container, obj)), kwargs)
)
class TestExpirerHelpers(TestCase): class TestExpirerHelpers(TestCase):
@ -231,7 +248,7 @@ class TestObjectExpirer(TestCase):
self.ts = make_timestamp_iter() self.ts = make_timestamp_iter()
now = int(time()) self.now = now = int(time())
self.empty_time = str(now - 864000) self.empty_time = str(now - 864000)
self.empty_time_container = self.get_expirer_container(self.empty_time) self.empty_time_container = self.get_expirer_container(self.empty_time)
@ -1585,38 +1602,34 @@ class TestObjectExpirer(TestCase):
# Test that object listing on the first container returns 503 and raise # Test that object listing on the first container returns 503 and raise
# UnexpectedResponse, and expect the second task container will # UnexpectedResponse, and expect the second task container will
# continue to be processed. # continue to be processed.
# In this test, all tasks are assigned to the tested expirer.
my_index = 0
divisor = 1
# Store reference to the real method before mocking self.expirer.swift.aco_dict['.expiring_objects'][
real_iter_objects = self.fake_swift.iter_objects self.just_past_time_container] = \
internal_client.UnexpectedResponse(
'Mocked error', Response(status=503))
def mock_iter_objects(account, container, **kwargs): with mock.patch.object(self.expirer, 'pop_queue'):
if container == self.just_past_time_container: self.expirer.run_once()
mock_resp = Response(status=503) # everything but the broken container
raise internal_client.UnexpectedResponse( expected = sorted(
'Mocked error', mock_resp) p
return real_iter_objects(account, container) for c, paths in self.expired_target_paths.items()
for p in paths
task_account_container_list = [ if c != self.just_past_time
('.expiring_objects', self.just_past_time_container), )
('.expiring_objects', self.past_time_container) self.assertEqual(
] expected, sorted(
expected = [ path
self.make_task(self.past_time_container, self.past_time, for method, path, kwargs in self.expirer.swift._calls
target_path) if method == 'delete_object'
for target_path in self.expired_target_paths[self.past_time]] ))
self.assertEqual(
with mock.patch.object(self.expirer.swift, 'iter_objects', [('.expiring_objects/%s' % self.empty_time_container,
mock_iter_objects): {'acceptable_statuses': (2, 404, 409)})], [
with mock.patch.object(self.expirer.swift, 'delete_container') \ (path, kwargs)
as mock_delete_container: for method, path, kwargs in self.expirer.swift._calls
self.assertEqual( if method == 'delete_container'
list(self.expirer.iter_task_to_expire( ])
task_account_container_list, my_index, divisor)),
expected)
self.assertEqual(mock_delete_container.mock_calls, [])
log_lines = self.logger.get_lines_for_level('error') log_lines = self.logger.get_lines_for_level('error')
self.assertEqual( self.assertEqual(
@ -1626,43 +1639,39 @@ class TestObjectExpirer(TestCase):
% self.just_past_time_container] % self.just_past_time_container]
) )
self.assertEqual( self.assertEqual(
{'tasks.assigned': 5}, {'tasks.assigned': 5, 'objects': 5},
self.expirer.logger.statsd_client.get_increment_counts() self.expirer.logger.statsd_client.get_increment_counts()
) )
def test_iter_task_to_expire_exception(self): def test_iter_task_to_expire_exception(self):
# Test that object listing on the first container raise Exception, and # Test that object listing on the first container raise Exception, and
# expect the second task container will continue to be processed. # expect the second task container will continue to be processed.
# In this test, all tasks are assigned to the tested expirer.
my_index = 0
divisor = 1
# Store reference to the real method before mocking self.expirer.swift.aco_dict['.expiring_objects'][
real_iter_objects = self.fake_swift.iter_objects self.just_past_time_container] = Exception('failed to connect')
def mock_iter_objects(account, container, **kwargs): with mock.patch.object(self.expirer, 'pop_queue'):
if container == self.just_past_time_container: self.expirer.run_once()
raise Exception('failed to connect')
return real_iter_objects(account, container)
task_account_container_list = [ # everything but the broken container
('.expiring_objects', self.just_past_time_container), expected = sorted(
('.expiring_objects', self.past_time_container) p
] for c, paths in self.expired_target_paths.items()
expected = [ for p in paths
self.make_task(self.past_time_container, self.past_time, if c != self.just_past_time
target_path) )
for target_path in self.expired_target_paths[self.past_time]] self.assertEqual(expected, sorted(
path
with mock.patch.object(self.expirer.swift, 'iter_objects', for method, path, kwargs in self.expirer.swift._calls
mock_iter_objects): if method == 'delete_object'
with mock.patch.object(self.expirer.swift, 'delete_container') \ ))
as mock_delete_container: self.assertEqual(
self.assertEqual( [('.expiring_objects/%s' % self.empty_time_container,
list(self.expirer.iter_task_to_expire( {'acceptable_statuses': (2, 404, 409)})], [
task_account_container_list, my_index, divisor)), (path, kwargs)
expected) for method, path, kwargs in self.expirer.swift._calls
self.assertEqual(mock_delete_container.mock_calls, []) if method == 'delete_container'
])
log_lines = self.logger.get_lines_for_level('error') log_lines = self.logger.get_lines_for_level('error')
self.assertEqual( self.assertEqual(
@ -1672,80 +1681,116 @@ class TestObjectExpirer(TestCase):
% self.just_past_time_container] % self.just_past_time_container]
) )
self.assertEqual( self.assertEqual(
{'tasks.assigned': 5}, {'tasks.assigned': 5, 'objects': 5},
self.expirer.logger.statsd_client.get_increment_counts() self.expirer.logger.statsd_client.get_increment_counts()
) )
def test_iter_task_to_expire_404_response_on_empty_container(self): def test_iter_task_to_expire_404_response_on_missing_container(self):
# Test that object listing on an empty container returns 404 and # Test that object listing on a missing container returns 404 and
# raise UnexpectedResponse, and expect ``iter_task_to_expire`` won't # raise UnexpectedResponse, and expect ``iter_task_to_expire`` won't
# delete this task container. # delete this task container.
# In this test, all tasks are assigned to the tested expirer. missing_time = str(self.now - 172800)
my_index = 0 missing_time_container = self.get_expirer_container(missing_time)
divisor = 1 self.expirer.swift.aco_dict[
'.expiring_objects'][missing_time_container] = \
internal_client.UnexpectedResponse(
'Mocked error', Response(status=404))
err_resp = Response(status=404) with mock.patch.object(self.expirer, 'pop_queue'):
err = internal_client.UnexpectedResponse('Mocked error', err_resp) self.expirer.run_once()
task_account_container_list = [ # all containers iter'd
('.expiring_objects', self.empty_time_container) self.assertEqual([
] ('.expiring_objects/%s' % c, {'acceptable_statuses': [2]})
for c in [
self.empty_time_container,
missing_time_container,
self.past_time_container,
self.just_past_time_container,
]
], [
(path, kwargs) for method, path, kwargs in
self.expirer.swift._calls
if method == 'iter_objects'
])
# everything is still expired
expected = sorted(
p
for c, paths in self.expired_target_paths.items()
for p in paths
)
self.assertEqual(expected, sorted(
path
for method, path, kwargs in self.expirer.swift._calls
if method == 'delete_object'
))
# Only the empty task container gets deleted.
self.assertEqual(
[('.expiring_objects/%s' % self.empty_time_container,
{'acceptable_statuses': (2, 404, 409)})], [
(path, kwargs)
for method, path, kwargs in self.expirer.swift._calls
if method == 'delete_container'
])
with mock.patch.object(self.expirer.swift, 'iter_objects',
side_effect=err) as mock_method:
with mock.patch.object(self.expirer.swift, 'delete_container') \
as mock_delete_container:
self.assertEqual(
list(self.expirer.iter_task_to_expire(
task_account_container_list, my_index, divisor)),
[])
log_lines = self.logger.get_lines_for_level('error') log_lines = self.logger.get_lines_for_level('error')
self.assertFalse(log_lines) self.assertFalse(log_lines)
# This empty task container won't get deleted.
self.assertEqual(mock_delete_container.mock_calls, []) def test_iter_task_to_expire_503_response_on_container(self):
self.assertEqual( # Test that object listing on a container returns 503 and raise
{}, self.expirer.logger.statsd_client.get_increment_counts()) # UnexpectedResponse, and expect ``iter_task_to_expire`` won't delete
self.assertEqual(mock_method.call_args_list, [ # this task container.
mock.call('.expiring_objects', missing_time = str(self.now - 172800)
self.empty_time_container, missing_time_container = self.get_expirer_container(missing_time)
acceptable_statuses=[2]) self.expirer.swift.aco_dict[
'.expiring_objects'][missing_time_container] = \
internal_client.UnexpectedResponse(
'Mocked error', Response(status=503))
with mock.patch.object(self.expirer, 'pop_queue'):
self.expirer.run_once()
# all containers iter'd
self.assertEqual([
('.expiring_objects/%s' % c, {'acceptable_statuses': [2]})
for c in [
self.empty_time_container,
missing_time_container,
self.past_time_container,
self.just_past_time_container,
]
], [
(path, kwargs) for method, path, kwargs in
self.expirer.swift._calls
if method == 'iter_objects'
]) ])
# everything is still expired
expected = sorted(
path
for c, paths in self.expired_target_paths.items()
for path in paths
)
self.assertEqual(expected, sorted(
path
for method, path, kwargs in self.expirer.swift._calls
if method == 'delete_object'
))
# Only the empty task container gets deleted.
self.assertEqual(
[('.expiring_objects/%s' % self.empty_time_container,
{'acceptable_statuses': (2, 404, 409)})], [
(path, kwargs)
for method, path, kwargs in self.expirer.swift._calls
if method == 'delete_container'
])
def test_iter_task_to_expire_503_response_on_empty_container(self):
# Test that object listing on an empty container returns 503 and
# raise UnexpectedResponse, and expect ``iter_task_to_expire`` won't
# delete this task container.
# In this test, all tasks are assigned to the tested expirer.
my_index = 0
divisor = 1
def mock_iter_objects(account, container, **kwargs):
mock_resp = Response(status=503)
raise internal_client.UnexpectedResponse('Mocked error', mock_resp)
task_account_container_list = [
('.expiring_objects', self.empty_time_container)
]
with mock.patch.object(self.expirer.swift, 'iter_objects',
side_effect=mock_iter_objects):
with mock.patch.object(self.expirer.swift, 'delete_container') \
as mock_delete_container:
self.assertEqual(
list(self.expirer.iter_task_to_expire(
task_account_container_list, my_index, divisor)),
[])
log_lines = self.logger.get_lines_for_level('error') log_lines = self.logger.get_lines_for_level('error')
self.assertEqual( self.assertEqual(
log_lines[0], log_lines[0],
'Unexpected response while listing objects in container ' 'Unexpected response while listing objects in container '
'.expiring_objects %s: Mocked error' '.expiring_objects %s: Mocked error'
% self.empty_time_container, % missing_time_container,
) )
# This empty task container won't get deleted.
self.assertEqual(mock_delete_container.mock_calls, [])
self.assertEqual(
{}, self.expirer.logger.statsd_client.get_increment_counts())
def test_run_once_unicode_problem(self): def test_run_once_unicode_problem(self):
requests = [] requests = []