swift 2.23.2__py3-none-any.whl → 2.35.0__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 (208) hide show
  1. swift/__init__.py +29 -50
  2. swift/account/auditor.py +21 -118
  3. swift/account/backend.py +33 -28
  4. swift/account/reaper.py +37 -28
  5. swift/account/replicator.py +22 -0
  6. swift/account/server.py +60 -26
  7. swift/account/utils.py +28 -11
  8. swift-2.23.2.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.2.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.2.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.2.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.2.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +183 -29
  17. swift/cli/manage_shard_ranges.py +708 -37
  18. swift-2.23.2.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.2.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.2.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.2.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  23. swift/cli/relinker.py +807 -126
  24. swift/cli/reload.py +135 -0
  25. swift/cli/ringbuilder.py +217 -20
  26. swift/cli/ringcomposer.py +0 -1
  27. swift/cli/shard-info.py +4 -3
  28. swift/common/base_storage_server.py +9 -20
  29. swift/common/bufferedhttp.py +48 -74
  30. swift/common/constraints.py +20 -15
  31. swift/common/container_sync_realms.py +9 -11
  32. swift/common/daemon.py +25 -8
  33. swift/common/db.py +198 -127
  34. swift/common/db_auditor.py +168 -0
  35. swift/common/db_replicator.py +95 -55
  36. swift/common/digest.py +141 -0
  37. swift/common/direct_client.py +144 -33
  38. swift/common/error_limiter.py +93 -0
  39. swift/common/exceptions.py +25 -1
  40. swift/common/header_key_dict.py +2 -9
  41. swift/common/http_protocol.py +373 -0
  42. swift/common/internal_client.py +129 -59
  43. swift/common/linkat.py +3 -4
  44. swift/common/manager.py +284 -67
  45. swift/common/memcached.py +396 -147
  46. swift/common/middleware/__init__.py +4 -0
  47. swift/common/middleware/account_quotas.py +211 -46
  48. swift/common/middleware/acl.py +3 -8
  49. swift/common/middleware/backend_ratelimit.py +230 -0
  50. swift/common/middleware/bulk.py +22 -34
  51. swift/common/middleware/catch_errors.py +1 -3
  52. swift/common/middleware/cname_lookup.py +6 -11
  53. swift/common/middleware/container_quotas.py +1 -1
  54. swift/common/middleware/container_sync.py +39 -17
  55. swift/common/middleware/copy.py +12 -0
  56. swift/common/middleware/crossdomain.py +22 -9
  57. swift/common/middleware/crypto/__init__.py +2 -1
  58. swift/common/middleware/crypto/crypto_utils.py +11 -15
  59. swift/common/middleware/crypto/decrypter.py +28 -11
  60. swift/common/middleware/crypto/encrypter.py +12 -17
  61. swift/common/middleware/crypto/keymaster.py +8 -15
  62. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  63. swift/common/middleware/dlo.py +15 -11
  64. swift/common/middleware/domain_remap.py +5 -4
  65. swift/common/middleware/etag_quoter.py +128 -0
  66. swift/common/middleware/formpost.py +73 -70
  67. swift/common/middleware/gatekeeper.py +8 -1
  68. swift/common/middleware/keystoneauth.py +33 -3
  69. swift/common/middleware/list_endpoints.py +4 -4
  70. swift/common/middleware/listing_formats.py +85 -49
  71. swift/common/middleware/memcache.py +4 -81
  72. swift/common/middleware/name_check.py +3 -2
  73. swift/common/middleware/proxy_logging.py +160 -92
  74. swift/common/middleware/ratelimit.py +17 -10
  75. swift/common/middleware/read_only.py +6 -4
  76. swift/common/middleware/recon.py +59 -22
  77. swift/common/middleware/s3api/acl_handlers.py +25 -3
  78. swift/common/middleware/s3api/acl_utils.py +6 -1
  79. swift/common/middleware/s3api/controllers/__init__.py +6 -0
  80. swift/common/middleware/s3api/controllers/acl.py +3 -2
  81. swift/common/middleware/s3api/controllers/bucket.py +242 -137
  82. swift/common/middleware/s3api/controllers/logging.py +2 -2
  83. swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
  84. swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
  85. swift/common/middleware/s3api/controllers/obj.py +112 -8
  86. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  87. swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
  88. swift/common/middleware/s3api/controllers/tagging.py +57 -0
  89. swift/common/middleware/s3api/controllers/versioning.py +36 -7
  90. swift/common/middleware/s3api/etree.py +22 -9
  91. swift/common/middleware/s3api/exception.py +0 -4
  92. swift/common/middleware/s3api/s3api.py +113 -41
  93. swift/common/middleware/s3api/s3request.py +384 -218
  94. swift/common/middleware/s3api/s3response.py +126 -23
  95. swift/common/middleware/s3api/s3token.py +16 -17
  96. swift/common/middleware/s3api/schema/delete.rng +1 -1
  97. swift/common/middleware/s3api/subresource.py +7 -10
  98. swift/common/middleware/s3api/utils.py +27 -10
  99. swift/common/middleware/slo.py +665 -358
  100. swift/common/middleware/staticweb.py +64 -37
  101. swift/common/middleware/symlink.py +52 -19
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +192 -174
  104. swift/common/middleware/versioned_writes/__init__.py +51 -0
  105. swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
  106. swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
  107. swift/common/middleware/x_profile/exceptions.py +1 -4
  108. swift/common/middleware/x_profile/html_viewer.py +18 -19
  109. swift/common/middleware/x_profile/profile_model.py +1 -2
  110. swift/common/middleware/xprofile.py +10 -10
  111. swift-2.23.2.data/scripts/swift-container-server → swift/common/recon.py +13 -8
  112. swift/common/registry.py +147 -0
  113. swift/common/request_helpers.py +324 -57
  114. swift/common/ring/builder.py +67 -25
  115. swift/common/ring/composite_builder.py +1 -1
  116. swift/common/ring/ring.py +177 -51
  117. swift/common/ring/utils.py +1 -1
  118. swift/common/splice.py +10 -6
  119. swift/common/statsd_client.py +205 -0
  120. swift/common/storage_policy.py +49 -44
  121. swift/common/swob.py +86 -102
  122. swift/common/{utils.py → utils/__init__.py} +2191 -2762
  123. swift/common/utils/base.py +131 -0
  124. swift/common/utils/config.py +433 -0
  125. swift/common/utils/ipaddrs.py +256 -0
  126. swift/common/utils/libc.py +345 -0
  127. swift/common/utils/logs.py +859 -0
  128. swift/common/utils/timestamp.py +412 -0
  129. swift/common/wsgi.py +555 -536
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +552 -227
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +397 -176
  135. swift/container/sharder.py +1580 -639
  136. swift/container/sync.py +94 -88
  137. swift/container/updater.py +53 -32
  138. swift/obj/auditor.py +153 -35
  139. swift/obj/diskfile.py +466 -217
  140. swift/obj/expirer.py +406 -124
  141. swift/obj/mem_diskfile.py +7 -4
  142. swift/obj/mem_server.py +1 -0
  143. swift/obj/reconstructor.py +523 -262
  144. swift/obj/replicator.py +249 -188
  145. swift/obj/server.py +213 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +653 -139
  149. swift/obj/watchers/__init__.py +0 -0
  150. swift/obj/watchers/dark_data.py +213 -0
  151. swift/proxy/controllers/account.py +11 -11
  152. swift/proxy/controllers/base.py +848 -604
  153. swift/proxy/controllers/container.py +452 -86
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1009 -490
  156. swift/proxy/server.py +185 -112
  157. swift-2.35.0.dist-info/AUTHORS +501 -0
  158. swift-2.35.0.dist-info/LICENSE +202 -0
  159. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/METADATA +52 -61
  160. swift-2.35.0.dist-info/RECORD +201 -0
  161. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  162. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  163. swift-2.35.0.dist-info/pbr.json +1 -0
  164. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  165. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  166. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  167. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  168. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  169. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  170. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  171. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  172. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  173. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  174. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  175. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  176. swift-2.23.2.data/scripts/swift-account-auditor +0 -23
  177. swift-2.23.2.data/scripts/swift-account-info +0 -51
  178. swift-2.23.2.data/scripts/swift-account-reaper +0 -23
  179. swift-2.23.2.data/scripts/swift-account-replicator +0 -34
  180. swift-2.23.2.data/scripts/swift-account-server +0 -23
  181. swift-2.23.2.data/scripts/swift-container-auditor +0 -23
  182. swift-2.23.2.data/scripts/swift-container-info +0 -51
  183. swift-2.23.2.data/scripts/swift-container-reconciler +0 -21
  184. swift-2.23.2.data/scripts/swift-container-replicator +0 -34
  185. swift-2.23.2.data/scripts/swift-container-sharder +0 -33
  186. swift-2.23.2.data/scripts/swift-container-sync +0 -23
  187. swift-2.23.2.data/scripts/swift-container-updater +0 -23
  188. swift-2.23.2.data/scripts/swift-dispersion-report +0 -24
  189. swift-2.23.2.data/scripts/swift-form-signature +0 -20
  190. swift-2.23.2.data/scripts/swift-init +0 -119
  191. swift-2.23.2.data/scripts/swift-object-auditor +0 -29
  192. swift-2.23.2.data/scripts/swift-object-expirer +0 -33
  193. swift-2.23.2.data/scripts/swift-object-info +0 -60
  194. swift-2.23.2.data/scripts/swift-object-reconstructor +0 -33
  195. swift-2.23.2.data/scripts/swift-object-relinker +0 -41
  196. swift-2.23.2.data/scripts/swift-object-replicator +0 -37
  197. swift-2.23.2.data/scripts/swift-object-server +0 -27
  198. swift-2.23.2.data/scripts/swift-object-updater +0 -23
  199. swift-2.23.2.data/scripts/swift-proxy-server +0 -23
  200. swift-2.23.2.data/scripts/swift-recon +0 -24
  201. swift-2.23.2.data/scripts/swift-ring-builder +0 -24
  202. swift-2.23.2.data/scripts/swift-ring-builder-analyzer +0 -22
  203. swift-2.23.2.data/scripts/swift-ring-composer +0 -22
  204. swift-2.23.2.dist-info/DESCRIPTION.rst +0 -166
  205. swift-2.23.2.dist-info/RECORD +0 -220
  206. swift-2.23.2.dist-info/metadata.json +0 -1
  207. swift-2.23.2.dist-info/pbr.json +0 -1
  208. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
