Merge "Remove empty part dirs during ssync replication"
This commit is contained in:
commit
3de21d945b
@ -101,6 +101,14 @@ MIN_TIME_UPDATE_AUDITOR_STATUS = 60
|
|||||||
RE_RSYNC_TEMPFILE = re.compile(r'^\..*\.([a-zA-Z0-9_]){6}$')
|
RE_RSYNC_TEMPFILE = re.compile(r'^\..*\.([a-zA-Z0-9_]){6}$')
|
||||||
|
|
||||||
|
|
||||||
|
def _unlink_if_present(filename):
|
||||||
|
try:
|
||||||
|
os.unlink(filename)
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno != errno.ENOENT:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def _get_filename(fd):
|
def _get_filename(fd):
|
||||||
"""
|
"""
|
||||||
Helper function to get to file name from a file descriptor or filename.
|
Helper function to get to file name from a file descriptor or filename.
|
||||||
@ -1023,7 +1031,17 @@ class BaseDiskFileManager(object):
|
|||||||
def is_reclaimable(timestamp):
|
def is_reclaimable(timestamp):
|
||||||
return (time.time() - float(timestamp)) > self.reclaim_age
|
return (time.time() - float(timestamp)) > self.reclaim_age
|
||||||
|
|
||||||
files = listdir(hsh_path)
|
try:
|
||||||
|
files = os.listdir(hsh_path)
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno == errno.ENOENT:
|
||||||
|
results = self.get_ondisk_files(
|
||||||
|
[], hsh_path, verify=False, **kwargs)
|
||||||
|
results['files'] = []
|
||||||
|
return results
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
files.sort(reverse=True)
|
files.sort(reverse=True)
|
||||||
results = self.get_ondisk_files(
|
results = self.get_ondisk_files(
|
||||||
files, hsh_path, verify=False, **kwargs)
|
files, hsh_path, verify=False, **kwargs)
|
||||||
@ -1039,6 +1057,11 @@ class BaseDiskFileManager(object):
|
|||||||
remove_file(join(hsh_path, file_info['filename']))
|
remove_file(join(hsh_path, file_info['filename']))
|
||||||
files.remove(file_info['filename'])
|
files.remove(file_info['filename'])
|
||||||
results['files'] = files
|
results['files'] = files
|
||||||
|
if not files: # everything got unlinked
|
||||||
|
try:
|
||||||
|
os.rmdir(hsh_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def _update_suffix_hashes(self, hashes, ondisk_info):
|
def _update_suffix_hashes(self, hashes, ondisk_info):
|
||||||
@ -1081,10 +1104,6 @@ class BaseDiskFileManager(object):
|
|||||||
continue
|
continue
|
||||||
raise
|
raise
|
||||||
if not ondisk_info['files']:
|
if not ondisk_info['files']:
|
||||||
try:
|
|
||||||
os.rmdir(hsh_path)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# ondisk_info has info dicts containing timestamps for those
|
# ondisk_info has info dicts containing timestamps for those
|
||||||
@ -1486,25 +1505,37 @@ class BaseDiskFileManager(object):
|
|||||||
dev_path = self.get_dev_path(device)
|
dev_path = self.get_dev_path(device)
|
||||||
if not dev_path:
|
if not dev_path:
|
||||||
raise DiskFileDeviceUnavailable()
|
raise DiskFileDeviceUnavailable()
|
||||||
|
|
||||||
|
partition_path = get_part_path(dev_path, policy, partition)
|
||||||
if suffixes is None:
|
if suffixes is None:
|
||||||
suffixes = self.yield_suffixes(device, partition, policy)
|
suffixes = self.yield_suffixes(device, partition, policy)
|
||||||
|
considering_all_suffixes = True
|
||||||
else:
|
else:
|
||||||
partition_path = get_part_path(dev_path, policy, partition)
|
|
||||||
suffixes = (
|
suffixes = (
|
||||||
(os.path.join(partition_path, suffix), suffix)
|
(os.path.join(partition_path, suffix), suffix)
|
||||||
for suffix in suffixes)
|
for suffix in suffixes)
|
||||||
|
considering_all_suffixes = False
|
||||||
|
|
||||||
key_preference = (
|
key_preference = (
|
||||||
('ts_meta', 'meta_info', 'timestamp'),
|
('ts_meta', 'meta_info', 'timestamp'),
|
||||||
('ts_data', 'data_info', 'timestamp'),
|
('ts_data', 'data_info', 'timestamp'),
|
||||||
('ts_data', 'ts_info', 'timestamp'),
|
('ts_data', 'ts_info', 'timestamp'),
|
||||||
('ts_ctype', 'ctype_info', 'ctype_timestamp'),
|
('ts_ctype', 'ctype_info', 'ctype_timestamp'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# We delete as many empty directories as we can.
|
||||||
|
# cleanup_ondisk_files() takes care of the hash dirs, and we take
|
||||||
|
# care of the suffix dirs and possibly even the partition dir.
|
||||||
|
have_nonempty_suffix = False
|
||||||
for suffix_path, suffix in suffixes:
|
for suffix_path, suffix in suffixes:
|
||||||
|
have_nonempty_hashdir = False
|
||||||
for object_hash in self._listdir(suffix_path):
|
for object_hash in self._listdir(suffix_path):
|
||||||
object_path = os.path.join(suffix_path, object_hash)
|
object_path = os.path.join(suffix_path, object_hash)
|
||||||
try:
|
try:
|
||||||
results = self.cleanup_ondisk_files(
|
results = self.cleanup_ondisk_files(
|
||||||
object_path, **kwargs)
|
object_path, **kwargs)
|
||||||
|
if results['files']:
|
||||||
|
have_nonempty_hashdir = True
|
||||||
timestamps = {}
|
timestamps = {}
|
||||||
for ts_key, info_key, info_ts_key in key_preference:
|
for ts_key, info_key, info_ts_key in key_preference:
|
||||||
if info_key not in results:
|
if info_key not in results:
|
||||||
@ -1524,6 +1555,34 @@ class BaseDiskFileManager(object):
|
|||||||
'Invalid diskfile filename in %r (%s)' % (
|
'Invalid diskfile filename in %r (%s)' % (
|
||||||
object_path, err))
|
object_path, err))
|
||||||
|
|
||||||
|
if have_nonempty_hashdir:
|
||||||
|
have_nonempty_suffix = True
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.rmdir(suffix_path)
|
||||||
|
except OSError:
|
||||||
|
# cleanup_ondisk_files tries to remove empty hash dirs,
|
||||||
|
# but if it fails, so will we. An empty directory
|
||||||
|
# structure won't cause errors (just slowdown), so we
|
||||||
|
# ignore the exception.
|
||||||
|
pass
|
||||||
|
if considering_all_suffixes and not have_nonempty_suffix:
|
||||||
|
# There's nothing of interest in the partition, so delete it
|
||||||
|
try:
|
||||||
|
# Remove hashes.pkl *then* hashes.invalid; otherwise, if we
|
||||||
|
# remove hashes.invalid but leave hashes.pkl, that makes it
|
||||||
|
# look as though the invalidations in hashes.invalid never
|
||||||
|
# occurred.
|
||||||
|
_unlink_if_present(os.path.join(partition_path, HASH_FILE))
|
||||||
|
_unlink_if_present(os.path.join(partition_path,
|
||||||
|
HASH_INVALIDATIONS_FILE))
|
||||||
|
# This lock is only held by people dealing with the hashes
|
||||||
|
# or the hash invalidations, and we've just removed those.
|
||||||
|
_unlink_if_present(os.path.join(partition_path, ".lock"))
|
||||||
|
os.rmdir(partition_path)
|
||||||
|
except OSError as err:
|
||||||
|
self.logger.debug("Error cleaning up empty partition: %s", err)
|
||||||
|
|
||||||
|
|
||||||
class BaseDiskFileWriter(object):
|
class BaseDiskFileWriter(object):
|
||||||
"""
|
"""
|
||||||
|
@ -817,11 +817,15 @@ class DiskFileManagerMixin(BaseDiskFileTestMixin):
|
|||||||
# don't reclaim anything
|
# don't reclaim anything
|
||||||
mock_time.time.return_value = 0.0
|
mock_time.time.return_value = 0.0
|
||||||
class_under_test.cleanup_ondisk_files(hashdir)
|
class_under_test.cleanup_ondisk_files(hashdir)
|
||||||
after_cleanup = set(os.listdir(hashdir))
|
|
||||||
errmsg = "expected %r, got %r for test %r" % (
|
if expected_after_cleanup:
|
||||||
sorted(expected_after_cleanup), sorted(after_cleanup), test
|
after_cleanup = set(os.listdir(hashdir))
|
||||||
)
|
errmsg = "expected %r, got %r for test %r" % (
|
||||||
self.assertEqual(expected_after_cleanup, after_cleanup, errmsg)
|
sorted(expected_after_cleanup), sorted(after_cleanup), test
|
||||||
|
)
|
||||||
|
self.assertEqual(expected_after_cleanup, after_cleanup, errmsg)
|
||||||
|
else:
|
||||||
|
self.assertFalse(os.path.exists(hashdir))
|
||||||
|
|
||||||
def _test_yield_hashes_cleanup(self, scenarios, policy):
|
def _test_yield_hashes_cleanup(self, scenarios, policy):
|
||||||
# opportunistic test to check that yield_hashes cleans up dir using
|
# opportunistic test to check that yield_hashes cleans up dir using
|
||||||
@ -849,11 +853,15 @@ class DiskFileManagerMixin(BaseDiskFileTestMixin):
|
|||||||
'ignored', '0', policy, suffixes=['abc']):
|
'ignored', '0', policy, suffixes=['abc']):
|
||||||
# return values are tested in test_yield_hashes_*
|
# return values are tested in test_yield_hashes_*
|
||||||
pass
|
pass
|
||||||
after_cleanup = set(os.listdir(hashdir))
|
|
||||||
errmsg = "expected %r, got %r for test %r" % (
|
if expected_after_cleanup:
|
||||||
sorted(expected_after_cleanup), sorted(after_cleanup), test
|
after_cleanup = set(os.listdir(hashdir))
|
||||||
)
|
errmsg = "expected %r, got %r for test %r" % (
|
||||||
self.assertEqual(expected_after_cleanup, after_cleanup, errmsg)
|
sorted(expected_after_cleanup), sorted(after_cleanup), test
|
||||||
|
)
|
||||||
|
self.assertEqual(expected_after_cleanup, after_cleanup, errmsg)
|
||||||
|
else:
|
||||||
|
self.assertFalse(os.path.exists(hashdir))
|
||||||
|
|
||||||
def test_get_ondisk_files_with_empty_dir(self):
|
def test_get_ondisk_files_with_empty_dir(self):
|
||||||
files = []
|
files = []
|
||||||
@ -1316,6 +1324,106 @@ class DiskFileManagerMixin(BaseDiskFileTestMixin):
|
|||||||
self.assertEqual(list(self.df_mgr.yield_hashes(
|
self.assertEqual(list(self.df_mgr.yield_hashes(
|
||||||
self.existing_device, '9', POLICIES[0])), [])
|
self.existing_device, '9', POLICIES[0])), [])
|
||||||
|
|
||||||
|
def test_yield_hashes_cleans_up_everything(self):
|
||||||
|
the_time = [1525354555.657585]
|
||||||
|
|
||||||
|
def mock_time():
|
||||||
|
return the_time[0]
|
||||||
|
|
||||||
|
with mock.patch('time.time', mock_time):
|
||||||
|
# Make a couple of (soon-to-be-)expired tombstones
|
||||||
|
df1 = self.df_mgr.get_diskfile(
|
||||||
|
self.existing_device, 0, 'a', 'c', 'o1', POLICIES[0])
|
||||||
|
df1.delete(Timestamp(the_time[0]))
|
||||||
|
df1_hash = utils.hash_path('a', 'c', 'o1')
|
||||||
|
df1_suffix = df1_hash[-3:]
|
||||||
|
|
||||||
|
df2 = self.df_mgr.get_diskfile(
|
||||||
|
self.existing_device, 0, 'a', 'c', 'o2', POLICIES[0])
|
||||||
|
df2.delete(Timestamp(the_time[0] + 1))
|
||||||
|
df2_hash = utils.hash_path('a', 'c', 'o2')
|
||||||
|
df2_suffix = df2_hash[-3:]
|
||||||
|
|
||||||
|
# sanity checks
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0',
|
||||||
|
df1_suffix, df1_hash,
|
||||||
|
"1525354555.65758.ts")))
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0',
|
||||||
|
df2_suffix, df2_hash,
|
||||||
|
"1525354556.65758.ts")))
|
||||||
|
|
||||||
|
# Expire the tombstones
|
||||||
|
the_time[0] += 2 * self.df_mgr.reclaim_age
|
||||||
|
|
||||||
|
hashes = list(self.df_mgr.yield_hashes(
|
||||||
|
self.existing_device, '0', POLICIES[0]))
|
||||||
|
self.assertEqual(hashes, [])
|
||||||
|
|
||||||
|
# The tombstones are gone
|
||||||
|
self.assertFalse(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0',
|
||||||
|
df1_suffix, df1_hash,
|
||||||
|
"1525354555.65758.ts")))
|
||||||
|
self.assertFalse(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0',
|
||||||
|
df2_suffix, df2_hash,
|
||||||
|
"1525354556.65758.ts")))
|
||||||
|
|
||||||
|
# The empty hash dirs are gone
|
||||||
|
self.assertFalse(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0',
|
||||||
|
df1_suffix, df1_hash)))
|
||||||
|
self.assertFalse(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0',
|
||||||
|
df2_suffix, df2_hash)))
|
||||||
|
|
||||||
|
# The empty suffix dirs are gone
|
||||||
|
self.assertFalse(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0',
|
||||||
|
df1_suffix)))
|
||||||
|
self.assertFalse(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0',
|
||||||
|
df2_suffix)))
|
||||||
|
|
||||||
|
# The empty partition dir is gone
|
||||||
|
self.assertFalse(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0')))
|
||||||
|
|
||||||
|
def test_focused_yield_hashes_does_not_clean_up(self):
|
||||||
|
the_time = [1525354555.657585]
|
||||||
|
|
||||||
|
def mock_time():
|
||||||
|
return the_time[0]
|
||||||
|
|
||||||
|
with mock.patch('time.time', mock_time):
|
||||||
|
df = self.df_mgr.get_diskfile(
|
||||||
|
self.existing_device, 0, 'a', 'c', 'o', POLICIES[0])
|
||||||
|
df.delete(Timestamp(the_time[0]))
|
||||||
|
df_hash = utils.hash_path('a', 'c', 'o')
|
||||||
|
df_suffix = df_hash[-3:]
|
||||||
|
|
||||||
|
# sanity check
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0',
|
||||||
|
df_suffix, df_hash,
|
||||||
|
"1525354555.65758.ts")))
|
||||||
|
|
||||||
|
# Expire the tombstone
|
||||||
|
the_time[0] += 2 * self.df_mgr.reclaim_age
|
||||||
|
|
||||||
|
hashes = list(self.df_mgr.yield_hashes(
|
||||||
|
self.existing_device, '0', POLICIES[0],
|
||||||
|
suffixes=[df_suffix]))
|
||||||
|
self.assertEqual(hashes, [])
|
||||||
|
|
||||||
|
# The partition dir is still there. Since we didn't visit all the
|
||||||
|
# suffix dirs, we didn't learn whether or not the partition dir was
|
||||||
|
# empty.
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(
|
||||||
|
self.testdir, self.existing_device, 'objects', '0')))
|
||||||
|
|
||||||
def test_yield_hashes_empty_suffixes(self):
|
def test_yield_hashes_empty_suffixes(self):
|
||||||
def _listdir(path):
|
def _listdir(path):
|
||||||
return []
|
return []
|
||||||
@ -1346,7 +1454,8 @@ class DiskFileManagerMixin(BaseDiskFileTestMixin):
|
|||||||
(hash_, timestamps)
|
(hash_, timestamps)
|
||||||
for hash_, timestamps in expected.items()]
|
for hash_, timestamps in expected.items()]
|
||||||
with mock.patch('os.listdir', _listdir), \
|
with mock.patch('os.listdir', _listdir), \
|
||||||
mock.patch('os.unlink'):
|
mock.patch('os.unlink'), \
|
||||||
|
mock.patch('os.rmdir'):
|
||||||
df_mgr = self.df_router[policy]
|
df_mgr = self.df_router[policy]
|
||||||
hash_items = list(df_mgr.yield_hashes(
|
hash_items = list(df_mgr.yield_hashes(
|
||||||
device, part, policy, **kwargs))
|
device, part, policy, **kwargs))
|
||||||
@ -3819,18 +3928,24 @@ class DiskFileMixin(BaseDiskFileTestMixin):
|
|||||||
self.fail("Expected exception OSError")
|
self.fail("Expected exception OSError")
|
||||||
|
|
||||||
def test_create_close_oserror(self):
|
def test_create_close_oserror(self):
|
||||||
df = self.df_mgr.get_diskfile(self.existing_device, '0', 'abc', '123',
|
# This is a horrible hack so you can run this test in isolation.
|
||||||
'xyz', policy=POLICIES.legacy)
|
# Some of the ctypes machinery calls os.close(), and that runs afoul
|
||||||
with mock.patch("swift.obj.diskfile.os.close",
|
# of our mock.
|
||||||
mock.MagicMock(side_effect=OSError(
|
with mock.patch.object(utils, '_sys_fallocate', None):
|
||||||
errno.EACCES, os.strerror(errno.EACCES)))):
|
utils.disable_fallocate()
|
||||||
try:
|
|
||||||
with df.create(size=200):
|
df = self.df_mgr.get_diskfile(self.existing_device, '0', 'abc',
|
||||||
|
'123', 'xyz', policy=POLICIES.legacy)
|
||||||
|
with mock.patch("swift.obj.diskfile.os.close",
|
||||||
|
mock.MagicMock(side_effect=OSError(
|
||||||
|
errno.EACCES, os.strerror(errno.EACCES)))):
|
||||||
|
try:
|
||||||
|
with df.create(size=200):
|
||||||
|
pass
|
||||||
|
except Exception as err:
|
||||||
|
self.fail("Unexpected exception raised: %r" % err)
|
||||||
|
else:
|
||||||
pass
|
pass
|
||||||
except Exception as err:
|
|
||||||
self.fail("Unexpected exception raised: %r" % err)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_write_metadata(self):
|
def test_write_metadata(self):
|
||||||
df, df_data = self._create_test_file('1234567890')
|
df, df_data = self._create_test_file('1234567890')
|
||||||
@ -6000,6 +6115,7 @@ class TestSuffixHashes(unittest.TestCase):
|
|||||||
def check_cleanup_ondisk_files(self, policy, input_files, output_files):
|
def check_cleanup_ondisk_files(self, policy, input_files, output_files):
|
||||||
orig_unlink = os.unlink
|
orig_unlink = os.unlink
|
||||||
file_list = list(input_files)
|
file_list = list(input_files)
|
||||||
|
rmdirs = []
|
||||||
|
|
||||||
def mock_listdir(path):
|
def mock_listdir(path):
|
||||||
return list(file_list)
|
return list(file_list)
|
||||||
@ -6016,7 +6132,8 @@ class TestSuffixHashes(unittest.TestCase):
|
|||||||
file_list.remove(os.path.basename(path))
|
file_list.remove(os.path.basename(path))
|
||||||
|
|
||||||
df_mgr = self.df_router[policy]
|
df_mgr = self.df_router[policy]
|
||||||
with unit_mock({'os.listdir': mock_listdir, 'os.unlink': mock_unlink}):
|
with unit_mock({'os.listdir': mock_listdir, 'os.unlink': mock_unlink,
|
||||||
|
'os.rmdir': rmdirs.append}):
|
||||||
if isinstance(output_files, Exception):
|
if isinstance(output_files, Exception):
|
||||||
path = os.path.join(self.testdir, 'does-not-matter')
|
path = os.path.join(self.testdir, 'does-not-matter')
|
||||||
self.assertRaises(output_files.__class__,
|
self.assertRaises(output_files.__class__,
|
||||||
@ -6024,6 +6141,10 @@ class TestSuffixHashes(unittest.TestCase):
|
|||||||
return
|
return
|
||||||
files = df_mgr.cleanup_ondisk_files('/whatever')['files']
|
files = df_mgr.cleanup_ondisk_files('/whatever')['files']
|
||||||
self.assertEqual(files, output_files)
|
self.assertEqual(files, output_files)
|
||||||
|
if files:
|
||||||
|
self.assertEqual(rmdirs, [])
|
||||||
|
else:
|
||||||
|
self.assertEqual(rmdirs, ['/whatever'])
|
||||||
|
|
||||||
# cleanup_ondisk_files tests - behaviors
|
# cleanup_ondisk_files tests - behaviors
|
||||||
|
|
||||||
|
@ -1175,21 +1175,7 @@ class TestGlobalSetupObjectReconstructor(unittest.TestCase):
|
|||||||
'sda1', '2', self.policy)
|
'sda1', '2', self.policy)
|
||||||
for path, hash_, ts in hash_gen:
|
for path, hash_, ts in hash_gen:
|
||||||
self.fail('found %s with %s in %s' % (hash_, ts, path))
|
self.fail('found %s with %s in %s' % (hash_, ts, path))
|
||||||
# but the partition directory and hashes pkl still exist
|
# even the partition directory is gone
|
||||||
self.assertTrue(os.access(part_path, os.F_OK))
|
|
||||||
hashes_path = os.path.join(self.objects_1, '2', diskfile.HASH_FILE)
|
|
||||||
self.assertTrue(os.access(hashes_path, os.F_OK))
|
|
||||||
|
|
||||||
# ... but on next pass
|
|
||||||
ssync_calls = []
|
|
||||||
with mocked_http_conn() as request_log:
|
|
||||||
with mock.patch('swift.obj.reconstructor.ssync_sender',
|
|
||||||
self._make_fake_ssync(ssync_calls)):
|
|
||||||
self.reconstructor.reconstruct(override_partitions=[2])
|
|
||||||
# reconstruct won't generate any replicate or ssync_calls
|
|
||||||
self.assertFalse(request_log.requests)
|
|
||||||
self.assertFalse(ssync_calls)
|
|
||||||
# and the partition will get removed!
|
|
||||||
self.assertFalse(os.access(part_path, os.F_OK))
|
self.assertFalse(os.access(part_path, os.F_OK))
|
||||||
|
|
||||||
def test_process_job_all_success(self):
|
def test_process_job_all_success(self):
|
||||||
|
@ -6872,12 +6872,7 @@ class TestObjectController(unittest.TestCase):
|
|||||||
resp = delete_request.get_response(self.object_controller)
|
resp = delete_request.get_response(self.object_controller)
|
||||||
# we won't even create the tombstone
|
# we won't even create the tombstone
|
||||||
self.assertFalse(os.path.exists(tombstone_file))
|
self.assertFalse(os.path.exists(tombstone_file))
|
||||||
# hashdir sticks around tho
|
# hashdir's empty, so it gets cleaned up
|
||||||
self.assertTrue(os.path.exists(objfile._datadir))
|
|
||||||
# REPLICATE will clean it all up
|
|
||||||
resp = replicate_request.get_response(self.object_controller)
|
|
||||||
self.assertEqual(resp.status_int, 200)
|
|
||||||
self.assertEqual({}, pickle.loads(resp.body))
|
|
||||||
self.assertFalse(os.path.exists(objfile._datadir))
|
self.assertFalse(os.path.exists(objfile._datadir))
|
||||||
|
|
||||||
def test_SSYNC_can_be_called(self):
|
def test_SSYNC_can_be_called(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user