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.
Files changed (112) hide show
  1. swift/account/auditor.py +11 -0
  2. swift/account/reaper.py +11 -1
  3. swift/account/replicator.py +22 -0
  4. swift/account/server.py +12 -1
  5. swift-2.33.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
  6. swift-2.33.0.data/scripts/swift-config → swift/cli/config.py +1 -1
  7. swift-2.33.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
  8. swift-2.33.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
  9. swift-2.33.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
  10. swift/cli/info.py +103 -2
  11. swift-2.33.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
  12. swift-2.33.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
  13. swift/cli/recon_cron.py +5 -5
  14. swift-2.33.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  15. swift/cli/relinker.py +1 -1
  16. swift/cli/ringbuilder.py +24 -0
  17. swift/common/db.py +2 -1
  18. swift/common/db_auditor.py +2 -2
  19. swift/common/db_replicator.py +6 -0
  20. swift/common/exceptions.py +12 -0
  21. swift/common/manager.py +102 -0
  22. swift/common/memcached.py +6 -13
  23. swift/common/middleware/account_quotas.py +144 -43
  24. swift/common/middleware/backend_ratelimit.py +166 -24
  25. swift/common/middleware/catch_errors.py +1 -3
  26. swift/common/middleware/cname_lookup.py +3 -5
  27. swift/common/middleware/container_sync.py +6 -10
  28. swift/common/middleware/crypto/crypto_utils.py +4 -5
  29. swift/common/middleware/crypto/decrypter.py +4 -5
  30. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  31. swift/common/middleware/proxy_logging.py +22 -16
  32. swift/common/middleware/ratelimit.py +6 -7
  33. swift/common/middleware/recon.py +6 -7
  34. swift/common/middleware/s3api/acl_handlers.py +9 -0
  35. swift/common/middleware/s3api/controllers/multi_upload.py +1 -9
  36. swift/common/middleware/s3api/controllers/obj.py +20 -1
  37. swift/common/middleware/s3api/s3api.py +2 -0
  38. swift/common/middleware/s3api/s3request.py +171 -62
  39. swift/common/middleware/s3api/s3response.py +35 -6
  40. swift/common/middleware/s3api/s3token.py +2 -2
  41. swift/common/middleware/s3api/utils.py +1 -0
  42. swift/common/middleware/slo.py +153 -52
  43. swift/common/middleware/tempauth.py +6 -4
  44. swift/common/middleware/tempurl.py +2 -2
  45. swift/common/middleware/x_profile/exceptions.py +1 -4
  46. swift/common/middleware/x_profile/html_viewer.py +9 -10
  47. swift/common/middleware/x_profile/profile_model.py +1 -2
  48. swift/common/middleware/xprofile.py +1 -2
  49. swift/common/request_helpers.py +69 -0
  50. swift/common/statsd_client.py +207 -0
  51. swift/common/utils/__init__.py +97 -1635
  52. swift/common/utils/base.py +138 -0
  53. swift/common/utils/config.py +443 -0
  54. swift/common/utils/logs.py +999 -0
  55. swift/common/wsgi.py +11 -3
  56. swift/container/auditor.py +11 -0
  57. swift/container/backend.py +10 -10
  58. swift/container/reconciler.py +11 -2
  59. swift/container/replicator.py +22 -1
  60. swift/container/server.py +12 -1
  61. swift/container/sharder.py +36 -12
  62. swift/container/sync.py +11 -1
  63. swift/container/updater.py +11 -2
  64. swift/obj/auditor.py +18 -2
  65. swift/obj/diskfile.py +8 -6
  66. swift/obj/expirer.py +155 -36
  67. swift/obj/reconstructor.py +28 -4
  68. swift/obj/replicator.py +61 -22
  69. swift/obj/server.py +64 -36
  70. swift/obj/updater.py +11 -2
  71. swift/proxy/controllers/base.py +38 -22
  72. swift/proxy/controllers/obj.py +23 -26
  73. swift/proxy/server.py +15 -1
  74. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +11 -3
  75. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/METADATA +6 -5
  76. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/RECORD +81 -107
  77. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +38 -0
  78. swift-2.34.0.dist-info/pbr.json +1 -0
  79. swift-2.33.0.data/scripts/swift-account-auditor +0 -23
  80. swift-2.33.0.data/scripts/swift-account-info +0 -52
  81. swift-2.33.0.data/scripts/swift-account-reaper +0 -23
  82. swift-2.33.0.data/scripts/swift-account-replicator +0 -34
  83. swift-2.33.0.data/scripts/swift-account-server +0 -23
  84. swift-2.33.0.data/scripts/swift-container-auditor +0 -23
  85. swift-2.33.0.data/scripts/swift-container-info +0 -59
  86. swift-2.33.0.data/scripts/swift-container-reconciler +0 -21
  87. swift-2.33.0.data/scripts/swift-container-replicator +0 -34
  88. swift-2.33.0.data/scripts/swift-container-server +0 -23
  89. swift-2.33.0.data/scripts/swift-container-sharder +0 -37
  90. swift-2.33.0.data/scripts/swift-container-sync +0 -23
  91. swift-2.33.0.data/scripts/swift-container-updater +0 -23
  92. swift-2.33.0.data/scripts/swift-dispersion-report +0 -24
  93. swift-2.33.0.data/scripts/swift-form-signature +0 -20
  94. swift-2.33.0.data/scripts/swift-init +0 -119
  95. swift-2.33.0.data/scripts/swift-object-auditor +0 -29
  96. swift-2.33.0.data/scripts/swift-object-expirer +0 -33
  97. swift-2.33.0.data/scripts/swift-object-info +0 -60
  98. swift-2.33.0.data/scripts/swift-object-reconstructor +0 -33
  99. swift-2.33.0.data/scripts/swift-object-relinker +0 -23
  100. swift-2.33.0.data/scripts/swift-object-replicator +0 -37
  101. swift-2.33.0.data/scripts/swift-object-server +0 -27
  102. swift-2.33.0.data/scripts/swift-object-updater +0 -23
  103. swift-2.33.0.data/scripts/swift-proxy-server +0 -23
  104. swift-2.33.0.data/scripts/swift-recon +0 -24
  105. swift-2.33.0.data/scripts/swift-recon-cron +0 -24
  106. swift-2.33.0.data/scripts/swift-ring-builder +0 -37
  107. swift-2.33.0.data/scripts/swift-ring-builder-analyzer +0 -22
  108. swift-2.33.0.data/scripts/swift-ring-composer +0 -22
  109. swift-2.33.0.dist-info/pbr.json +0 -1
  110. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
  111. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
  112. {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
- if is_legacy_conf:
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
- def read_conf_for_queue_access(self, swift):
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
- self.swift = swift or InternalClient(
126
- self.ic_conf_path, 'Swift Object Expirer', request_tries,
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
- self.processes = int(self.conf.get('processes', 0))
132
- self.process = int(self.conf.get('process', 0))
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 the object that doesn't reach
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 one task
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
- is_async = o.get('content_type') == ASYNC_DELETE_TYPE
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 get_process_values(self, kwargs):
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 = int(kwargs['processes'])
498
+ self.processes = non_negative_int(kwargs['processes'])
399
499
 
400
500
  if kwargs.get('process') is not None:
401
- self.process = int(kwargs['process'])
501
+ self.process = non_negative_int(kwargs['process'])
402
502
 
403
- if self.process < 0:
404
- raise ValueError(
405
- 'process must be an integer greater than or equal to 0')
503
+ self._validate_processes_config()
406
504
 
407
- if self.processes < 0:
408
- raise ValueError(
409
- 'processes must be an integer greater than or equal to 0')
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()
@@ -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
- resp = http_connect(
918
+ conn = http_connect(
917
919
  node['replication_ip'], node['replication_port'],
918
920
  node['device'], job['partition'], 'REPLICATE',
919
- '', headers=headers).getresponse()
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
- conn.getresponse().read()
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 >= self.handoff_delete
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
- resp = http_connect(
690
+ conn = http_connect(
679
691
  node['replication_ip'], node['replication_port'],
680
692
  node['device'], job['partition'], 'REPLICATE',
681
- '', headers=headers).getresponse()
682
- if resp.status == HTTP_INSUFFICIENT_STORAGE:
683
- self.logger.error('%s responded as unmounted',
684
- node_str)
685
- attempts_left += 1
686
- failure_devs_info.add((node['replication_ip'],
687
- node['device']))
688
- continue
689
- if resp.status != HTTP_OK:
690
- self.logger.error(
691
- "Invalid response %(resp)s from %(remote)s",
692
- {'resp': resp.status, 'remote': node_str})
693
- failure_devs_info.add((node['replication_ip'],
694
- node['device']))
695
- continue
696
- remote_hash = pickle.loads(resp.read())
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()