@@ -19,19 +19,20 @@ import itertools
19
19
  import logging
20
20
 
21
21
  from eventlet import GreenPile, GreenPool, Timeout
22
- import six
23
22
 
24
23
  from swift.common import constraints
25
- from swift.common.daemon import Daemon
24
+ from swift.common.daemon import Daemon, run_daemon
26
25
  from swift.common.direct_client import (
27
26
  direct_head_container, direct_delete_container_object,
28
27
  direct_put_container_object, ClientException)
29
28
  from swift.common.internal_client import InternalClient, UnexpectedResponse
29
+ from swift.common.request_helpers import MISPLACED_OBJECTS_ACCOUNT, \
30
+ USE_REPLICATION_NETWORK_HEADER
30
31
  from swift.common.utils import get_logger, split_path, majority_size, \
31
32
  FileLikeIter, Timestamp, last_modified_date_to_timestamp, \
32
- LRUCache, decode_timestamps
33
+ LRUCache, decode_timestamps, hash_path, parse_options
34
+ from swift.common.storage_policy import POLICIES
33
35
 
34
- MISPLACED_OBJECTS_ACCOUNT = '.misplaced_objects'
35
36
  MISPLACED_OBJECTS_CONTAINER_DIVISOR = 3600 # 1 hour
36
37
  CONTAINER_POLICY_TTL = 30
