Merge "Add parallelism to object expirer daemon."
This commit is contained in:
commit
5bfd2d798d
@ -17,8 +17,17 @@
|
|||||||
from swift.common.daemon import run_daemon
|
from swift.common.daemon import run_daemon
|
||||||
from swift.common.utils import parse_options
|
from swift.common.utils import parse_options
|
||||||
from swift.obj.expirer import ObjectExpirer
|
from swift.obj.expirer import ObjectExpirer
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
conf_file, options = parse_options(once=True)
|
parser = OptionParser("%prog CONFIG [options]")
|
||||||
|
parser.add_option('--processes', dest='processes',
|
||||||
|
help="Number of processes to use to do the work, don't "
|
||||||
|
"use this option to do all the work in one process")
|
||||||
|
parser.add_option('--process', dest='process',
|
||||||
|
help="Process number for this process, don't use "
|
||||||
|
"this option to do all the work in one process, this "
|
||||||
|
"is used to determine which part of the work this "
|
||||||
|
"process should do")
|
||||||
|
conf_file, options = parse_options(parser=parser, once=True)
|
||||||
run_daemon(ObjectExpirer, conf_file, **options)
|
run_daemon(ObjectExpirer, conf_file, **options)
|
||||||
|
@ -10,7 +10,13 @@ The ``X-Delete-After`` header takes a integer number of seconds. The proxy serve
|
|||||||
|
|
||||||
As expiring objects are added to the system, the object servers will record the expirations in a hidden ``.expiring_objects`` account for the ``swift-object-expirer`` to handle later.
|
As expiring objects are added to the system, the object servers will record the expirations in a hidden ``.expiring_objects`` account for the ``swift-object-expirer`` to handle later.
|
||||||
|
|
||||||
Just one instance of the ``swift-object-expirer`` daemon needs to run for a cluster. This isn't exactly automatic failover high availability, but if this daemon doesn't run for a few hours it should not be any real issue. The expired-but-not-yet-deleted objects will still ``404 Not Found`` if someone tries to ``GET`` or ``HEAD`` them and they'll just be deleted a bit later when the daemon is restarted.
|
Usually, just one instance of the ``swift-object-expirer`` daemon needs to run for a cluster. This isn't exactly automatic failover high availability, but if this daemon doesn't run for a few hours it should not be any real issue. The expired-but-not-yet-deleted objects will still ``404 Not Found`` if someone tries to ``GET`` or ``HEAD`` them and they'll just be deleted a bit later when the daemon is restarted.
|
||||||
|
|
||||||
|
By default, the ``swift-object-expirer`` daemon will run with a concurrency of 1. Increase this value to get more concurrency. A concurrency of 1 may not be enough to delete expiring objects in a timely fashion for a particular swift cluster.
|
||||||
|
|
||||||
|
It is possible to run multiple daemons to do different parts of the work if a single process with a concurrency of more than 1 is not enough (see the sample config file for details).
|
||||||
|
|
||||||
|
To run the ``swift-object-expirer`` as multiple processes, set ``processes`` to the number of processes (either in the config file or on the command line). Then run one process for each part. Use ``process`` to specify the part of the work to be done by a process using the command line or the config. So, for example, if you'd like to run three processes, set ``processes`` to 3 and run three processes with ``process`` set to 0, 1, and 2 for the three processes. If multiple processes are used, it's necessary to run one for each part of the work or that part of the work will not be done.
|
||||||
|
|
||||||
The daemon uses the ``/etc/swift/object-expirer.conf`` by default, and here is a quick sample conf file::
|
The daemon uses the ``/etc/swift/object-expirer.conf`` by default, and here is a quick sample conf file::
|
||||||
|
|
||||||
|
@ -27,6 +27,21 @@
|
|||||||
# interval = 300
|
# interval = 300
|
||||||
# auto_create_account_prefix = .
|
# auto_create_account_prefix = .
|
||||||
# report_interval = 300
|
# report_interval = 300
|
||||||
|
# concurrency is the level of concurrency o use to do the work, this value
|
||||||
|
# must be set to at least 1
|
||||||
|
# concurrency = 1
|
||||||
|
# processes is how many parts to divide the work into, one part per process
|
||||||
|
# that will be doing the work
|
||||||
|
# processes set 0 means that a single process will be doing all the work
|
||||||
|
# processes can also be specified on the command line and will override the
|
||||||
|
# config value
|
||||||
|
# processes = 0
|
||||||
|
# process is which of the parts a particular process will work on
|
||||||
|
# process can also be specified on the command line and will overide the config
|
||||||
|
# value
|
||||||
|
# process is "zero based", if you want to use 3 processes, you should run
|
||||||
|
# processes with process set to 0, 1, and 2
|
||||||
|
# process = 0
|
||||||
|
|
||||||
[pipeline:main]
|
[pipeline:main]
|
||||||
pipeline = catch_errors cache proxy-server
|
pipeline = catch_errors cache proxy-server
|
||||||
|
@ -17,8 +17,10 @@ import urllib
|
|||||||
from random import random
|
from random import random
|
||||||
from time import time
|
from time import time
|
||||||
from os.path import join
|
from os.path import join
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from eventlet import sleep, Timeout
|
from eventlet import sleep, Timeout
|
||||||
|
from eventlet.greenpool import GreenPool
|
||||||
|
|
||||||
from swift.common.daemon import Daemon
|
from swift.common.daemon import Daemon
|
||||||
from swift.common.internal_client import InternalClient
|
from swift.common.internal_client import InternalClient
|
||||||
@ -53,6 +55,11 @@ class ObjectExpirer(Daemon):
|
|||||||
self.recon_cache_path = conf.get('recon_cache_path',
|
self.recon_cache_path = conf.get('recon_cache_path',
|
||||||
'/var/cache/swift')
|
'/var/cache/swift')
|
||||||
self.rcache = join(self.recon_cache_path, 'object.recon')
|
self.rcache = join(self.recon_cache_path, 'object.recon')
|
||||||
|
self.concurrency = int(conf.get('concurrency', 1))
|
||||||
|
if self.concurrency < 1:
|
||||||
|
raise ValueError("concurrency must be set to at least 1")
|
||||||
|
self.processes = int(self.conf.get('processes', 0))
|
||||||
|
self.process = int(self.conf.get('process', 0))
|
||||||
|
|
||||||
def report(self, final=False):
|
def report(self, final=False):
|
||||||
"""
|
"""
|
||||||
@ -82,8 +89,13 @@ class ObjectExpirer(Daemon):
|
|||||||
:param args: Extra args to fulfill the Daemon interface; this daemon
|
:param args: Extra args to fulfill the Daemon interface; this daemon
|
||||||
has no additional args.
|
has no additional args.
|
||||||
:param kwargs: Extra keyword args to fulfill the Daemon interface; this
|
:param kwargs: Extra keyword args to fulfill the Daemon interface; this
|
||||||
daemon has no additional keyword args.
|
daemon accepts processes and process keyword args.
|
||||||
|
These will override the values from the config file if
|
||||||
|
provided.
|
||||||
"""
|
"""
|
||||||
|
processes, process = self.get_process_values(kwargs)
|
||||||
|
pool = GreenPool(self.concurrency)
|
||||||
|
containers_to_delete = []
|
||||||
self.report_first_time = self.report_last_time = time()
|
self.report_first_time = self.report_last_time = time()
|
||||||
self.report_objects = 0
|
self.report_objects = 0
|
||||||
try:
|
try:
|
||||||
@ -97,27 +109,25 @@ class ObjectExpirer(Daemon):
|
|||||||
timestamp = int(container)
|
timestamp = int(container)
|
||||||
if timestamp > int(time()):
|
if timestamp > int(time()):
|
||||||
break
|
break
|
||||||
|
containers_to_delete.append(container)
|
||||||
for o in self.swift.iter_objects(self.expiring_objects_account,
|
for o in self.swift.iter_objects(self.expiring_objects_account,
|
||||||
container):
|
container):
|
||||||
obj = o['name'].encode('utf8')
|
obj = o['name'].encode('utf8')
|
||||||
|
if processes > 0:
|
||||||
|
obj_process = int(
|
||||||
|
hashlib.md5('%s/%s' % (container, obj)).
|
||||||
|
hexdigest(), 16)
|
||||||
|
if obj_process % processes != process:
|
||||||
|
continue
|
||||||
timestamp, actual_obj = obj.split('-', 1)
|
timestamp, actual_obj = obj.split('-', 1)
|
||||||
timestamp = int(timestamp)
|
timestamp = int(timestamp)
|
||||||
if timestamp > int(time()):
|
if timestamp > int(time()):
|
||||||
break
|
break
|
||||||
start_time = time()
|
pool.spawn_n(
|
||||||
try:
|
self.delete_object, actual_obj, timestamp,
|
||||||
self.delete_actual_object(actual_obj, timestamp)
|
|
||||||
self.swift.delete_object(self.expiring_objects_account,
|
|
||||||
container, obj)
|
container, obj)
|
||||||
self.report_objects += 1
|
pool.waitall()
|
||||||
self.logger.increment('objects')
|
for container in containers_to_delete:
|
||||||
except (Exception, Timeout), err:
|
|
||||||
self.logger.increment('errors')
|
|
||||||
self.logger.exception(
|
|
||||||
_('Exception while deleting object %s %s %s') %
|
|
||||||
(container, obj, str(err)))
|
|
||||||
self.logger.timing_since('timing', start_time)
|
|
||||||
self.report()
|
|
||||||
try:
|
try:
|
||||||
self.swift.delete_container(
|
self.swift.delete_container(
|
||||||
self.expiring_objects_account,
|
self.expiring_objects_account,
|
||||||
@ -145,13 +155,63 @@ class ObjectExpirer(Daemon):
|
|||||||
while True:
|
while True:
|
||||||
begin = time()
|
begin = time()
|
||||||
try:
|
try:
|
||||||
self.run_once()
|
self.run_once(*args, **kwargs)
|
||||||
except (Exception, Timeout):
|
except (Exception, Timeout):
|
||||||
self.logger.exception(_('Unhandled exception'))
|
self.logger.exception(_('Unhandled exception'))
|
||||||
elapsed = time() - begin
|
elapsed = time() - begin
|
||||||
if elapsed < self.interval:
|
if elapsed < self.interval:
|
||||||
sleep(random() * (self.interval - elapsed))
|
sleep(random() * (self.interval - elapsed))
|
||||||
|
|
||||||
|
def get_process_values(self, kwargs):
|
||||||
|
"""
|
||||||
|
Gets the processes, process from the kwargs if those values exist.
|
||||||
|
|
||||||
|
Otherwise, return processes, process set in the config file.
|
||||||
|
|
||||||
|
:param kwargs: Keyword args passed into the run_forever(), run_once()
|
||||||
|
methods. They have values specified on the command
|
||||||
|
line when the daemon is run.
|
||||||
|
"""
|
||||||
|
if kwargs.get('processes') is not None:
|
||||||
|
processes = int(kwargs['processes'])
|
||||||
|
else:
|
||||||
|
processes = self.processes
|
||||||
|
|
||||||
|
if kwargs.get('process') is not None:
|
||||||
|
process = int(kwargs['process'])
|
||||||
|
else:
|
||||||
|
process = self.process
|
||||||
|
|
||||||
|
if process < 0:
|
||||||
|
raise ValueError(
|
||||||
|
'process must be an integer greater than or equal to 0')
|
||||||
|
|
||||||
|
if processes < 0:
|
||||||
|
raise ValueError(
|
||||||
|
'processes must be an integer greater than or equal to 0')
|
||||||
|
|
||||||
|
if processes and process >= processes:
|
||||||
|
raise ValueError(
|
||||||
|
'process must be less than or equal to processes')
|
||||||
|
|
||||||
|
return processes, process
|
||||||
|
|
||||||
|
def delete_object(self, actual_obj, timestamp, container, obj):
|
||||||
|
start_time = time()
|
||||||
|
try:
|
||||||
|
self.delete_actual_object(actual_obj, timestamp)
|
||||||
|
self.swift.delete_object(self.expiring_objects_account,
|
||||||
|
container, obj)
|
||||||
|
self.report_objects += 1
|
||||||
|
self.logger.increment('objects')
|
||||||
|
except (Exception, Timeout), err:
|
||||||
|
self.logger.increment('errors')
|
||||||
|
self.logger.exception(
|
||||||
|
_('Exception while deleting object %s %s %s') %
|
||||||
|
(container, obj, str(err)))
|
||||||
|
self.logger.timing_since('timing', start_time)
|
||||||
|
self.report()
|
||||||
|
|
||||||
def delete_actual_object(self, actual_obj, timestamp):
|
def delete_actual_object(self, actual_obj, timestamp):
|
||||||
"""
|
"""
|
||||||
Deletes the end-user object indicated by the actual object name given
|
Deletes the end-user object indicated by the actual object name given
|
||||||
|
@ -17,6 +17,7 @@ import urllib
|
|||||||
from time import time
|
from time import time
|
||||||
from unittest import main, TestCase
|
from unittest import main, TestCase
|
||||||
from test.unit import FakeLogger
|
from test.unit import FakeLogger
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
@ -52,12 +53,164 @@ class TestObjectExpirer(TestCase):
|
|||||||
internal_client.sleep = self.old_sleep
|
internal_client.sleep = self.old_sleep
|
||||||
internal_client.loadapp = self.loadapp
|
internal_client.loadapp = self.loadapp
|
||||||
|
|
||||||
|
def test_get_process_values_from_kwargs(self):
|
||||||
|
x = expirer.ObjectExpirer({})
|
||||||
|
vals = {
|
||||||
|
'processes': 5,
|
||||||
|
'process': 1,
|
||||||
|
}
|
||||||
|
self.assertEqual((5, 1), x.get_process_values(vals))
|
||||||
|
|
||||||
|
def test_get_process_values_from_config(self):
|
||||||
|
vals = {
|
||||||
|
'processes': 5,
|
||||||
|
'process': 1,
|
||||||
|
}
|
||||||
|
x = expirer.ObjectExpirer(vals)
|
||||||
|
self.assertEqual((5, 1), x.get_process_values({}))
|
||||||
|
|
||||||
|
def test_get_process_values_negative_process(self):
|
||||||
|
vals = {
|
||||||
|
'processes': 5,
|
||||||
|
'process': -1,
|
||||||
|
}
|
||||||
|
# from config
|
||||||
|
x = expirer.ObjectExpirer(vals)
|
||||||
|
self.assertRaises(ValueError, x.get_process_values, {})
|
||||||
|
# from kwargs
|
||||||
|
x = expirer.ObjectExpirer({})
|
||||||
|
self.assertRaises(ValueError, x.get_process_values, vals)
|
||||||
|
|
||||||
|
def test_get_process_values_negative_processes(self):
|
||||||
|
vals = {
|
||||||
|
'processes': -5,
|
||||||
|
'process': 1,
|
||||||
|
}
|
||||||
|
# from config
|
||||||
|
x = expirer.ObjectExpirer(vals)
|
||||||
|
self.assertRaises(ValueError, x.get_process_values, {})
|
||||||
|
# from kwargs
|
||||||
|
x = expirer.ObjectExpirer({})
|
||||||
|
self.assertRaises(ValueError, x.get_process_values, vals)
|
||||||
|
|
||||||
|
def test_get_process_values_process_greater_than_processes(self):
|
||||||
|
vals = {
|
||||||
|
'processes': 5,
|
||||||
|
'process': 7,
|
||||||
|
}
|
||||||
|
# from config
|
||||||
|
x = expirer.ObjectExpirer(vals)
|
||||||
|
self.assertRaises(ValueError, x.get_process_values, {})
|
||||||
|
# from kwargs
|
||||||
|
x = expirer.ObjectExpirer({})
|
||||||
|
self.assertRaises(ValueError, x.get_process_values, vals)
|
||||||
|
|
||||||
|
def test_init_concurrency_too_small(self):
|
||||||
|
conf = {
|
||||||
|
'concurrency': 0,
|
||||||
|
}
|
||||||
|
self.assertRaises(ValueError, expirer.ObjectExpirer, conf)
|
||||||
|
conf = {
|
||||||
|
'concurrency': -1,
|
||||||
|
}
|
||||||
|
self.assertRaises(ValueError, expirer.ObjectExpirer, conf)
|
||||||
|
|
||||||
|
def test_process_based_concurrency(self):
|
||||||
|
class ObjectExpirer(expirer.ObjectExpirer):
|
||||||
|
def __init__(self, conf):
|
||||||
|
super(ObjectExpirer, self).__init__(conf)
|
||||||
|
self.processes = 3
|
||||||
|
self.deleted_objects = {}
|
||||||
|
|
||||||
|
def delete_object(self, actual_obj, timestamp, container, obj):
|
||||||
|
if not container in self.deleted_objects:
|
||||||
|
self.deleted_objects[container] = set()
|
||||||
|
self.deleted_objects[container].add(obj)
|
||||||
|
|
||||||
|
class InternalClient(object):
|
||||||
|
def __init__(self, containers):
|
||||||
|
self.containers = containers
|
||||||
|
|
||||||
|
def get_account_info(self, *a, **kw):
|
||||||
|
return len(self.containers.keys()), \
|
||||||
|
sum([len(self.containers[x]) for x in self.containers])
|
||||||
|
|
||||||
|
def iter_containers(self, *a, **kw):
|
||||||
|
return [{'name': x} for x in self.containers.keys()]
|
||||||
|
|
||||||
|
def iter_objects(self, account, container):
|
||||||
|
return [{'name': x} for x in self.containers[container]]
|
||||||
|
|
||||||
|
def delete_container(*a, **kw):
|
||||||
|
pass
|
||||||
|
|
||||||
|
containers = {
|
||||||
|
0: set('1-one 2-two 3-three'.split()),
|
||||||
|
1: set('2-two 3-three 4-four'.split()),
|
||||||
|
2: set('5-five 6-six'.split()),
|
||||||
|
3: set('7-seven'.split()),
|
||||||
|
}
|
||||||
|
x = ObjectExpirer({})
|
||||||
|
x.swift = InternalClient(containers)
|
||||||
|
|
||||||
|
deleted_objects = {}
|
||||||
|
for i in xrange(0, 3):
|
||||||
|
x.process = i
|
||||||
|
x.run_once()
|
||||||
|
self.assertNotEqual(deleted_objects, x.deleted_objects)
|
||||||
|
deleted_objects = deepcopy(x.deleted_objects)
|
||||||
|
self.assertEqual(containers, deleted_objects)
|
||||||
|
|
||||||
|
def test_delete_object(self):
|
||||||
|
class InternalClient(object):
|
||||||
|
def __init__(self, test, account, container, obj):
|
||||||
|
self.test = test
|
||||||
|
self.account = account
|
||||||
|
self.container = container
|
||||||
|
self.obj = obj
|
||||||
|
self.delete_object_called = False
|
||||||
|
|
||||||
|
def delete_object(self, account, container, obj):
|
||||||
|
self.test.assertEqual(self.account, account)
|
||||||
|
self.test.assertEqual(self.container, container)
|
||||||
|
self.test.assertEqual(self.obj, obj)
|
||||||
|
self.delete_object_called = True
|
||||||
|
|
||||||
|
class DeleteActualObject(object):
|
||||||
|
def __init__(self, test, actual_obj, timestamp):
|
||||||
|
self.test = test
|
||||||
|
self.actual_obj = actual_obj
|
||||||
|
self.timestamp = timestamp
|
||||||
|
self.called = False
|
||||||
|
|
||||||
|
def __call__(self, actual_obj, timestamp):
|
||||||
|
self.test.assertEqual(self.actual_obj, actual_obj)
|
||||||
|
self.test.assertEqual(self.timestamp, timestamp)
|
||||||
|
self.called = True
|
||||||
|
|
||||||
|
account = 'account'
|
||||||
|
container = 'container'
|
||||||
|
obj = 'obj'
|
||||||
|
actual_obj = 'actual_obj'
|
||||||
|
timestamp = 'timestamp'
|
||||||
|
|
||||||
|
x = expirer.ObjectExpirer({})
|
||||||
|
x.logger = FakeLogger()
|
||||||
|
x.swift = \
|
||||||
|
InternalClient(self, x.expiring_objects_account, container, obj)
|
||||||
|
x.delete_actual_object = \
|
||||||
|
DeleteActualObject(self, actual_obj, timestamp)
|
||||||
|
|
||||||
|
x.delete_object(actual_obj, timestamp, container, obj)
|
||||||
|
self.assertTrue(x.swift.delete_object_called)
|
||||||
|
self.assertTrue(x.delete_actual_object.called)
|
||||||
|
|
||||||
def test_report(self):
|
def test_report(self):
|
||||||
x = expirer.ObjectExpirer({})
|
x = expirer.ObjectExpirer({})
|
||||||
x.logger = FakeLogger()
|
x.logger = FakeLogger()
|
||||||
|
|
||||||
x.report()
|
x.report()
|
||||||
self.assertEquals(x.logger.log_dict['info'], [])
|
self.assertEqual(x.logger.log_dict['info'], [])
|
||||||
|
|
||||||
x.logger._clear()
|
x.logger._clear()
|
||||||
x.report(final=True)
|
x.report(final=True)
|
||||||
@ -79,7 +232,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
x.logger = FakeLogger()
|
x.logger = FakeLogger()
|
||||||
x.swift = 'throw error because a string does not have needed methods'
|
x.swift = 'throw error because a string does not have needed methods'
|
||||||
x.run_once()
|
x.run_once()
|
||||||
self.assertEquals(x.logger.log_dict['exception'],
|
self.assertEqual(x.logger.log_dict['exception'],
|
||||||
[(("Unhandled exception",), {},
|
[(("Unhandled exception",), {},
|
||||||
"'str' object has no attribute "
|
"'str' object has no attribute "
|
||||||
"'get_account_info'")])
|
"'get_account_info'")])
|
||||||
@ -96,7 +249,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
x.logger = FakeLogger()
|
x.logger = FakeLogger()
|
||||||
x.swift = InternalClient()
|
x.swift = InternalClient()
|
||||||
x.run_once()
|
x.run_once()
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
x.logger.log_dict['info'],
|
x.logger.log_dict['info'],
|
||||||
[(('Pass beginning; 1 possible containers; '
|
[(('Pass beginning; 1 possible containers; '
|
||||||
'2 possible objects',), {}),
|
'2 possible objects',), {}),
|
||||||
@ -123,7 +276,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
for exccall in x.logger.log_dict['exception']:
|
for exccall in x.logger.log_dict['exception']:
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'This should not have been called' not in exccall[0][0])
|
'This should not have been called' not in exccall[0][0])
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
x.logger.log_dict['info'],
|
x.logger.log_dict['info'],
|
||||||
[(('Pass beginning; 1 possible containers; '
|
[(('Pass beginning; 1 possible containers; '
|
||||||
'2 possible objects',), {}),
|
'2 possible objects',), {}),
|
||||||
@ -134,7 +287,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
x.logger = FakeLogger()
|
x.logger = FakeLogger()
|
||||||
x.swift = InternalClient([{'name': str(int(time() - 86400))}])
|
x.swift = InternalClient([{'name': str(int(time() - 86400))}])
|
||||||
x.run_once()
|
x.run_once()
|
||||||
self.assertEquals(x.logger.log_dict['exception'],
|
self.assertEqual(x.logger.log_dict['exception'],
|
||||||
[(('Unhandled exception',), {},
|
[(('Unhandled exception',), {},
|
||||||
str(Exception('This should not have been called')))])
|
str(Exception('This should not have been called')))])
|
||||||
|
|
||||||
@ -167,7 +320,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
for exccall in x.logger.log_dict['exception']:
|
for exccall in x.logger.log_dict['exception']:
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'This should not have been called' not in exccall[0][0])
|
'This should not have been called' not in exccall[0][0])
|
||||||
self.assertEquals(x.logger.log_dict['info'],
|
self.assertEqual(x.logger.log_dict['info'],
|
||||||
[(('Pass beginning; 1 possible containers; '
|
[(('Pass beginning; 1 possible containers; '
|
||||||
'2 possible objects',), {}),
|
'2 possible objects',), {}),
|
||||||
(('Pass completed in 0s; 0 objects expired',), {})])
|
(('Pass completed in 0s; 0 objects expired',), {})])
|
||||||
@ -184,7 +337,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
for exccall in x.logger.log_dict['exception']:
|
for exccall in x.logger.log_dict['exception']:
|
||||||
if exccall[0][0].startswith('Exception while deleting '):
|
if exccall[0][0].startswith('Exception while deleting '):
|
||||||
excswhiledeleting.append(exccall[0][0])
|
excswhiledeleting.append(exccall[0][0])
|
||||||
self.assertEquals(excswhiledeleting,
|
self.assertEqual(excswhiledeleting,
|
||||||
['Exception while deleting object %d %d-actual-obj '
|
['Exception while deleting object %d %d-actual-obj '
|
||||||
'This should not have been called' % (ts, ts)])
|
'This should not have been called' % (ts, ts)])
|
||||||
|
|
||||||
@ -227,10 +380,10 @@ class TestObjectExpirer(TestCase):
|
|||||||
for exccall in x.logger.log_dict['exception']:
|
for exccall in x.logger.log_dict['exception']:
|
||||||
if exccall[0][0].startswith('Exception while deleting '):
|
if exccall[0][0].startswith('Exception while deleting '):
|
||||||
excswhiledeleting.append(exccall[0][0])
|
excswhiledeleting.append(exccall[0][0])
|
||||||
self.assertEquals(excswhiledeleting,
|
self.assertEqual(excswhiledeleting,
|
||||||
['Exception while deleting object %d %d-actual-obj '
|
['Exception while deleting object %d %d-actual-obj '
|
||||||
'failed to delete actual object' % (ts, ts)])
|
'failed to delete actual object' % (ts, ts)])
|
||||||
self.assertEquals(x.logger.log_dict['info'],
|
self.assertEqual(x.logger.log_dict['info'],
|
||||||
[(('Pass beginning; 1 possible containers; '
|
[(('Pass beginning; 1 possible containers; '
|
||||||
'2 possible objects',), {}),
|
'2 possible objects',), {}),
|
||||||
(('Pass completed in 0s; 0 objects expired',), {})])
|
(('Pass completed in 0s; 0 objects expired',), {})])
|
||||||
@ -247,7 +400,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
for exccall in x.logger.log_dict['exception']:
|
for exccall in x.logger.log_dict['exception']:
|
||||||
if exccall[0][0].startswith('Exception while deleting '):
|
if exccall[0][0].startswith('Exception while deleting '):
|
||||||
excswhiledeleting.append(exccall[0][0])
|
excswhiledeleting.append(exccall[0][0])
|
||||||
self.assertEquals(excswhiledeleting,
|
self.assertEqual(excswhiledeleting,
|
||||||
['Exception while deleting object %d %d-actual-obj This should '
|
['Exception while deleting object %d %d-actual-obj This should '
|
||||||
'not have been called' % (ts, ts)])
|
'not have been called' % (ts, ts)])
|
||||||
|
|
||||||
@ -275,12 +428,12 @@ class TestObjectExpirer(TestCase):
|
|||||||
x = expirer.ObjectExpirer({})
|
x = expirer.ObjectExpirer({})
|
||||||
x.logger = FakeLogger()
|
x.logger = FakeLogger()
|
||||||
x.delete_actual_object = lambda o, t: None
|
x.delete_actual_object = lambda o, t: None
|
||||||
self.assertEquals(x.report_objects, 0)
|
self.assertEqual(x.report_objects, 0)
|
||||||
x.swift = InternalClient([{'name': str(int(time() - 86400))}],
|
x.swift = InternalClient([{'name': str(int(time() - 86400))}],
|
||||||
[{'name': '%d-actual-obj' % int(time() - 86400)}])
|
[{'name': '%d-actual-obj' % int(time() - 86400)}])
|
||||||
x.run_once()
|
x.run_once()
|
||||||
self.assertEquals(x.report_objects, 1)
|
self.assertEqual(x.report_objects, 1)
|
||||||
self.assertEquals(x.logger.log_dict['info'],
|
self.assertEqual(x.logger.log_dict['info'],
|
||||||
[(('Pass beginning; 1 possible containers; '
|
[(('Pass beginning; 1 possible containers; '
|
||||||
'2 possible objects',), {}),
|
'2 possible objects',), {}),
|
||||||
(('Pass completed in 0s; 1 objects expired',), {})])
|
(('Pass completed in 0s; 1 objects expired',), {})])
|
||||||
@ -315,12 +468,12 @@ class TestObjectExpirer(TestCase):
|
|||||||
x = expirer.ObjectExpirer({})
|
x = expirer.ObjectExpirer({})
|
||||||
x.logger = FakeLogger()
|
x.logger = FakeLogger()
|
||||||
x.delete_actual_object = delete_actual_object_test_for_unicode
|
x.delete_actual_object = delete_actual_object_test_for_unicode
|
||||||
self.assertEquals(x.report_objects, 0)
|
self.assertEqual(x.report_objects, 0)
|
||||||
x.swift = InternalClient([{'name': str(int(time() - 86400))}],
|
x.swift = InternalClient([{'name': str(int(time() - 86400))}],
|
||||||
[{'name': u'%d-actual-obj' % int(time() - 86400)}])
|
[{'name': u'%d-actual-obj' % int(time() - 86400)}])
|
||||||
x.run_once()
|
x.run_once()
|
||||||
self.assertEquals(x.report_objects, 1)
|
self.assertEqual(x.report_objects, 1)
|
||||||
self.assertEquals(x.logger.log_dict['info'],
|
self.assertEqual(x.logger.log_dict['info'],
|
||||||
[(('Pass beginning; 1 possible containers; '
|
[(('Pass beginning; 1 possible containers; '
|
||||||
'2 possible objects',), {}),
|
'2 possible objects',), {}),
|
||||||
(('Pass completed in 0s; 1 objects expired',), {})])
|
(('Pass completed in 0s; 1 objects expired',), {})])
|
||||||
@ -373,20 +526,20 @@ class TestObjectExpirer(TestCase):
|
|||||||
for exccall in x.logger.log_dict['exception']:
|
for exccall in x.logger.log_dict['exception']:
|
||||||
if exccall[0][0].startswith('Exception while deleting '):
|
if exccall[0][0].startswith('Exception while deleting '):
|
||||||
excswhiledeleting.append(exccall[0][0])
|
excswhiledeleting.append(exccall[0][0])
|
||||||
self.assertEquals(excswhiledeleting, [
|
self.assertEqual(sorted(excswhiledeleting), sorted([
|
||||||
'Exception while deleting object %d %d-actual-obj failed to '
|
'Exception while deleting object %d %d-actual-obj failed to '
|
||||||
'delete actual object' % (cts, ots),
|
'delete actual object' % (cts, ots),
|
||||||
'Exception while deleting object %d %d-next-obj failed to '
|
'Exception while deleting object %d %d-next-obj failed to '
|
||||||
'delete actual object' % (cts, ots),
|
'delete actual object' % (cts, ots),
|
||||||
|
'Exception while deleting object %d %d-actual-obj failed to '
|
||||||
|
'delete actual object' % (cts + 1, ots),
|
||||||
|
'Exception while deleting object %d %d-next-obj failed to '
|
||||||
|
'delete actual object' % (cts + 1, ots),
|
||||||
'Exception while deleting container %d failed to delete '
|
'Exception while deleting container %d failed to delete '
|
||||||
'container' % (cts,),
|
'container' % (cts,),
|
||||||
'Exception while deleting object %d %d-actual-obj failed to '
|
|
||||||
'delete actual object' % (cts + 1, ots),
|
|
||||||
'Exception while deleting object %d %d-next-obj failed to '
|
|
||||||
'delete actual object' % (cts + 1, ots),
|
|
||||||
'Exception while deleting container %d failed to delete '
|
'Exception while deleting container %d failed to delete '
|
||||||
'container' % (cts + 1,)])
|
'container' % (cts + 1,)]))
|
||||||
self.assertEquals(x.logger.log_dict['info'],
|
self.assertEqual(x.logger.log_dict['info'],
|
||||||
[(('Pass beginning; 1 possible containers; '
|
[(('Pass beginning; 1 possible containers; '
|
||||||
'2 possible objects',), {}),
|
'2 possible objects',), {}),
|
||||||
(('Pass completed in 0s; 0 objects expired',), {})])
|
(('Pass completed in 0s; 0 objects expired',), {})])
|
||||||
@ -412,8 +565,8 @@ class TestObjectExpirer(TestCase):
|
|||||||
finally:
|
finally:
|
||||||
expirer.random = orig_random
|
expirer.random = orig_random
|
||||||
expirer.sleep = orig_sleep
|
expirer.sleep = orig_sleep
|
||||||
self.assertEquals(str(err), 'test_run_forever')
|
self.assertEqual(str(err), 'test_run_forever')
|
||||||
self.assertEquals(last_not_sleep, 0.5 * interval)
|
self.assertEqual(last_not_sleep, 0.5 * interval)
|
||||||
|
|
||||||
def test_run_forever_catches_usual_exceptions(self):
|
def test_run_forever_catches_usual_exceptions(self):
|
||||||
raises = [0]
|
raises = [0]
|
||||||
@ -435,8 +588,8 @@ class TestObjectExpirer(TestCase):
|
|||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
expirer.sleep = orig_sleep
|
expirer.sleep = orig_sleep
|
||||||
self.assertEquals(str(err), 'exiting exception 2')
|
self.assertEqual(str(err), 'exiting exception 2')
|
||||||
self.assertEquals(x.logger.log_dict['exception'],
|
self.assertEqual(x.logger.log_dict['exception'],
|
||||||
[(('Unhandled exception',), {},
|
[(('Unhandled exception',), {},
|
||||||
'exception 1')])
|
'exception 1')])
|
||||||
|
|
||||||
@ -453,7 +606,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
x = expirer.ObjectExpirer({})
|
x = expirer.ObjectExpirer({})
|
||||||
ts = '1234'
|
ts = '1234'
|
||||||
x.delete_actual_object('/path/to/object', ts)
|
x.delete_actual_object('/path/to/object', ts)
|
||||||
self.assertEquals(got_env[0]['HTTP_X_IF_DELETE_AT'], ts)
|
self.assertEqual(got_env[0]['HTTP_X_IF_DELETE_AT'], ts)
|
||||||
|
|
||||||
def test_delete_actual_object_nourlquoting(self):
|
def test_delete_actual_object_nourlquoting(self):
|
||||||
# delete_actual_object should not do its own url quoting because
|
# delete_actual_object should not do its own url quoting because
|
||||||
@ -470,8 +623,8 @@ class TestObjectExpirer(TestCase):
|
|||||||
x = expirer.ObjectExpirer({})
|
x = expirer.ObjectExpirer({})
|
||||||
ts = '1234'
|
ts = '1234'
|
||||||
x.delete_actual_object('/path/to/object name', ts)
|
x.delete_actual_object('/path/to/object name', ts)
|
||||||
self.assertEquals(got_env[0]['HTTP_X_IF_DELETE_AT'], ts)
|
self.assertEqual(got_env[0]['HTTP_X_IF_DELETE_AT'], ts)
|
||||||
self.assertEquals(got_env[0]['PATH_INFO'], '/v1/path/to/object name')
|
self.assertEqual(got_env[0]['PATH_INFO'], '/v1/path/to/object name')
|
||||||
|
|
||||||
def test_delete_actual_object_handles_404(self):
|
def test_delete_actual_object_handles_404(self):
|
||||||
|
|
||||||
@ -513,7 +666,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
exc = err
|
exc = err
|
||||||
finally:
|
finally:
|
||||||
pass
|
pass
|
||||||
self.assertEquals(503, exc.resp.status_int)
|
self.assertEqual(503, exc.resp.status_int)
|
||||||
|
|
||||||
def test_delete_actual_object_quotes(self):
|
def test_delete_actual_object_quotes(self):
|
||||||
name = 'this name should get quoted'
|
name = 'this name should get quoted'
|
||||||
@ -522,7 +675,7 @@ class TestObjectExpirer(TestCase):
|
|||||||
x.swift.make_request = mock.MagicMock()
|
x.swift.make_request = mock.MagicMock()
|
||||||
x.delete_actual_object(name, timestamp)
|
x.delete_actual_object(name, timestamp)
|
||||||
x.swift.make_request.assert_called_once()
|
x.swift.make_request.assert_called_once()
|
||||||
self.assertEquals(x.swift.make_request.call_args[0][1],
|
self.assertEqual(x.swift.make_request.call_args[0][1],
|
||||||
'/v1/' + urllib.quote(name))
|
'/v1/' + urllib.quote(name))
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user