swift 2.33.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 +12 -1
- swift-2.33.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
- swift-2.33.0.data/scripts/swift-config → swift/cli/config.py +1 -1
- swift-2.33.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
- swift-2.33.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
- swift-2.33.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
- swift/cli/info.py +103 -2
- swift-2.33.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
- swift-2.33.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
- swift/cli/recon_cron.py +5 -5
- swift-2.33.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
- swift/cli/relinker.py +1 -1
- swift/cli/ringbuilder.py +24 -0
- swift/common/db.py +2 -1
- swift/common/db_auditor.py +2 -2
- swift/common/db_replicator.py +6 -0
- swift/common/exceptions.py +12 -0
- swift/common/manager.py +102 -0
- swift/common/memcached.py +6 -13
- 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 +22 -16
- swift/common/middleware/ratelimit.py +6 -7
- swift/common/middleware/recon.py +6 -7
- swift/common/middleware/s3api/acl_handlers.py +9 -0
- swift/common/middleware/s3api/controllers/multi_upload.py +1 -9
- swift/common/middleware/s3api/controllers/obj.py +20 -1
- swift/common/middleware/s3api/s3api.py +2 -0
- swift/common/middleware/s3api/s3request.py +171 -62
- swift/common/middleware/s3api/s3response.py +35 -6
- swift/common/middleware/s3api/s3token.py +2 -2
- swift/common/middleware/s3api/utils.py +1 -0
- swift/common/middleware/slo.py +153 -52
- swift/common/middleware/tempauth.py +6 -4
- swift/common/middleware/tempurl.py +2 -2
- 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 +69 -0
- swift/common/statsd_client.py +207 -0
- swift/common/utils/__init__.py +97 -1635
- swift/common/utils/base.py +138 -0
- swift/common/utils/config.py +443 -0
- swift/common/utils/logs.py +999 -0
- swift/common/wsgi.py +11 -3
- swift/container/auditor.py +11 -0
- swift/container/backend.py +10 -10
- swift/container/reconciler.py +11 -2
- swift/container/replicator.py +22 -1
- swift/container/server.py +12 -1
- swift/container/sharder.py +36 -12
- swift/container/sync.py +11 -1
- swift/container/updater.py +11 -2
- swift/obj/auditor.py +18 -2
- swift/obj/diskfile.py +8 -6
- swift/obj/expirer.py +155 -36
- swift/obj/reconstructor.py +28 -4
- swift/obj/replicator.py +61 -22
- swift/obj/server.py +64 -36
- swift/obj/updater.py +11 -2
- swift/proxy/controllers/base.py +38 -22
- swift/proxy/controllers/obj.py +23 -26
- swift/proxy/server.py +15 -1
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +11 -3
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/METADATA +6 -5
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/RECORD +81 -107
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +38 -0
- swift-2.34.0.dist-info/pbr.json +1 -0
- swift-2.33.0.data/scripts/swift-account-auditor +0 -23
- swift-2.33.0.data/scripts/swift-account-info +0 -52
- swift-2.33.0.data/scripts/swift-account-reaper +0 -23
- swift-2.33.0.data/scripts/swift-account-replicator +0 -34
- swift-2.33.0.data/scripts/swift-account-server +0 -23
- swift-2.33.0.data/scripts/swift-container-auditor +0 -23
- swift-2.33.0.data/scripts/swift-container-info +0 -59
- swift-2.33.0.data/scripts/swift-container-reconciler +0 -21
- swift-2.33.0.data/scripts/swift-container-replicator +0 -34
- swift-2.33.0.data/scripts/swift-container-server +0 -23
- swift-2.33.0.data/scripts/swift-container-sharder +0 -37
- swift-2.33.0.data/scripts/swift-container-sync +0 -23
- swift-2.33.0.data/scripts/swift-container-updater +0 -23
- swift-2.33.0.data/scripts/swift-dispersion-report +0 -24
- swift-2.33.0.data/scripts/swift-form-signature +0 -20
- swift-2.33.0.data/scripts/swift-init +0 -119
- swift-2.33.0.data/scripts/swift-object-auditor +0 -29
- swift-2.33.0.data/scripts/swift-object-expirer +0 -33
- swift-2.33.0.data/scripts/swift-object-info +0 -60
- swift-2.33.0.data/scripts/swift-object-reconstructor +0 -33
- swift-2.33.0.data/scripts/swift-object-relinker +0 -23
- swift-2.33.0.data/scripts/swift-object-replicator +0 -37
- swift-2.33.0.data/scripts/swift-object-server +0 -27
- swift-2.33.0.data/scripts/swift-object-updater +0 -23
- swift-2.33.0.data/scripts/swift-proxy-server +0 -23
- swift-2.33.0.data/scripts/swift-recon +0 -24
- swift-2.33.0.data/scripts/swift-recon-cron +0 -24
- swift-2.33.0.data/scripts/swift-ring-builder +0 -37
- swift-2.33.0.data/scripts/swift-ring-builder-analyzer +0 -22
- swift-2.33.0.data/scripts/swift-ring-composer +0 -22
- swift-2.33.0.dist-info/pbr.json +0 -1
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/top_level.txt +0 -0
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,23 +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
|
-
self.expiring_objects_account = AUTO_CREATE_ACCOUNT_PREFIX + \
|
118
|
-
(self.conf.get('expiring_objects_account_name') or
|
119
|
-
'expiring_objects')
|
120
|
-
|
121
|
-
# This is for common parameter with general task queue in future
|
122
|
-
self.task_container_prefix = ''
|
186
|
+
self.delay_reaping_times = read_conf_for_delay_reaping_times(conf)
|
123
187
|
|
188
|
+
def _make_internal_client(self, is_legacy_conf):
|
189
|
+
if is_legacy_conf:
|
190
|
+
ic_conf_path = self.conf_path
|
191
|
+
else:
|
192
|
+
ic_conf_path = \
|
193
|
+
self.conf.get('internal_client_conf_path') or \
|
194
|
+
'/etc/swift/internal-client.conf'
|
124
195
|
request_tries = int(self.conf.get('request_tries') or 3)
|
125
|
-
|
126
|
-
|
196
|
+
return InternalClient(
|
197
|
+
ic_conf_path, 'Swift Object Expirer', request_tries,
|
127
198
|
use_replication_network=True,
|
128
199
|
global_conf={'log_name': '%s-ic' % self.conf.get(
|
129
200
|
'log_name', self.log_route)})
|
130
201
|
|
131
|
-
|
132
|
-
self.
|
202
|
+
def read_conf_for_queue_access(self):
|
203
|
+
self.expiring_objects_account = AUTO_CREATE_ACCOUNT_PREFIX + \
|
204
|
+
(self.conf.get('expiring_objects_account_name') or
|
205
|
+
'expiring_objects')
|
206
|
+
|
207
|
+
# This is for common parameter with general task queue in future
|
208
|
+
self.task_container_prefix = ''
|
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()
|
133
212
|
|
134
213
|
def report(self, final=False):
|
135
214
|
"""
|
@@ -246,6 +325,10 @@ class ObjectExpirer(Daemon):
|
|
246
325
|
break
|
247
326
|
yield task_container
|
248
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
|
+
|
249
332
|
def iter_task_to_expire(self, task_account_container_list,
|
250
333
|
my_index, divisor):
|
251
334
|
"""
|
@@ -266,18 +349,30 @@ class ObjectExpirer(Daemon):
|
|
266
349
|
except ValueError:
|
267
350
|
self.logger.exception('Unexcepted error handling task %r' %
|
268
351
|
task_object)
|
352
|
+
self.logger.increment('tasks.parse_errors')
|
269
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
|
+
|
270
358
|
if delete_timestamp > Timestamp.now():
|
271
|
-
# we shouldn't yield
|
359
|
+
# we shouldn't yield ANY more objects that can't reach
|
272
360
|
# the expiration date yet.
|
273
361
|
break
|
274
362
|
|
275
|
-
# Only one expirer daemon assigned for
|
363
|
+
# Only one expirer daemon assigned for each task
|
276
364
|
if self.hash_mod('%s/%s' % (task_container, task_object),
|
277
365
|
divisor) != my_index:
|
366
|
+
self.logger.increment('tasks.skipped')
|
278
367
|
continue
|
279
368
|
|
280
|
-
|
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')
|
281
376
|
yield {'task_account': task_account,
|
282
377
|
'task_container': task_container,
|
283
378
|
'task_object': task_object,
|
@@ -308,6 +403,9 @@ class ObjectExpirer(Daemon):
|
|
308
403
|
These will override the values from the config file if
|
309
404
|
provided.
|
310
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)
|
311
409
|
# This if-clause will be removed when general task queue feature is
|
312
410
|
# implemented.
|
313
411
|
if not self.dequeue_from_legacy:
|
@@ -318,7 +416,6 @@ class ObjectExpirer(Daemon):
|
|
318
416
|
'with dequeue_from_legacy == true.')
|
319
417
|
return
|
320
418
|
|
321
|
-
self.get_process_values(kwargs)
|
322
419
|
pool = GreenPool(self.concurrency)
|
323
420
|
self.report_first_time = self.report_last_time = time()
|
324
421
|
self.report_objects = 0
|
@@ -373,6 +470,9 @@ class ObjectExpirer(Daemon):
|
|
373
470
|
:param kwargs: Extra keyword args to fulfill the Daemon interface; this
|
374
471
|
daemon has no additional keyword args.
|
375
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)
|
376
476
|
sleep(random() * self.interval)
|
377
477
|
while True:
|
378
478
|
begin = time()
|
@@ -384,7 +484,7 @@ class ObjectExpirer(Daemon):
|
|
384
484
|
if elapsed < self.interval:
|
385
485
|
sleep(random() * (self.interval - elapsed))
|
386
486
|
|
387
|
-
def
|
487
|
+
def override_proceses_config_from_command_line(self, **kwargs):
|
388
488
|
"""
|
389
489
|
Sets self.processes and self.process from the kwargs if those
|
390
490
|
values exist, otherwise, leaves those values as they were set in
|
@@ -395,19 +495,20 @@ class ObjectExpirer(Daemon):
|
|
395
495
|
line when the daemon is run.
|
396
496
|
"""
|
397
497
|
if kwargs.get('processes') is not None:
|
398
|
-
self.processes =
|
498
|
+
self.processes = non_negative_int(kwargs['processes'])
|
399
499
|
|
400
500
|
if kwargs.get('process') is not None:
|
401
|
-
self.process =
|
501
|
+
self.process = non_negative_int(kwargs['process'])
|
402
502
|
|
403
|
-
|
404
|
-
raise ValueError(
|
405
|
-
'process must be an integer greater than or equal to 0')
|
503
|
+
self._validate_processes_config()
|
406
504
|
|
407
|
-
|
408
|
-
|
409
|
-
|
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.
|
410
509
|
|
510
|
+
:raiess: ValueError if processes config is invalid
|
511
|
+
"""
|
411
512
|
if self.processes and self.process >= self.processes:
|
412
513
|
raise ValueError(
|
413
514
|
'process must be less than processes')
|
@@ -484,3 +585,21 @@ class ObjectExpirer(Daemon):
|
|
484
585
|
self.swift.delete_object(*split_path('/' + actual_obj, 3, 3, True),
|
485
586
|
headers=headers,
|
486
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/reconstructor.py
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
import itertools
|
16
16
|
import json
|
17
17
|
import errno
|
18
|
+
from optparse import OptionParser
|
18
19
|
import os
|
19
20
|
from os.path import join
|
20
21
|
import random
|
@@ -33,10 +34,10 @@ from swift.common.utils import (
|
|
33
34
|
GreenAsyncPile, Timestamp, remove_file, node_to_string,
|
34
35
|
load_recon_cache, parse_override_options, distribute_evenly,
|
35
36
|
PrefixLoggerAdapter, remove_directory, config_request_node_count_value,
|
36
|
-
non_negative_int)
|
37
|
+
non_negative_int, parse_options)
|
37
38
|
from swift.common.header_key_dict import HeaderKeyDict
|
38
39
|
from swift.common.bufferedhttp import http_connect
|
39
|
-
from swift.common.daemon import Daemon
|
40
|
+
from swift.common.daemon import Daemon, run_daemon
|
40
41
|
from swift.common.recon import RECON_OBJECT_FILE, DEFAULT_RECON_CACHE_PATH
|
41
42
|
from swift.common.ring.utils import is_local_device
|
42
43
|
from swift.obj.ssync_sender import Sender as ssync_sender
|
@@ -911,12 +912,14 @@ class ObjectReconstructor(Daemon):
|
|
911
912
|
except StopIteration:
|
912
913
|
break
|
913
914
|
attempts_remaining -= 1
|
915
|
+
conn = None
|
914
916
|
try:
|
915
917
|
with Timeout(self.http_timeout):
|
916
|
-
|
918
|
+
conn = http_connect(
|
917
919
|
node['replication_ip'], node['replication_port'],
|
918
920
|
node['device'], job['partition'], 'REPLICATE',
|
919
|
-
'', headers=headers)
|
921
|
+
'', headers=headers)
|
922
|
+
resp = conn.getresponse()
|
920
923
|
if resp.status == HTTP_INSUFFICIENT_STORAGE:
|
921
924
|
self.logger.error(
|
922
925
|
'%s responded as unmounted',
|
@@ -939,6 +942,9 @@ class ObjectReconstructor(Daemon):
|
|
939
942
|
'from %r' % _full_path(
|
940
943
|
node, job['partition'], '',
|
941
944
|
job['policy']))
|
945
|
+
finally:
|
946
|
+
if conn:
|
947
|
+
conn.close()
|
942
948
|
if remote_suffixes is None:
|
943
949
|
raise SuffixSyncError('Unable to get remote suffix hashes')
|
944
950
|
|
@@ -1556,3 +1562,21 @@ class ObjectReconstructor(Daemon):
|
|
1556
1562
|
self.logger.debug('reconstruction sleeping for %s seconds.',
|
1557
1563
|
self.interval)
|
1558
1564
|
sleep(self.interval)
|
1565
|
+
|
1566
|
+
|
1567
|
+
def main():
|
1568
|
+
parser = OptionParser("%prog CONFIG [options]")
|
1569
|
+
parser.add_option('-d', '--devices',
|
1570
|
+
help='Reconstruct only given devices. '
|
1571
|
+
'Comma-separated list. '
|
1572
|
+
'Only has effect if --once is used.')
|
1573
|
+
parser.add_option('-p', '--partitions',
|
1574
|
+
help='Reconstruct only given partitions. '
|
1575
|
+
'Comma-separated list. '
|
1576
|
+
'Only has effect if --once is used.')
|
1577
|
+
conf_file, options = parse_options(parser=parser, once=True)
|
1578
|
+
run_daemon(ObjectReconstructor, conf_file, **options)
|
1579
|
+
|
1580
|
+
|
1581
|
+
if __name__ == '__main__':
|
1582
|
+
main()
|
swift/obj/replicator.py
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
16
|
from collections import defaultdict
|
17
|
+
from optparse import OptionParser
|
17
18
|
import os
|
18
19
|
import errno
|
19
20
|
from os.path import isdir, isfile, join, dirname
|
@@ -35,9 +36,9 @@ from swift.common.utils import whataremyips, unlink_older_than, \
|
|
35
36
|
rsync_module_interpolation, mkdirs, config_true_value, \
|
36
37
|
config_auto_int_value, storage_directory, \
|
37
38
|
load_recon_cache, PrefixLoggerAdapter, parse_override_options, \
|
38
|
-
distribute_evenly, listdir, node_to_string
|
39
|
+
distribute_evenly, listdir, node_to_string, parse_options
|
39
40
|
from swift.common.bufferedhttp import http_connect
|
40
|
-
from swift.common.daemon import Daemon
|
41
|
+
from swift.common.daemon import Daemon, run_daemon
|
41
42
|
from swift.common.http import HTTP_OK, HTTP_INSUFFICIENT_STORAGE
|
42
43
|
from swift.common.recon import RECON_OBJECT_FILE, DEFAULT_RECON_CACHE_PATH
|
43
44
|
from swift.obj import ssync_sender
|
@@ -195,6 +196,12 @@ class ObjectReplicator(Daemon):
|
|
195
196
|
'operation, please disable handoffs_first and '
|
196
197
|
'handoff_delete before the next '
|
197
198
|
'normal rebalance')
|
199
|
+
if all(self.load_object_ring(p).replica_count <= self.handoff_delete
|
200
|
+
for p in self.policies):
|
201
|
+
self.logger.warning('No storage policies found for which '
|
202
|
+
'handoff_delete=%d would have an effect. '
|
203
|
+
'Disabling.', self.handoff_delete)
|
204
|
+
self.handoff_delete = 0
|
198
205
|
self.is_multiprocess_worker = None
|
199
206
|
self._df_router = DiskFileRouter(conf, self.logger)
|
200
207
|
self._child_process_reaper_queue = queue.LightQueue()
|
@@ -473,7 +480,10 @@ class ObjectReplicator(Daemon):
|
|
473
480
|
node['replication_ip'], node['replication_port'],
|
474
481
|
node['device'], job['partition'], 'REPLICATE',
|
475
482
|
'/' + '-'.join(suffixes), headers=headers)
|
476
|
-
|
483
|
+
try:
|
484
|
+
conn.getresponse().read()
|
485
|
+
finally:
|
486
|
+
conn.close()
|
477
487
|
return success, {}
|
478
488
|
|
479
489
|
def ssync(self, node, job, suffixes, remote_check_objs=None):
|
@@ -554,7 +564,8 @@ class ObjectReplicator(Daemon):
|
|
554
564
|
if self.handoff_delete:
|
555
565
|
# delete handoff if we have had handoff_delete successes
|
556
566
|
successes_count = len([resp for resp in responses if resp])
|
557
|
-
delete_handoff = successes_count >=
|
567
|
+
delete_handoff = successes_count >= min(
|
568
|
+
self.handoff_delete, len(job['nodes']))
|
558
569
|
else:
|
559
570
|
# delete handoff if all syncs were successful
|
560
571
|
delete_handoff = len(responses) == len(job['nodes']) and \
|
@@ -608,7 +619,8 @@ class ObjectReplicator(Daemon):
|
|
608
619
|
try:
|
609
620
|
tpool.execute(shutil.rmtree, path)
|
610
621
|
except OSError as e:
|
611
|
-
if e.errno not in (errno.ENOENT, errno.ENOTEMPTY, errno.ENODATA
|
622
|
+
if e.errno not in (errno.ENOENT, errno.ENOTEMPTY, errno.ENODATA,
|
623
|
+
errno.EUCLEAN):
|
612
624
|
# Don't worry if there was a race to create or delete,
|
613
625
|
# or some disk corruption that happened after the sync
|
614
626
|
raise
|
@@ -675,25 +687,30 @@ class ObjectReplicator(Daemon):
|
|
675
687
|
continue
|
676
688
|
try:
|
677
689
|
with Timeout(self.http_timeout):
|
678
|
-
|
690
|
+
conn = http_connect(
|
679
691
|
node['replication_ip'], node['replication_port'],
|
680
692
|
node['device'], job['partition'], 'REPLICATE',
|
681
|
-
'', headers=headers)
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
693
|
+
'', headers=headers)
|
694
|
+
try:
|
695
|
+
resp = conn.getresponse()
|
696
|
+
if resp.status == HTTP_INSUFFICIENT_STORAGE:
|
697
|
+
self.logger.error('%s responded as unmounted',
|
698
|
+
node_str)
|
699
|
+
attempts_left += 1
|
700
|
+
failure_devs_info.add((node['replication_ip'],
|
701
|
+
node['device']))
|
702
|
+
continue
|
703
|
+
if resp.status != HTTP_OK:
|
704
|
+
self.logger.error(
|
705
|
+
"Invalid response %(resp)s "
|
706
|
+
"from %(remote)s",
|
707
|
+
{'resp': resp.status, 'remote': node_str})
|
708
|
+
failure_devs_info.add((node['replication_ip'],
|
709
|
+
node['device']))
|
710
|
+
continue
|
711
|
+
remote_hash = pickle.loads(resp.read())
|
712
|
+
finally:
|
713
|
+
conn.close()
|
697
714
|
del resp
|
698
715
|
suffixes = [suffix for suffix in local_hash if
|
699
716
|
local_hash[suffix] !=
|
@@ -1140,3 +1157,25 @@ class ObjectReplicator(Daemon):
|
|
1140
1157
|
# This method is called after run_once using multiple workers.
|
1141
1158
|
update = self.aggregate_recon_update()
|
1142
1159
|
dump_recon_cache(update, self.rcache, self.logger)
|
1160
|
+
|
1161
|
+
|
1162
|
+
def main():
|
1163
|
+
parser = OptionParser("%prog CONFIG [options]")
|
1164
|
+
parser.add_option('-d', '--devices',
|
1165
|
+
help='Replicate only given devices. '
|
1166
|
+
'Comma-separated list. '
|
1167
|
+
'Only has effect if --once is used.')
|
1168
|
+
parser.add_option('-p', '--partitions',
|
1169
|
+
help='Replicate only given partitions. '
|
1170
|
+
'Comma-separated list. '
|
1171
|
+
'Only has effect if --once is used.')
|
1172
|
+
parser.add_option('-i', '--policies',
|
1173
|
+
help='Replicate only given policy indices. '
|
1174
|
+
'Comma-separated list. '
|
1175
|
+
'Only has effect if --once is used.')
|
1176
|
+
conf_file, options = parse_options(parser=parser, once=True)
|
1177
|
+
run_daemon(ObjectReplicator, conf_file, **options)
|
1178
|
+
|
1179
|
+
|
1180
|
+
if __name__ == '__main__':
|
1181
|
+
main()
|