37
38
 
@@ -85,8 +86,13 @@ def cmp_policy_info(info, remote_info):
85
86
  return 1
86
87
  elif not remote_recreated:
87
88
  return -1
88
- return cmp(remote_info['status_changed_at'],
89
- info['status_changed_at'])
89
+ # both have been recreated, everything devoles to here eventually
90
+ most_recent_successful_delete = max(info['delete_timestamp'],
91
+ remote_info['delete_timestamp'])
92
+ if info['put_timestamp'] < most_recent_successful_delete:
93
+ return 1
94
+ elif remote_info['put_timestamp'] < most_recent_successful_delete:
95
+ return -1
90
96
  return cmp(info['status_changed_at'], remote_info['status_changed_at'])
91
97
 
92
98
 
@@ -224,6 +230,7 @@ def add_to_reconciler_queue(container_ring, account, container, obj,
224
230
  'X-Etag': obj_timestamp,
225
231
  'X-Timestamp': x_timestamp,
226
232
  'X-Content-Type': q_op_type,
233
+ USE_REPLICATION_NETWORK_HEADER: 'true',
227
234
  }
228
235
 
229
236
  def _check_success(*args, **kwargs):
@@ -264,11 +271,7 @@ def parse_raw_obj(obj_info):
264
271
  :returns: a queue entry dict with the keys: q_policy_index, account,
