swift 2.32.0__py2.py3-none-any.whl → 2.34.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- swift/account/auditor.py +11 -0
- swift/account/reaper.py +11 -1
- swift/account/replicator.py +22 -0
- swift/account/server.py +13 -12
- swift-2.32.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
- swift-2.32.0.data/scripts/swift-config → swift/cli/config.py +1 -1
- swift-2.32.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
- swift-2.32.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
- swift-2.32.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
- swift/cli/info.py +131 -3
- swift-2.32.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
- swift-2.32.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
- swift-2.32.0.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +9 -18
- swift-2.32.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
- swift/cli/relinker.py +1 -1
- swift/cli/reload.py +141 -0
- swift/cli/ringbuilder.py +24 -0
- swift/common/daemon.py +12 -2
- swift/common/db.py +14 -9
- swift/common/db_auditor.py +2 -2
- swift/common/db_replicator.py +6 -0
- swift/common/exceptions.py +12 -0
- swift/common/http_protocol.py +76 -3
- swift/common/manager.py +120 -5
- swift/common/memcached.py +24 -25
- swift/common/middleware/account_quotas.py +144 -43
- swift/common/middleware/backend_ratelimit.py +166 -24
- swift/common/middleware/catch_errors.py +1 -3
- swift/common/middleware/cname_lookup.py +3 -5
- swift/common/middleware/container_sync.py +6 -10
- swift/common/middleware/crypto/crypto_utils.py +4 -5
- swift/common/middleware/crypto/decrypter.py +4 -5
- swift/common/middleware/crypto/kms_keymaster.py +2 -1
- swift/common/middleware/proxy_logging.py +57 -43
- swift/common/middleware/ratelimit.py +6 -7
- swift/common/middleware/recon.py +6 -7
- swift/common/middleware/s3api/acl_handlers.py +10 -1
- swift/common/middleware/s3api/controllers/__init__.py +3 -0
- swift/common/middleware/s3api/controllers/acl.py +3 -2
- swift/common/middleware/s3api/controllers/logging.py +2 -2
- swift/common/middleware/s3api/controllers/multi_upload.py +31 -15
- swift/common/middleware/s3api/controllers/obj.py +20 -1
- swift/common/middleware/s3api/controllers/object_lock.py +44 -0
- swift/common/middleware/s3api/s3api.py +6 -0
- swift/common/middleware/s3api/s3request.py +190 -74
- swift/common/middleware/s3api/s3response.py +48 -8
- swift/common/middleware/s3api/s3token.py +2 -2
- swift/common/middleware/s3api/utils.py +2 -1
- swift/common/middleware/slo.py +508 -310
- swift/common/middleware/staticweb.py +45 -14
- swift/common/middleware/tempauth.py +6 -4
- swift/common/middleware/tempurl.py +134 -93
- swift/common/middleware/x_profile/exceptions.py +1 -4
- swift/common/middleware/x_profile/html_viewer.py +9 -10
- swift/common/middleware/x_profile/profile_model.py +1 -2
- swift/common/middleware/xprofile.py +1 -2
- swift/common/request_helpers.py +101 -8
- swift/common/statsd_client.py +207 -0
- swift/common/storage_policy.py +1 -1
- swift/common/swob.py +5 -2
- swift/common/utils/__init__.py +331 -1774
- swift/common/utils/base.py +138 -0
- swift/common/utils/config.py +443 -0
- swift/common/utils/logs.py +999 -0
- swift/common/utils/timestamp.py +23 -2
- swift/common/wsgi.py +19 -3
- swift/container/auditor.py +11 -0
- swift/container/backend.py +136 -31
- swift/container/reconciler.py +11 -2
- swift/container/replicator.py +64 -7
- swift/container/server.py +276 -146
- swift/container/sharder.py +86 -42
- swift/container/sync.py +11 -1
- swift/container/updater.py +12 -2
- swift/obj/auditor.py +20 -3
- swift/obj/diskfile.py +63 -25
- swift/obj/expirer.py +154 -47
- swift/obj/mem_diskfile.py +2 -1
- swift/obj/mem_server.py +1 -0
- swift/obj/reconstructor.py +28 -4
- swift/obj/replicator.py +63 -24
- swift/obj/server.py +76 -59
- swift/obj/updater.py +12 -2
- swift/obj/watchers/dark_data.py +72 -34
- swift/proxy/controllers/account.py +3 -2
- swift/proxy/controllers/base.py +254 -148
- swift/proxy/controllers/container.py +274 -289
- swift/proxy/controllers/obj.py +120 -166
- swift/proxy/server.py +17 -13
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +14 -4
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/METADATA +9 -7
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/RECORD +97 -120
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +39 -0
- swift-2.34.0.dist-info/pbr.json +1 -0
- swift-2.32.0.data/scripts/swift-account-auditor +0 -23
- swift-2.32.0.data/scripts/swift-account-info +0 -52
- swift-2.32.0.data/scripts/swift-account-reaper +0 -23
- swift-2.32.0.data/scripts/swift-account-replicator +0 -34
- swift-2.32.0.data/scripts/swift-account-server +0 -23
- swift-2.32.0.data/scripts/swift-container-auditor +0 -23
- swift-2.32.0.data/scripts/swift-container-info +0 -56
- swift-2.32.0.data/scripts/swift-container-reconciler +0 -21
- swift-2.32.0.data/scripts/swift-container-replicator +0 -34
- swift-2.32.0.data/scripts/swift-container-server +0 -23
- swift-2.32.0.data/scripts/swift-container-sharder +0 -37
- swift-2.32.0.data/scripts/swift-container-sync +0 -23
- swift-2.32.0.data/scripts/swift-container-updater +0 -23
- swift-2.32.0.data/scripts/swift-dispersion-report +0 -24
- swift-2.32.0.data/scripts/swift-form-signature +0 -20
- swift-2.32.0.data/scripts/swift-init +0 -119
- swift-2.32.0.data/scripts/swift-object-auditor +0 -29
- swift-2.32.0.data/scripts/swift-object-expirer +0 -33
- swift-2.32.0.data/scripts/swift-object-info +0 -60
- swift-2.32.0.data/scripts/swift-object-reconstructor +0 -33
- swift-2.32.0.data/scripts/swift-object-relinker +0 -23
- swift-2.32.0.data/scripts/swift-object-replicator +0 -37
- swift-2.32.0.data/scripts/swift-object-server +0 -27
- swift-2.32.0.data/scripts/swift-object-updater +0 -23
- swift-2.32.0.data/scripts/swift-proxy-server +0 -23
- swift-2.32.0.data/scripts/swift-recon +0 -24
- swift-2.32.0.data/scripts/swift-ring-builder +0 -37
- swift-2.32.0.data/scripts/swift-ring-builder-analyzer +0 -22
- swift-2.32.0.data/scripts/swift-ring-composer +0 -22
- swift-2.32.0.dist-info/pbr.json +0 -1
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/top_level.txt +0 -0
swift/obj/diskfile.py
CHANGED
@@ -65,7 +65,8 @@ from swift.common.utils import mkdirs, Timestamp, \
|
|
65
65
|
get_md5_socket, F_SETPIPE_SZ, decode_timestamps, encode_timestamps, \
|
66
66
|
MD5_OF_EMPTY_STRING, link_fd_to_path, \
|
67
67
|
O_TMPFILE, makedirs_count, replace_partition_in_path, remove_directory, \
|
68
|
-
md5, is_file_older, non_negative_float
|
68
|
+
md5, is_file_older, non_negative_float, config_fallocate_value, \
|
69
|
+
fs_has_free_space, CooperativeIterator
|
69
70
|
from swift.common.splice import splice, tee
|
70
71
|
from swift.common.exceptions import DiskFileQuarantined, DiskFileNotExist, \
|
71
72
|
DiskFileCollision, DiskFileNoSpace, DiskFileDeviceUnavailable, \
|
@@ -428,19 +429,23 @@ def consolidate_hashes(partition_dir):
|
|
428
429
|
with lock_path(partition_dir):
|
429
430
|
hashes = read_hashes(partition_dir)
|
430
431
|
|
431
|
-
found_invalidation_entry = False
|
432
|
+
found_invalidation_entry = hashes_updated = False
|
432
433
|
try:
|
433
434
|
with open(invalidations_file, 'r') as inv_fh:
|
434
435
|
for line in inv_fh:
|
435
436
|
found_invalidation_entry = True
|
436
437
|
suffix = line.strip()
|
438
|
+
if not valid_suffix(suffix):
|
439
|
+
continue
|
440
|
+
hashes_updated = True
|
437
441
|
hashes[suffix] = None
|
438
442
|
except (IOError, OSError) as e:
|
439
443
|
if e.errno != errno.ENOENT:
|
440
444
|
raise
|
441
445
|
|
442
|
-
if
|
446
|
+
if hashes_updated:
|
443
447
|
write_hashes(partition_dir, hashes)
|
448
|
+
if found_invalidation_entry:
|
444
449
|
# Now that all the invalidations are reflected in hashes.pkl, it's
|
445
450
|
# safe to clear out the invalidations file.
|
446
451
|
with open(invalidations_file, 'wb') as inv_fh:
|
@@ -595,7 +600,8 @@ def object_audit_location_generator(devices, datadir, mount_check=True,
|
|
595
600
|
try:
|
596
601
|
suffixes = listdir(part_path)
|
597
602
|
except OSError as e:
|
598
|
-
if e.errno not in (errno.ENOTDIR, errno.ENODATA
|
603
|
+
if e.errno not in (errno.ENOTDIR, errno.ENODATA,
|
604
|
+
errno.EUCLEAN):
|
599
605
|
raise
|
600
606
|
continue
|
601
607
|
for asuffix in suffixes:
|
@@ -603,7 +609,8 @@ def object_audit_location_generator(devices, datadir, mount_check=True,
|
|
603
609
|
try:
|
604
610
|
hashes = listdir(suff_path)
|
605
611
|
except OSError as e:
|
606
|
-
if e.errno not in (errno.ENOTDIR, errno.ENODATA
|
612
|
+
if e.errno not in (errno.ENOTDIR, errno.ENODATA,
|
613
|
+
errno.EUCLEAN):
|
607
614
|
raise
|
608
615
|
continue
|
609
616
|
for hsh in hashes:
|
@@ -751,6 +758,8 @@ class BaseDiskFileManager(object):
|
|
751
758
|
replication_concurrency_per_device)
|
752
759
|
self.replication_lock_timeout = int(conf.get(
|
753
760
|
'replication_lock_timeout', 15))
|
761
|
+
self.fallocate_reserve, self.fallocate_is_percent = \
|
762
|
+
config_fallocate_value(conf.get('fallocate_reserve', '1%'))
|
754
763
|
|
755
764
|
self.use_splice = False
|
756
765
|
self.pipe_size = None
|
@@ -1207,7 +1216,7 @@ class BaseDiskFileManager(object):
|
|
1207
1216
|
'it is not a directory', {'hsh_path': hsh_path,
|
1208
1217
|
'quar_path': quar_path})
|
1209
1218
|
continue
|
1210
|
-
elif err.errno
|
1219
|
+
elif err.errno in (errno.ENODATA, errno.EUCLEAN):
|
1211
1220
|
try:
|
1212
1221
|
# We've seen cases where bad sectors lead to ENODATA
|
1213
1222
|
# here; use a similar hack as above
|
@@ -1562,7 +1571,7 @@ class BaseDiskFileManager(object):
|
|
1562
1571
|
'it is not a directory', {'object_path': object_path,
|
1563
1572
|
'quar_path': quar_path})
|
1564
1573
|
raise DiskFileNotExist()
|
1565
|
-
elif err.errno
|
1574
|
+
elif err.errno in (errno.ENODATA, errno.EUCLEAN):
|
1566
1575
|
try:
|
1567
1576
|
# We've seen cases where bad sectors lead to ENODATA here;
|
1568
1577
|
# use a similar hack as above
|
@@ -1793,10 +1802,12 @@ class BaseDiskFileWriter(object):
|
|
1793
1802
|
:param bytes_per_sync: number bytes written between sync calls
|
1794
1803
|
:param diskfile: the diskfile creating this DiskFileWriter instance
|
1795
1804
|
:param next_part_power: the next partition power to be used
|
1805
|
+
:param extension: the file extension to be used; may be used internally
|
1806
|
+
to distinguish between PUT/POST/DELETE operations
|
1796
1807
|
"""
|
1797
1808
|
|
1798
1809
|
def __init__(self, name, datadir, size, bytes_per_sync, diskfile,
|
1799
|
-
next_part_power):
|
1810
|
+
next_part_power, extension='.data'):
|
1800
1811
|
# Parameter tracking
|
1801
1812
|
self._name = name
|
1802
1813
|
self._datadir = datadir
|
@@ -1807,11 +1818,11 @@ class BaseDiskFileWriter(object):
|
|
1807
1818
|
self._bytes_per_sync = bytes_per_sync
|
1808
1819
|
self._diskfile = diskfile
|
1809
1820
|
self.next_part_power = next_part_power
|
1821
|
+
self._extension = extension
|
1810
1822
|
|
1811
1823
|
# Internal attributes
|
1812
1824
|
self._upload_size = 0
|
1813
1825
|
self._last_sync = 0
|
1814
|
-
self._extension = '.data'
|
1815
1826
|
self._put_succeeded = False
|
1816
1827
|
|
1817
1828
|
@property
|
@@ -1856,13 +1867,26 @@ class BaseDiskFileWriter(object):
|
|
1856
1867
|
# No more inodes in filesystem
|
1857
1868
|
raise DiskFileNoSpace()
|
1858
1869
|
raise
|
1859
|
-
if self.
|
1870
|
+
if self._extension == '.ts':
|
1871
|
+
# DELETEs always bypass any free-space reserve checks
|
1872
|
+
pass
|
1873
|
+
elif self._size:
|
1860
1874
|
try:
|
1861
1875
|
fallocate(self._fd, self._size)
|
1862
1876
|
except OSError as err:
|
1863
1877
|
if err.errno in (errno.ENOSPC, errno.EDQUOT):
|
1864
1878
|
raise DiskFileNoSpace()
|
1865
1879
|
raise
|
1880
|
+
else:
|
1881
|
+
# If we don't know the size (i.e. self._size is None) or the size
|
1882
|
+
# is known to be zero, we still want to block writes once we're
|
1883
|
+
# past the reserve threshold.
|
1884
|
+
if not fs_has_free_space(
|
1885
|
+
self._fd,
|
1886
|
+
self.manager.fallocate_reserve,
|
1887
|
+
self.manager.fallocate_is_percent
|
1888
|
+
):
|
1889
|
+
raise DiskFileNoSpace()
|
1866
1890
|
return self
|
1867
1891
|
|
1868
1892
|
def close(self):
|
@@ -2088,11 +2112,13 @@ class BaseDiskFileReader(object):
|
|
2088
2112
|
:param pipe_size: size of pipe buffer used in zero-copy operations
|
2089
2113
|
:param diskfile: the diskfile creating this DiskFileReader instance
|
2090
2114
|
:param keep_cache: should resulting reads be kept in the buffer cache
|
2115
|
+
:param cooperative_period: the period parameter when does cooperative
|
2116
|
+
yielding during file read
|
2091
2117
|
"""
|
2092
2118
|
def __init__(self, fp, data_file, obj_size, etag,
|
2093
2119
|
disk_chunk_size, keep_cache_size, device_path, logger,
|
2094
2120
|
quarantine_hook, use_splice, pipe_size, diskfile,
|
2095
|
-
keep_cache=False):
|
2121
|
+
keep_cache=False, cooperative_period=0):
|
2096
2122
|
# Parameter tracking
|
2097
2123
|
self._fp = fp
|
2098
2124
|
self._data_file = data_file
|
@@ -2111,6 +2137,7 @@ class BaseDiskFileReader(object):
|
|
2111
2137
|
self._keep_cache = obj_size < keep_cache_size
|
2112
2138
|
else:
|
2113
2139
|
self._keep_cache = False
|
2140
|
+
self._cooperative_period = cooperative_period
|
2114
2141
|
|
2115
2142
|
# Internal Attributes
|
2116
2143
|
self._iter_etag = None
|
@@ -2135,6 +2162,10 @@ class BaseDiskFileReader(object):
|
|
2135
2162
|
self._iter_etag.update(chunk)
|
2136
2163
|
|
2137
2164
|
def __iter__(self):
|
2165
|
+
return CooperativeIterator(
|
2166
|
+
self._inner_iter(), period=self._cooperative_period)
|
2167
|
+
|
2168
|
+
def _inner_iter(self):
|
2138
2169
|
"""Returns an iterator over the data file."""
|
2139
2170
|
try:
|
2140
2171
|
dropped_cache = 0
|
@@ -2581,7 +2612,7 @@ class BaseDiskFile(object):
|
|
2581
2612
|
# want this one file and not its parent.
|
2582
2613
|
os.path.join(self._datadir, "made-up-filename"),
|
2583
2614
|
"Expected directory, found file at %s" % self._datadir)
|
2584
|
-
elif err.errno
|
2615
|
+
elif err.errno in (errno.ENODATA, errno.EUCLEAN):
|
2585
2616
|
try:
|
2586
2617
|
# We've seen cases where bad sectors lead to ENODATA here
|
2587
2618
|
raise self._quarantine(
|
@@ -2611,7 +2642,7 @@ class BaseDiskFile(object):
|
|
2611
2642
|
self._fp = self._construct_from_data_file(
|
2612
2643
|
current_time=current_time, modernize=modernize, **file_info)
|
2613
2644
|
except IOError as e:
|
2614
|
-
if e.errno
|
2645
|
+
if e.errno in (errno.ENODATA, errno.EUCLEAN):
|
2615
2646
|
raise self._quarantine(
|
2616
2647
|
file_info['data_file'],
|
2617
2648
|
"Failed to open %s: %s" % (file_info['data_file'], e))
|
@@ -2953,7 +2984,7 @@ class BaseDiskFile(object):
|
|
2953
2984
|
with self.open(current_time=current_time):
|
2954
2985
|
return self.get_metadata()
|
2955
2986
|
|
2956
|
-
def reader(self, keep_cache=False,
|
2987
|
+
def reader(self, keep_cache=False, cooperative_period=0,
|
2957
2988
|
_quarantine_hook=lambda m: None):
|
2958
2989
|
"""
|
2959
2990
|
Return a :class:`swift.common.swob.Response` class compatible
|
@@ -2965,6 +2996,8 @@ class BaseDiskFile(object):
|
|
2965
2996
|
|
2966
2997
|
:param keep_cache: caller's preference for keeping data read in the
|
2967
2998
|
OS buffer cache
|
2999
|
+
:param cooperative_period: the period parameter for cooperative
|
3000
|
+
yielding during file read
|
2968
3001
|
:param _quarantine_hook: 1-arg callable called when obj quarantined;
|
2969
3002
|
the arg is the reason for quarantine.
|
2970
3003
|
Default is to ignore it.
|
@@ -2976,19 +3009,23 @@ class BaseDiskFile(object):
|
|
2976
3009
|
self._metadata['ETag'], self._disk_chunk_size,
|
2977
3010
|
self._manager.keep_cache_size, self._device_path, self._logger,
|
2978
3011
|
use_splice=self._use_splice, quarantine_hook=_quarantine_hook,
|
2979
|
-
pipe_size=self._pipe_size, diskfile=self, keep_cache=keep_cache
|
3012
|
+
pipe_size=self._pipe_size, diskfile=self, keep_cache=keep_cache,
|
3013
|
+
cooperative_period=cooperative_period)
|
2980
3014
|
# At this point the reader object is now responsible for closing
|
2981
3015
|
# the file pointer.
|
2982
3016
|
self._fp = None
|
2983
3017
|
return dr
|
2984
3018
|
|
2985
|
-
def
|
3019
|
+
def _writer(self, size, extension):
|
2986
3020
|
return self.writer_cls(self._name, self._datadir, size,
|
2987
3021
|
self._bytes_per_sync, self,
|
2988
|
-
self.next_part_power)
|
3022
|
+
self.next_part_power, extension=extension)
|
3023
|
+
|
3024
|
+
def writer(self, size=None):
|
3025
|
+
return self._writer(size, '.data')
|
2989
3026
|
|
2990
3027
|
@contextmanager
|
2991
|
-
def create(self, size=None):
|
3028
|
+
def create(self, size=None, extension='.data'):
|
2992
3029
|
"""
|
2993
3030
|
Context manager to create a file. We create a temporary file first, and
|
2994
3031
|
then return a DiskFileWriter object to encapsulate the state.
|
@@ -3001,9 +3038,11 @@ class BaseDiskFile(object):
|
|
3001
3038
|
|
3002
3039
|
:param size: optional initial size of file to explicitly allocate on
|
3003
3040
|
disk
|
3041
|
+
:param extension: file extension to use for the newly-created file;
|
3042
|
+
defaults to ``.data`` for the sake of tests
|
3004
3043
|
:raises DiskFileNoSpace: if a size is specified and allocation fails
|
3005
3044
|
"""
|
3006
|
-
dfw = self.
|
3045
|
+
dfw = self._writer(size, extension)
|
3007
3046
|
try:
|
3008
3047
|
yield dfw.open()
|
3009
3048
|
finally:
|
@@ -3019,8 +3058,7 @@ class BaseDiskFile(object):
|
|
3019
3058
|
:raises DiskFileError: this implementation will raise the same
|
3020
3059
|
errors as the `create()` method.
|
3021
3060
|
"""
|
3022
|
-
with self.create() as writer:
|
3023
|
-
writer._extension = '.meta'
|
3061
|
+
with self.create(extension='.meta') as writer:
|
3024
3062
|
writer.put(metadata)
|
3025
3063
|
|
3026
3064
|
def delete(self, timestamp):
|
@@ -3042,8 +3080,7 @@ class BaseDiskFile(object):
|
|
3042
3080
|
"""
|
3043
3081
|
# this is dumb, only tests send in strings
|
3044
3082
|
timestamp = Timestamp(timestamp)
|
3045
|
-
with self.create() as deleter:
|
3046
|
-
deleter._extension = '.ts'
|
3083
|
+
with self.create(extension='.ts') as deleter:
|
3047
3084
|
deleter.put({'X-Timestamp': timestamp.internal})
|
3048
3085
|
|
3049
3086
|
|
@@ -3136,11 +3173,12 @@ class ECDiskFileReader(BaseDiskFileReader):
|
|
3136
3173
|
def __init__(self, fp, data_file, obj_size, etag,
|
3137
3174
|
disk_chunk_size, keep_cache_size, device_path, logger,
|
3138
3175
|
quarantine_hook, use_splice, pipe_size, diskfile,
|
3139
|
-
keep_cache=False):
|
3176
|
+
keep_cache=False, cooperative_period=0):
|
3140
3177
|
super(ECDiskFileReader, self).__init__(
|
3141
3178
|
fp, data_file, obj_size, etag,
|
3142
3179
|
disk_chunk_size, keep_cache_size, device_path, logger,
|
3143
|
-
quarantine_hook, use_splice, pipe_size, diskfile, keep_cache
|
3180
|
+
quarantine_hook, use_splice, pipe_size, diskfile, keep_cache,
|
3181
|
+
cooperative_period)
|
3144
3182
|
self.frag_buf = None
|
3145
3183
|
self.frag_offset = 0
|
3146
3184
|
self.frag_size = self._diskfile.policy.fragment_size
|
swift/obj/expirer.py
CHANGED
@@ -14,9 +14,11 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
16
|
import six
|
17
|
+
from six.moves import urllib
|
17
18
|
|
18
19
|
from random import random
|
19
20
|
from time import time
|
21
|
+
from optparse import OptionParser
|
20
22
|
from os.path import join
|
21
23
|
from collections import defaultdict, deque
|
22
24
|
|
@@ -24,11 +26,12 @@ from eventlet import sleep, Timeout
|
|
24
26
|
from eventlet.greenpool import GreenPool
|
25
27
|
|
26
28
|
from swift.common.constraints import AUTO_CREATE_ACCOUNT_PREFIX
|
27
|
-
from swift.common.daemon import Daemon
|
29
|
+
from swift.common.daemon import Daemon, run_daemon
|
28
30
|
from swift.common.internal_client import InternalClient, UnexpectedResponse
|
29
31
|
from swift.common.utils import get_logger, dump_recon_cache, split_path, \
|
30
32
|
Timestamp, config_true_value, normalize_delete_at_timestamp, \
|
31
|
-
RateLimitedIterator, md5
|
33
|
+
RateLimitedIterator, md5, non_negative_float, non_negative_int, \
|
34
|
+
parse_content_type, parse_options
|
32
35
|
from swift.common.http import HTTP_NOT_FOUND, HTTP_CONFLICT, \
|
33
36
|
HTTP_PRECONDITION_FAILED
|
34
37
|
from swift.common.recon import RECON_OBJECT_FILE, DEFAULT_RECON_CACHE_PATH
|
@@ -36,6 +39,7 @@ from swift.common.recon import RECON_OBJECT_FILE, DEFAULT_RECON_CACHE_PATH
|
|
36
39
|
from swift.container.reconciler import direct_delete_container_entry
|
37
40
|
|
38
41
|
MAX_OBJECTS_TO_CACHE = 100000
|
42
|
+
X_DELETE_TYPE = 'text/plain'
|
39
43
|
ASYNC_DELETE_TYPE = 'application/async-deleted'
|
40
44
|
|
41
45
|
|
@@ -66,6 +70,80 @@ def parse_task_obj(task_obj):
|
|
66
70
|
return timestamp, target_account, target_container, target_obj
|
67
71
|
|
68
72
|
|
73
|
+
def extract_expirer_bytes_from_ctype(content_type):
|
74
|
+
"""
|
75
|
+
Parse a content-type and return the number of bytes.
|
76
|
+
|
77
|
+
:param content_type: a content-type string
|
78
|
+
:return: int or None
|
79
|
+
"""
|
80
|
+
content_type, params = parse_content_type(content_type)
|
81
|
+
bytes_size = None
|
82
|
+
for k, v in params:
|
83
|
+
if k == 'swift_expirer_bytes':
|
84
|
+
bytes_size = int(v)
|
85
|
+
return bytes_size
|
86
|
+
|
87
|
+
|
88
|
+
def embed_expirer_bytes_in_ctype(content_type, metadata):
|
89
|
+
"""
|
90
|
+
Embed number of bytes into content-type. The bytes should come from
|
91
|
+
content-length on regular objects, but future extensions to "bytes in
|
92
|
+
expirer queue" monitoring may want to more closely consider expiration of
|
93
|
+
large multipart object manifests.
|
94
|
+
|
95
|
+
:param content_type: a content-type string
|
96
|
+
:param metadata: a dict, from Diskfile metadata
|
97
|
+
:return: str
|
98
|
+
"""
|
99
|
+
# as best I can tell this key is required by df.open
|
100
|
+
report_bytes = metadata['Content-Length']
|
101
|
+
return "%s;swift_expirer_bytes=%d" % (content_type, int(report_bytes))
|
102
|
+
|
103
|
+
|
104
|
+
def read_conf_for_delay_reaping_times(conf):
|
105
|
+
delay_reaping_times = {}
|
106
|
+
for conf_key in conf:
|
107
|
+
delay_reaping_prefix = "delay_reaping_"
|
108
|
+
if not conf_key.startswith(delay_reaping_prefix):
|
109
|
+
continue
|
110
|
+
delay_reaping_key = urllib.parse.unquote(
|
111
|
+
conf_key[len(delay_reaping_prefix):])
|
112
|
+
if delay_reaping_key.strip('/') != delay_reaping_key:
|
113
|
+
raise ValueError(
|
114
|
+
'%s '
|
115
|
+
'should be in the form delay_reaping_<account> '
|
116
|
+
'or delay_reaping_<account>/<container> '
|
117
|
+
'(leading or trailing "/" is not allowed)' % conf_key)
|
118
|
+
try:
|
119
|
+
# If split_path fails, have multiple '/' or
|
120
|
+
# account name is invalid
|
121
|
+
account, container = split_path(
|
122
|
+
'/' + delay_reaping_key, 1, 2
|
123
|
+
)
|
124
|
+
except ValueError:
|
125
|
+
raise ValueError(
|
126
|
+
'%s '
|
127
|
+
'should be in the form delay_reaping_<account> '
|
128
|
+
'or delay_reaping_<account>/<container> '
|
129
|
+
'(at most one "/" is allowed)' % conf_key)
|
130
|
+
try:
|
131
|
+
delay_reaping_times[(account, container)] = non_negative_float(
|
132
|
+
conf.get(conf_key)
|
133
|
+
)
|
134
|
+
except ValueError:
|
135
|
+
raise ValueError(
|
136
|
+
'%s must be a float '
|
137
|
+
'greater than or equal to 0' % conf_key)
|
138
|
+
return delay_reaping_times
|
139
|
+
|
140
|
+
|
141
|
+
def get_delay_reaping(delay_reaping_times, target_account, target_container):
|
142
|
+
return delay_reaping_times.get(
|
143
|
+
(target_account, target_container),
|
144
|
+
delay_reaping_times.get((target_account, None), 0.0))
|
145
|
+
|
146
|
+
|
69
147
|
class ObjectExpirer(Daemon):
|
70
148
|
"""
|
71
149
|
Daemon that queries the internal hidden task accounts to discover objects
|
@@ -89,16 +167,8 @@ class ObjectExpirer(Daemon):
|
|
89
167
|
self.dequeue_from_legacy = \
|
90
168
|
True if is_legacy_conf else \
|
91
169
|
config_true_value(conf.get('dequeue_from_legacy', 'false'))
|
92
|
-
|
93
|
-
|
94
|
-
self.ic_conf_path = self.conf_path
|
95
|
-
else:
|
96
|
-
self.ic_conf_path = \
|
97
|
-
self.conf.get('internal_client_conf_path') or \
|
98
|
-
'/etc/swift/internal-client.conf'
|
99
|
-
|
100
|
-
self.read_conf_for_queue_access(swift)
|
101
|
-
|
170
|
+
self.swift = swift or self._make_internal_client(is_legacy_conf)
|
171
|
+
self.read_conf_for_queue_access()
|
102
172
|
self.report_interval = float(conf.get('report_interval') or 300)
|
103
173
|
self.report_first_time = self.report_last_time = time()
|
104
174
|
self.report_objects = 0
|
@@ -113,35 +183,32 @@ class ObjectExpirer(Daemon):
|
|
113
183
|
# with the tombstone reclaim age in the consistency engine.
|
114
184
|
self.reclaim_age = int(conf.get('reclaim_age', 604800))
|
115
185
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
'swift-constraints section of '
|
122
|
-
'swift.conf. This option will '
|
123
|
-
'be ignored in a future release.')
|
124
|
-
auto_create_account_prefix = \
|
125
|
-
self.conf['auto_create_account_prefix']
|
186
|
+
self.delay_reaping_times = read_conf_for_delay_reaping_times(conf)
|
187
|
+
|
188
|
+
def _make_internal_client(self, is_legacy_conf):
|
189
|
+
if is_legacy_conf:
|
190
|
+
ic_conf_path = self.conf_path
|
126
191
|
else:
|
127
|
-
|
192
|
+
ic_conf_path = \
|
193
|
+
self.conf.get('internal_client_conf_path') or \
|
194
|
+
'/etc/swift/internal-client.conf'
|
195
|
+
request_tries = int(self.conf.get('request_tries') or 3)
|
196
|
+
return InternalClient(
|
197
|
+
ic_conf_path, 'Swift Object Expirer', request_tries,
|
198
|
+
use_replication_network=True,
|
199
|
+
global_conf={'log_name': '%s-ic' % self.conf.get(
|
200
|
+
'log_name', self.log_route)})
|
128
201
|
|
129
|
-
|
202
|
+
def read_conf_for_queue_access(self):
|
203
|
+
self.expiring_objects_account = AUTO_CREATE_ACCOUNT_PREFIX + \
|
130
204
|
(self.conf.get('expiring_objects_account_name') or
|
131
205
|
'expiring_objects')
|
132
206
|
|
133
207
|
# This is for common parameter with general task queue in future
|
134
208
|
self.task_container_prefix = ''
|
135
|
-
|
136
|
-
|
137
|
-
self.
|
138
|
-
self.ic_conf_path, 'Swift Object Expirer', request_tries,
|
139
|
-
use_replication_network=True,
|
140
|
-
global_conf={'log_name': '%s-ic' % self.conf.get(
|
141
|
-
'log_name', self.log_route)})
|
142
|
-
|
143
|
-
self.processes = int(self.conf.get('processes', 0))
|
144
|
-
self.process = int(self.conf.get('process', 0))
|
209
|
+
self.processes = non_negative_int(self.conf.get('processes', 0))
|
210
|
+
self.process = non_negative_int(self.conf.get('process', 0))
|
211
|
+
self._validate_processes_config()
|
145
212
|
|
146
213
|
def report(self, final=False):
|
147
214
|
"""
|
@@ -258,6 +325,10 @@ class ObjectExpirer(Daemon):
|
|
258
325
|
break
|
259
326
|
yield task_container
|
260
327
|
|
328
|
+
def get_delay_reaping(self, target_account, target_container):
|
329
|
+
return get_delay_reaping(self.delay_reaping_times, target_account,
|
330
|
+
target_container)
|
331
|
+
|
261
332
|
def iter_task_to_expire(self, task_account_container_list,
|
262
333
|
my_index, divisor):
|
263
334
|
"""
|
@@ -278,18 +349,30 @@ class ObjectExpirer(Daemon):
|
|
278
349
|
except ValueError:
|
279
350
|
self.logger.exception('Unexcepted error handling task %r' %
|
280
351
|
task_object)
|
352
|
+
self.logger.increment('tasks.parse_errors')
|
281
353
|
continue
|
354
|
+
is_async = o.get('content_type') == ASYNC_DELETE_TYPE
|
355
|
+
delay_reaping = self.get_delay_reaping(target_account,
|
356
|
+
target_container)
|
357
|
+
|
282
358
|
if delete_timestamp > Timestamp.now():
|
283
|
-
# we shouldn't yield
|
359
|
+
# we shouldn't yield ANY more objects that can't reach
|
284
360
|
# the expiration date yet.
|
285
361
|
break
|
286
362
|
|
287
|
-
# Only one expirer daemon assigned for
|
363
|
+
# Only one expirer daemon assigned for each task
|
288
364
|
if self.hash_mod('%s/%s' % (task_container, task_object),
|
289
365
|
divisor) != my_index:
|
366
|
+
self.logger.increment('tasks.skipped')
|
290
367
|
continue
|
291
368
|
|
292
|
-
|
369
|
+
if delete_timestamp > Timestamp(time() - delay_reaping) \
|
370
|
+
and not is_async:
|
371
|
+
# we shouldn't yield the object during the delay
|
372
|
+
self.logger.increment('tasks.delayed')
|
373
|
+
continue
|
374
|
+
|
375
|
+
self.logger.increment('tasks.assigned')
|
293
376
|
yield {'task_account': task_account,
|
294
377
|
'task_container': task_container,
|
295
378
|
'task_object': task_object,
|
@@ -320,6 +403,9 @@ class ObjectExpirer(Daemon):
|
|
320
403
|
These will override the values from the config file if
|
321
404
|
provided.
|
322
405
|
"""
|
406
|
+
# these config values are available to override at the command line,
|
407
|
+
# blow-up now if they're wrong
|
408
|
+
self.override_proceses_config_from_command_line(**kwargs)
|
323
409
|
# This if-clause will be removed when general task queue feature is
|
324
410
|
# implemented.
|
325
411
|
if not self.dequeue_from_legacy:
|
@@ -330,7 +416,6 @@ class ObjectExpirer(Daemon):
|
|
330
416
|
'with dequeue_from_legacy == true.')
|
331
417
|
return
|
332
418
|
|
333
|
-
self.get_process_values(kwargs)
|
334
419
|
pool = GreenPool(self.concurrency)
|
335
420
|
self.report_first_time = self.report_last_time = time()
|
336
421
|
self.report_objects = 0
|
@@ -385,6 +470,9 @@ class ObjectExpirer(Daemon):
|
|
385
470
|
:param kwargs: Extra keyword args to fulfill the Daemon interface; this
|
386
471
|
daemon has no additional keyword args.
|
387
472
|
"""
|
473
|
+
# these config values are available to override at the command line
|
474
|
+
# blow-up now if they're wrong
|
475
|
+
self.override_proceses_config_from_command_line(**kwargs)
|
388
476
|
sleep(random() * self.interval)
|
389
477
|
while True:
|
390
478
|
begin = time()
|
@@ -396,7 +484,7 @@ class ObjectExpirer(Daemon):
|
|
396
484
|
if elapsed < self.interval:
|
397
485
|
sleep(random() * (self.interval - elapsed))
|
398
486
|
|
399
|
-
def
|
487
|
+
def override_proceses_config_from_command_line(self, **kwargs):
|
400
488
|
"""
|
401
489
|
Sets self.processes and self.process from the kwargs if those
|
402
490
|
values exist, otherwise, leaves those values as they were set in
|
@@ -407,19 +495,20 @@ class ObjectExpirer(Daemon):
|
|
407
495
|
line when the daemon is run.
|
408
496
|
"""
|
409
497
|
if kwargs.get('processes') is not None:
|
410
|
-
self.processes =
|
498
|
+
self.processes = non_negative_int(kwargs['processes'])
|
411
499
|
|
412
500
|
if kwargs.get('process') is not None:
|
413
|
-
self.process =
|
501
|
+
self.process = non_negative_int(kwargs['process'])
|
414
502
|
|
415
|
-
|
416
|
-
raise ValueError(
|
417
|
-
'process must be an integer greater than or equal to 0')
|
503
|
+
self._validate_processes_config()
|
418
504
|
|
419
|
-
|
420
|
-
|
421
|
-
|
505
|
+
def _validate_processes_config(self):
|
506
|
+
"""
|
507
|
+
Used in constructor and in override_proceses_config_from_command_line
|
508
|
+
to validate the processes configuration requirements.
|
422
509
|
|
510
|
+
:raiess: ValueError if processes config is invalid
|
511
|
+
"""
|
423
512
|
if self.processes and self.process >= self.processes:
|
424
513
|
raise ValueError(
|
425
514
|
'process must be less than processes')
|
@@ -496,3 +585,21 @@ class ObjectExpirer(Daemon):
|
|
496
585
|
self.swift.delete_object(*split_path('/' + actual_obj, 3, 3, True),
|
497
586
|
headers=headers,
|
498
587
|
acceptable_statuses=acceptable_statuses)
|
588
|
+
|
589
|
+
|
590
|
+
def main():
|
591
|
+
parser = OptionParser("%prog CONFIG [options]")
|
592
|
+
parser.add_option('--processes', dest='processes',
|
593
|
+
help="Number of processes to use to do the work, don't "
|
594
|
+
"use this option to do all the work in one process")
|
595
|
+
parser.add_option('--process', dest='process',
|
596
|
+
help="Process number for this process, don't use "
|
597
|
+
"this option to do all the work in one process, this "
|
598
|
+
"is used to determine which part of the work this "
|
599
|
+
"process should do")
|
600
|
+
conf_file, options = parse_options(parser=parser, once=True)
|
601
|
+
run_daemon(ObjectExpirer, conf_file, **options)
|
602
|
+
|
603
|
+
|
604
|
+
if __name__ == '__main__':
|
605
|
+
main()
|
swift/obj/mem_diskfile.py
CHANGED
@@ -426,13 +426,14 @@ class DiskFile(object):
|
|
426
426
|
with self.open(current_time=current_time):
|
427
427
|
return self.get_metadata()
|
428
428
|
|
429
|
-
def reader(self, keep_cache=False):
|
429
|
+
def reader(self, keep_cache=False, cooperative_period=0):
|
430
430
|
"""
|
431
431
|
Return a swift.common.swob.Response class compatible "app_iter"
|
432
432
|
object. The responsibility of closing the open file is passed to the
|
433
433
|
DiskFileReader object.
|
434
434
|
|
435
435
|
:param keep_cache:
|
436
|
+
:param cooperative_period:
|
436
437
|
"""
|
437
438
|
dr = DiskFileReader(self._name, self._fp,
|
438
439
|
int(self._metadata['Content-Length']),
|
swift/obj/mem_server.py
CHANGED