265
272
  container, obj, q_op, q_ts, q_record, and path
266
273
  """
267
- if six.PY2:
268
- raw_obj_name = obj_info['name'].encode('utf-8')
269
- else:
270
- raw_obj_name = obj_info['name']
271
-
274
+ raw_obj_name = obj_info['name']
272
275
  policy_index, obj_name = raw_obj_name.split(':', 1)
273
276
  q_policy_index = int(policy_index)
274
277
  account, container, obj = split_path(obj_name, 3, 3, rest_with_last=True)
@@ -307,7 +310,8 @@ def direct_get_container_policy_index(container_ring, account_name,
307
310
  """
308
311
  def _eat_client_exception(*args):
309
312
  try:
310
- return direct_head_container(*args)
313
+ return direct_head_container(*args, headers={
314
+ USE_REPLICATION_NETWORK_HEADER: 'true'})
311
315
  except ClientException as err:
312
316
  if err.http_status == 404:
313
317
  return err.http_headers
@@ -333,6 +337,10 @@ def direct_delete_container_entry(container_ring, account_name, container_name,
333
337
  object listing. Does not talk to object servers; use this only when a
334
338
  container entry does not actually have a corresponding object.
335
339
  """
340
+ if headers is None:
341
+ headers = {}
342
+ headers[USE_REPLICATION_NETWORK_HEADER] = 'true'
343
+
336
344
  pool = GreenPool()
337
345
  part, nodes = container_ring.get_nodes(account_name, container_name)
338
346
  for node in nodes:
@@ -348,23 +356,45 @@ class ContainerReconciler(Daemon):
348
356
  """
349
357
  Move objects that are in the wrong storage policy.
350
358
  """
359
+ log_route = 'container-reconciler'
351
360
 
352
- def __init__(self, conf):
361
+ def __init__(self, conf, logger=None, swift=None):
353
362
  self.conf = conf
354
363
  # This option defines how long an un-processable misplaced object
355
364
  # marker will be retried before it is abandoned. It is not coupled
356
365
  # with the tombstone reclaim age in the consistency engine.
357
366
  self.reclaim_age = int(conf.get('reclaim_age', 86400 * 7))
358
- self.interval = int(conf.get('interval', 30))
367
+ self.interval = float(conf.get('interval', 30))
359
368
  conf_path = conf.get('__file__') or \
360
369
  '/etc/swift/container-reconciler.conf'
361
- self.logger = get_logger(conf, log_route='container-reconciler')
370
+ self.logger = logger or get_logger(
371
+ conf, log_route=self.log_route)
362
372
  request_tries = int(conf.get('request_tries') or 3)
363
- self.swift = InternalClient(conf_path,
364
- 'Swift Container Reconciler',
365
- request_tries)
373
+ self.swift = swift or InternalClient(
374
+ conf_path,
375
+ 'Swift Container Reconciler',
376
+ request_tries,
377
+ use_replication_network=True,
378
+ global_conf={'log_name': '%s-ic' % conf.get(
379
+ 'log_name', self.log_route)})
380
+ self.swift_dir = conf.get('swift_dir', '/etc/swift')
366
381
  self.stats = defaultdict(int)
367
382
  self.last_stat_time = time.time()
383
+ self.ring_check_interval = float(conf.get('ring_check_interval', 15))
384
+ self.concurrency = int(conf.get('concurrency', 1))
385
+ if self.concurrency < 1:
386
+ raise ValueError("concurrency must be set to at least 1")
387
+ self.processes = int(self.conf.get('processes', 0))
388
+ if self.processes < 0:
389
+ raise ValueError(
390
+ 'processes must be an integer greater than or equal to 0')
391
+ self.process = int(self.conf.get('process', 0))
392
+ if self.process < 0:
393
+ raise ValueError(
394
+ 'process must be an integer greater than or equal to 0')
395
+ if self.processes and self.process >= self.processes:
396
+ raise ValueError(
397
+ 'process must be less than processes')
368
398
 
369
399
  def stats_log(self, metric, msg, *args, **kwargs):
370
400
  """
@@ -400,7 +430,7 @@ class ContainerReconciler(Daemon):
400
430
  an object was manually re-enqued.
401
431
  """
402
432
  q_path = '/%s/%s/%s' % (MISPLACED_OBJECTS_ACCOUNT, container, obj)
403
- x_timestamp = slightly_later_timestamp(max(q_record, q_ts))
433
+ x_timestamp = slightly_later_timestamp(max(q_record, q_ts), offset=2)
404
434
  self.stats_log('pop_queue', 'remove %r (%f) from the queue (%s)',
405
435
  q_path, q_ts, x_timestamp)
406
436
  headers = {'X-Timestamp': x_timestamp}
@@ -408,6 +438,13 @@ class ContainerReconciler(Daemon):
408
438
  self.swift.container_ring, MISPLACED_OBJECTS_ACCOUNT,
409
439
  container, obj, headers=headers)
410
440
 
441
+ def can_reconcile_policy(self, policy_index):
442
+ pol = POLICIES.get_by_index(policy_index)
443
+ if pol:
444
+ pol.load_ring(self.swift_dir, reload_time=self.ring_check_interval)
445
+ return pol.object_ring.next_part_power is None
446
+ return False
447
+
411
448
  def throw_tombstones(self, account, container, obj, timestamp,
412
449
  policy_index, path):
413
450
  """
@@ -474,15 +511,33 @@ class ContainerReconciler(Daemon):
474
511
  container_policy_index, q_policy_index)
475
512
  return True
476
513
 
514
+ # don't reconcile if the source or container policy_index is in the
515
+ # middle of a PPI
516
+ if not self.can_reconcile_policy(q_policy_index):
517
+ self.stats_log('ppi_skip', 'Source policy (%r) in the middle of '
518
+ 'a part power increase (PPI)', q_policy_index)
519
+ return False
520
+ if not self.can_reconcile_policy(container_policy_index):
521
+ self.stats_log('ppi_skip', 'Container policy (%r) in the middle '
522
+ 'of a part power increase (PPI)',
523
+ container_policy_index)
524
+ return False
525
+
477
526
  # check if object exists in the destination already
478
527
  self.logger.debug('checking for %r (%f) in destination '
479
528
  'policy_index %s', path, q_ts,
480
529
  container_policy_index)
481
530
  headers = {
482
531
  'X-Backend-Storage-Policy-Index': container_policy_index}
483
- dest_obj = self.swift.get_object_metadata(account, container, obj,
484
- headers=headers,
485
- acceptable_statuses=(2, 4))
532
+ try:
533
+ dest_obj = self.swift.get_object_metadata(
534
+ account, container, obj, headers=headers,
535
+ acceptable_statuses=(2, 4))
536
+ except UnexpectedResponse:
537
+ self.stats_log('unavailable_destination', '%r (%f) unable to '
538
+ 'determine the destination timestamp, if any',
539
+ path, q_ts)
540
+ return False
486
541
  dest_ts = Timestamp(dest_obj.get('x-backend-timestamp', 0))
487
542
  if dest_ts >= q_ts:
488
543
  self.stats_log('found_object', '%r (%f) in policy_index %s '
@@ -563,7 +618,7 @@ class ContainerReconciler(Daemon):
563
618
  # optimistically move any source with a timestamp >= q_ts
564
619
  ts = max(Timestamp(source_ts), q_ts)
565
620
  # move the object
566
- put_timestamp = slightly_later_timestamp(ts, offset=2)
621
+ put_timestamp = slightly_later_timestamp(ts, offset=3)
567
622
  self.stats_log('copy_attempt', '%r (%f) in policy_index %s will be '
568
623
  'moved to policy_index %s (%s)', path, source_ts,
569
624
  q_policy_index, container_policy_index, put_timestamp)
@@ -603,7 +658,7 @@ class ContainerReconciler(Daemon):
603
658
  Issue a DELETE request against the destination to match the
604
659
  misplaced DELETE against the source.
605
660
  """
606
- delete_timestamp = slightly_later_timestamp(q_ts, offset=2)
661
+ delete_timestamp = slightly_later_timestamp(q_ts, offset=3)
607
662
  self.stats_log('delete_attempt', '%r (%f) in policy_index %s '
608
663
  'will be deleted from policy_index %s (%s)', path,
609
664
  source_ts, q_policy_index, container_policy_index,
@@ -679,9 +734,9 @@ class ContainerReconciler(Daemon):
679
734
  # hit most recent container first instead of waiting on the updaters
680
735
  current_container = get_reconciler_container_name(time.time())
681
736
  yield current_container
682
- container_gen = self.swift.iter_containers(MISPLACED_OBJECTS_ACCOUNT)
683
737
  self.logger.debug('looking for containers in %s',
684
738
  MISPLACED_OBJECTS_ACCOUNT)
739
+ container_gen = self.swift.iter_containers(MISPLACED_OBJECTS_ACCOUNT)
685
740
  while True:
686
741
  one_page = None
687
742
  try:
@@ -698,9 +753,6 @@ class ContainerReconciler(Daemon):
698
753
  # reversed order since we expect older containers to be empty
699
754
  for c in reversed(one_page):
700
755
  container = c['name']
701
- if six.PY2:
702
- # encoding here is defensive
703
- container = container.encode('utf8')
704
756
  if container == current_container:
705
757
  continue # we've already hit this one this pass
706
758
  yield container
@@ -732,29 +784,57 @@ class ContainerReconciler(Daemon):
732
784
  MISPLACED_OBJECTS_ACCOUNT, container,
733
785
  acceptable_statuses=(2, 404, 409, 412))
734
786
 
787
+ def should_process(self, queue_item):
788
+ """
789
+ Check if a given entry should be handled by this process.
790
+
791
+ :param container: the queue container
792
+ :param queue_item: an entry from the queue
793
+ """
794
+ if not self.processes:
795
+ return True
796
+ hexdigest = hash_path(
797
+ queue_item['account'], queue_item['container'], queue_item['obj'])
798
+ return int(hexdigest, 16) % self.processes == self.process
799
+
800
+ def process_queue_item(self, q_container, q_entry, queue_item):
801
+ """
802
+ Process an entry and remove from queue on success.
803
+
804
+ :param q_container: the queue container
805
+ :param q_entry: the raw_obj name from the q_container
806
+ :param queue_item: a parsed entry from the queue
807
+ """
808
+ finished = self.reconcile_object(queue_item)
809
+ if finished:
810
+ self.pop_queue(q_container, q_entry,
811
+ queue_item['q_ts'],
812
+ queue_item['q_record'])
813
+
735
814
  def reconcile(self):
736
815
  """
737
- Main entry point for processing misplaced objects.
816
+ Main entry point for concurrent processing of misplaced objects.
738
817
 
739
- Iterate over all queue entries and delegate to reconcile_object.
818
+ Iterate over all queue entries and delegate processing to spawned
819
+ workers in the pool.
740
820
  """
741
821
  self.logger.debug('pulling items from the queue')
822
+ pool = GreenPool(self.concurrency)
742
823
  for container in self._iter_containers():
824
+ self.logger.debug('checking container %s', container)
743
825
  for raw_obj in self._iter_objects(container):
744
826
  try:
745
- obj_info = parse_raw_obj(raw_obj)
827
+ queue_item = parse_raw_obj(raw_obj)
746
828
  except Exception:
747
829
  self.stats_log('invalid_record',
748
830
  'invalid queue record: %r', raw_obj,
749
831
  level=logging.ERROR, exc_info=True)
750
832
  continue
751
- finished = self.reconcile_object(obj_info)
752
- if finished:
753
- self.pop_queue(container, raw_obj['name'],
754
- obj_info['q_ts'],
755
- obj_info['q_record'])
833
+ if self.should_process(queue_item):
834
+ pool.spawn_n(self.process_queue_item,
835
+ container, raw_obj['name'], queue_item)
756
836
  self.log_stats()
757
- self.logger.debug('finished container %s', container)
837
+ pool.waitall()
758
838
 
759
839
  def run_once(self, *args, **kwargs):
760
840
  """
@@ -772,3 +852,12 @@ class ContainerReconciler(Daemon):
772
852
  self.stats = defaultdict(int)
773
853
  self.logger.info('sleeping between intervals (%ss)', self.interval)
774
854
  time.sleep(self.interval)
855
+
856
+
857
+ def main():
858
+ conf_file, options = parse_options(once=True)
859
+ run_daemon(ContainerReconciler, conf_file, **options)
860
+
861
+
862
+ if __name__ == '__main__':
863
+ main()
@@ -14,22 +14,54 @@
14
14
  # limitations under the License.
15
15
 
16
16
  import os
17
- import itertools
18
17
  import json
19
18
  from collections import defaultdict
20
19
  from eventlet import Timeout
20
+ import optparse
21
+ from random import choice
21
22
 
22
23
  from swift.container.sync_store import ContainerSyncStore
23
- from swift.container.backend import ContainerBroker, DATADIR, SHARDED
24
+ from swift.container.backend import ContainerBroker, DATADIR, SHARDED, \
25
+ merge_shards
24
26
  from swift.container.reconciler import (
25
27
  MISPLACED_OBJECTS_ACCOUNT, incorrect_policy_index,
26
28
  get_reconciler_container_name, get_row_to_q_entry_translator)
27
29
  from swift.common import db_replicator
30
+ from swift.common.daemon import run_daemon
28
31
  from swift.common.storage_policy import POLICIES
29
32
  from swift.common.swob import HTTPOk, HTTPAccepted
30
- from swift.common.exceptions import DeviceUnavailable
31
33
  from swift.common.http import is_success
32
- from swift.common.utils import Timestamp, majority_size, get_db_files
34
+ from swift.common.utils import Timestamp, majority_size, get_db_files, \
35
+ parse_options
36
+
37
+
38
+ def check_merge_own_shard_range(shards, broker, logger, source):
39
+ """
40
+ If broker has own_shard_range *with an epoch* then filter out an
41
+ own_shard_range *without an epoch*, and log a warning about it.
42
+
43
+ :param shards: a list of candidate ShardRanges to merge
44
+ :param broker: a ContainerBroker
45
+ :param logger: a logger
46
+ :param source: string to log as source of shards
47
+ :return: a list of ShardRanges to actually merge
48
+ """
49
+ # work-around for https://bugs.launchpad.net/swift/+bug/1980451
50
+ own_sr = broker.get_own_shard_range()
51
+ if own_sr.epoch is None:
52
+ return shards
53
+ to_merge = []
54
+ for shard in shards:
55
+ if shard['name'] == own_sr.name and not shard['epoch']:
56
+ shard_copy = dict(shard)
57
+ new_content = merge_shards(shard_copy, dict(own_sr))
58
+ if new_content and shard_copy['epoch'] is None:
59
+ logger.warning(
60
+ 'Ignoring remote osr w/o epoch, own_sr: %r, remote_sr: %r,'
61
+ ' source: %s', dict(own_sr), shard, source)
62
+ continue
63
+ to_merge.append(shard)
64
+ return to_merge
33
65
 
34
66
 
35
67
  class ContainerReplicator(db_replicator.Replicator):
@@ -139,23 +171,44 @@ class ContainerReplicator(db_replicator.Replicator):
139
171
  with Timeout(self.node_timeout):
140
172
  response = http.replicate('get_shard_ranges')
141
173
  if response and is_success(response.status):
142
- broker.merge_shard_ranges(json.loads(
143
- response.data.decode('ascii')))
174
+ shards = json.loads(response.data.decode('ascii'))
175
+ shards = check_merge_own_shard_range(
176
+ shards, broker, self.logger, '%s%s' % (http.host, http.path))
177
+ broker.merge_shard_ranges(shards)
144
178
 
145
179
  def find_local_handoff_for_part(self, part):
146
180
  """
147
- Look through devices in the ring for the first handoff device that was
148
- identified during job creation as available on this node.
181
+ Find a device in the ring that is on this node on which to place a
182
+ partition. Preference is given to a device that is a primary location
183
+ for the partition. If no such device is found then a local device with
184
+ weight is chosen, and failing that any local device.
149
185
 
186
+ :param part: a partition
150
187
  :returns: a node entry from the ring
151
188
  """
152
- nodes = self.ring.get_part_nodes(part)
153
- more_nodes = self.ring.get_more_nodes(part)
189
+ if not self._local_device_ids:
190
+ raise RuntimeError('Cannot find local handoff; no local devices')
154
191
 
155
- for node in itertools.chain(nodes, more_nodes):
192
+ for node in self.ring.get_part_nodes(part):
156
193
  if node['id'] in self._local_device_ids:
157
194
  return node
158
- return None
195
+
196
+ # don't attempt to minimize handoff depth: just choose any local
197
+ # device, but start by only picking a device with a weight, just in
198
+ # case some devices are being drained...
199
+ local_devs_with_weight = [
200
+ dev for dev in self._local_device_ids.values()
201
+ if dev.get('weight', 0)]
202
+ if local_devs_with_weight:
203
+ return choice(local_devs_with_weight)
204
+
205
+ # we have to return something, so choose any local device..
206
+ node = choice(list(self._local_device_ids.values()))
207
+ self.logger.warning(
208
+ "Could not find a non-zero weight device for handoff partition "
209
+ "%d, falling back device %s" %
210
+ (part, node['device']))
211
+ return node
159
212
 
160
213
  def get_reconciler_broker(self, timestamp):
161
214
  """
@@ -173,14 +226,12 @@ class ContainerReplicator(db_replicator.Replicator):
173
226
  account = MISPLACED_OBJECTS_ACCOUNT
174
227
  part = self.ring.get_part(account, container)
175
228
  node = self.find_local_handoff_for_part(part)
176
- if not node:
177
- raise DeviceUnavailable(
178
- 'No mounted devices found suitable to Handoff reconciler '
179
- 'container %s in partition %s' % (container, part))
180
- broker = ContainerBroker.create_broker(
229
+ broker, initialized = ContainerBroker.create_broker(
181
230
  os.path.join(self.root, node['device']), part, account, container,
182
231
  logger=self.logger, put_timestamp=timestamp,
183
232
  storage_policy_index=0)
233
+ self.logger.increment('reconciler_db_created' if initialized
234
+ else 'reconciler_db_exists')
184
235
  if self.reconciler_containers is not None:
185
236
  self.reconciler_containers[container] = part, broker, node['id']
186
237
  return broker
@@ -198,8 +249,9 @@ class ContainerReplicator(db_replicator.Replicator):
198
249
 
199
250
  try:
200
251
  reconciler = self.get_reconciler_broker(container)
201
- except DeviceUnavailable as e:
202
- self.logger.warning('DeviceUnavailable: %s', e)
252
+ except Exception:
253
+ self.logger.exception('Failed to get reconciler broker for '
254
+ 'container %s', container)
203
255
  return False
204
256
  self.logger.debug('Adding %d objects to the reconciler at %s',
205
257
  len(item_list), reconciler.db_file)
@@ -377,13 +429,35 @@ class ContainerReplicatorRpc(db_replicator.ReplicatorRpc):
377
429
  def _post_rsync_then_merge_hook(self, existing_broker, new_broker):
378
430
  # Note the following hook will need to change to using a pointer and
379
431
  # limit in the future.
380
- new_broker.merge_shard_ranges(
381
- existing_broker.get_all_shard_range_data())
432
+ shards = existing_broker.get_all_shard_range_data()
433
+ shards = check_merge_own_shard_range(
434
+ shards, new_broker, self.logger, 'rsync')
435
+ new_broker.merge_shard_ranges(shards)
382
436
 
383
437
  def merge_shard_ranges(self, broker, args):
384
- broker.merge_shard_ranges(args[0])
438
+ shards = check_merge_own_shard_range(
439
+ args[0], broker, self.logger, 'repl_req')
440
+ broker.merge_shard_ranges(shards)
385
441
  return HTTPAccepted()
386
442
 
387
443
  def get_shard_ranges(self, broker, args):
388
444
  return HTTPOk(headers={'Content-Type': 'application/json'},
389
445
  body=json.dumps(broker.get_all_shard_range_data()))
446
+
447
+
448
+ def main():
449
+ parser = optparse.OptionParser("%prog CONFIG [options]")
450
+ parser.add_option('-d', '--devices',
451
+ help=('Replicate only given devices. '
452
+ 'Comma-separated list. '
453
+ 'Only has effect if --once is used.'))
454
+ parser.add_option('-p', '--partitions',
455
+ help=('Replicate only given partitions. '
456
+ 'Comma-separated list. '
457
+ 'Only has effect if --once is used.'))
458
+ conf_file, options = parse_options(parser=parser, once=True)
459
+ run_daemon(ContainerReplicator, conf_file, **options)
460
+
461
+
462
+ if __name__ == '__main__':
463
+ main()