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
@@ -20,8 +20,7 @@ import errno
20
20
  import os
21
21
  from uuid import uuid4
22
22
 
23
- import six
24
- from six.moves import range
23
+ from urllib.parse import unquote
25
24
  import sqlite3
26
25
  from eventlet import tpool
27
26
 
@@ -29,9 +28,10 @@ from swift.common.constraints import CONTAINER_LISTING_LIMIT
29
28
  from swift.common.exceptions import LockTimeout
30
29
  from swift.common.utils import Timestamp, encode_timestamps, \
31
30
  decode_timestamps, extract_swift_bytes, storage_directory, hash_path, \
32
- ShardRange, renamer, find_shard_range, MD5_OF_EMPTY_STRING, mkdirs, \
33
- get_db_files, parse_db_filename, make_db_file_path, split_path
34
- from swift.common.db import DatabaseBroker, utf8encode, BROKER_TIMEOUT, \
31
+ ShardRange, renamer, MD5_OF_EMPTY_STRING, mkdirs, get_db_files, \
32
+ parse_db_filename, make_db_file_path, split_path, RESERVED_BYTE, \
33
+ ShardRangeList, Namespace
34
+ from swift.common.db import DatabaseBroker, BROKER_TIMEOUT, \
35
35
  zero_like, DatabaseAlreadyExists, SQLITE_ARG_LIMIT
36
36
 
37
37
  DATADIR = 'containers'
@@ -52,13 +52,25 @@ SHARD_STATS_STATES = [ShardRange.ACTIVE, ShardRange.SHARDING,
52
52
  SHARD_LISTING_STATES = SHARD_STATS_STATES + [ShardRange.CLEAVED]
53
53
  SHARD_UPDATE_STATES = [ShardRange.CREATED, ShardRange.CLEAVED,
54
54
  ShardRange.ACTIVE, ShardRange.SHARDING]
55
-
55
+ # when auditing a shard gets its own shard range, which could be in any state
56
+ # except FOUND, and any potential acceptors excluding FOUND ranges that may be
57
+ # unwanted overlaps
58
+ SHARD_AUDITING_STATES = [ShardRange.CREATED, ShardRange.CLEAVED,
59
+ ShardRange.ACTIVE, ShardRange.SHARDING,
60
+ ShardRange.SHARDED, ShardRange.SHRINKING,
61
+ ShardRange.SHRUNK]
62
+ # shard's may not be fully populated while in the FOUND and CREATED
63
+ # state, so shards should only update their own shard range's object
64
+ # stats when they are in the following states
65
+ SHARD_UPDATE_STAT_STATES = [ShardRange.CLEAVED, ShardRange.ACTIVE,
66
+ ShardRange.SHARDING, ShardRange.SHARDED,
67
+ ShardRange.SHRINKING, ShardRange.SHRUNK]
56
68
 
57
69
  # attribute names in order used when transforming shard ranges from dicts to
58
70
  # tuples and vice-versa
59
71
  SHARD_RANGE_KEYS = ('name', 'timestamp', 'lower', 'upper', 'object_count',
60
72
  'bytes_used', 'meta_timestamp', 'deleted', 'state',
61
- 'state_timestamp', 'epoch')
73
+ 'state_timestamp', 'epoch', 'reported', 'tombstones')
62
74
 
63
75
  POLICY_STAT_TABLE_CREATE = '''
64
76
  CREATE TABLE policy_stat (
@@ -265,6 +277,7 @@ def merge_shards(shard_data, existing):
265
277
  if existing['timestamp'] < shard_data['timestamp']:
266
278
  # note that currently we do not roll forward any meta or state from
267
279
  # an item that was created at older time, newer created time trumps
280
+ shard_data['reported'] = 0 # reset the latch
268
281
  return True
269
282
  elif existing['timestamp'] > shard_data['timestamp']:
270
283
  return False
@@ -278,9 +291,23 @@ def merge_shards(shard_data, existing):
278
291
  if existing['meta_timestamp'] >= shard_data['meta_timestamp']:
279
292
  for k in ('object_count', 'bytes_used', 'meta_timestamp'):
280
293
  shard_data[k] = existing[k]
294
+ shard_data['tombstones'] = existing.get('tombstones', -1)
281
295
  else:
282
296
  new_content = True
283
297
 
298
+ # We can latch the reported flag
299
+ if existing['reported'] and \
300
+ existing['object_count'] == shard_data['object_count'] and \
301
+ existing['bytes_used'] == shard_data['bytes_used'] and \
302
+ existing.get('tombstones', -1) == shard_data['tombstones'] and \
303
+ existing['state'] == shard_data['state'] and \
304
+ existing['epoch'] == shard_data['epoch']:
305
+ shard_data['reported'] = 1
306
+ else:
307
+ shard_data.setdefault('reported', 0)
308
+ if shard_data['reported'] and not existing['reported']:
309
+ new_content = True
310
+
284
311
  if (existing['state_timestamp'] == shard_data['state_timestamp']
285
312
  and shard_data['state'] > existing['state']):
286
313
  new_content = True
@@ -292,6 +319,38 @@ def merge_shards(shard_data, existing):
292
319
  return new_content
293
320
 
294
321
 
322
+ def sift_shard_ranges(new_shard_ranges, existing_shard_ranges):
323
+ """
324
+ Compares new and existing shard ranges, updating the new shard ranges with
325
+ any more recent state from the existing, and returns shard ranges sorted
326
+ into those that need adding because they contain new or updated state and
327
+ those that need deleting because their state has been superseded.
328
+
329
+ :param new_shard_ranges: a list of dicts, each of which represents a shard
330
+ range.
331
+ :param existing_shard_ranges: a dict mapping shard range names to dicts
332
+ representing a shard range.
333
+ :return: a tuple (to_add, to_delete); to_add is a list of dicts, each of
334
+ which represents a shard range that is to be added to the existing
335
+ shard ranges; to_delete is a set of shard range names that are to be
336
+ deleted.
337
+ """
338
+ to_delete = set()
339
+ to_add = {}
340
+ for item in new_shard_ranges:
341
+ item_ident = item['name']
342
+ existing = existing_shard_ranges.get(item_ident)
343
+ if merge_shards(item, existing):
344
+ # exists with older timestamp
345
+ if item_ident in existing_shard_ranges:
346
+ to_delete.add(item_ident)
347
+ # duplicate entries in item_list
348
+ if (item_ident not in to_add or
349
+ merge_shards(item, to_add[item_ident])):
350
+ to_add[item_ident] = item
351
+ return to_add.values(), to_delete
352
+
353
+
295
354
  class ContainerBroker(DatabaseBroker):
296
355
  """
297
356
  Encapsulates working with a container database.
@@ -299,34 +358,34 @@ class ContainerBroker(DatabaseBroker):
299
358
  Note that this may involve multiple on-disk DB files if the container
300
359
  becomes sharded:
301
360
 
302
- * :attr:`_db_file` is the path to the legacy container DB name, i.e.
303
- ``<hash>.db``. This file should exist for an initialised broker that
304
- has never been sharded, but will not exist once a container has been
305
- sharded.
306
- * :attr:`db_files` is a list of existing db files for the broker. This
307
- list should have at least one entry for an initialised broker, and
308
- should have two entries while a broker is in SHARDING state.
309
- * :attr:`db_file` is the path to whichever db is currently authoritative
310
- for the container. Depending on the container's state, this may not be
311
- the same as the ``db_file`` argument given to :meth:`~__init__`, unless
312
- ``force_db_file`` is True in which case :attr:`db_file` is always equal
313
- to the ``db_file`` argument given to :meth:`~__init__`.
314
- * :attr:`pending_file` is always equal to :attr:`_db_file` extended with
315
- ``.pending``, i.e. ``<hash>.db.pending``.
361
+ * :attr:`_db_file` is the path to the legacy container DB name, i.e.
362
+ ``<hash>.db``. This file should exist for an initialised broker that
363
+ has never been sharded, but will not exist once a container has been
364
+ sharded.
365
+ * :attr:`db_files` is a list of existing db files for the broker. This
366
+ list should have at least one entry for an initialised broker, and
367
+ should have two entries while a broker is in SHARDING state.
368
+ * :attr:`db_file` is the path to whichever db is currently authoritative
369
+ for the container. Depending on the container's state, this may not be
370
+ the same as the ``db_file`` argument given to :meth:`~__init__`, unless
371
+ ``force_db_file`` is True in which case :attr:`db_file` is always equal
372
+ to the ``db_file`` argument given to :meth:`~__init__`.
373
+ * :attr:`pending_file` is always equal to :attr:`_db_file` extended with
374
+ ``.pending``, i.e. ``<hash>.db.pending``.
316
375
  """
317
376
  db_type = 'container'
318
377
  db_contains_type = 'object'
319
378
  db_reclaim_timestamp = 'created_at'
379
+ delete_meta_whitelist = ['x-container-sysmeta-shard-quoted-root',
380
+ 'x-container-sysmeta-shard-root',
381
+ 'x-container-sysmeta-sharding']
320
382
 
321
383
  def __init__(self, db_file, timeout=BROKER_TIMEOUT, logger=None,
322
384
  account=None, container=None, pending_timeout=None,
323
385
  stale_reads_ok=False, skip_commits=False,
324
386
  force_db_file=False):
325
387
  self._init_db_file = db_file
326
- if db_file == ':memory:':
327
- base_db_file = db_file
328
- else:
329
- base_db_file = make_db_file_path(db_file, None)
388
+ base_db_file = make_db_file_path(db_file, None)
330
389
  super(ContainerBroker, self).__init__(
331
390
  base_db_file, timeout, logger, account, container, pending_timeout,
332
391
  stale_reads_ok, skip_commits=skip_commits)
@@ -352,7 +411,10 @@ class ContainerBroker(DatabaseBroker):
352
411
  :param put_timestamp: initial timestamp if broker needs to be
353
412
  initialized
354
413
  :param storage_policy_index: the storage policy index
355
- :return: a :class:`swift.container.backend.ContainerBroker` instance
414
+ :return: a tuple of (``broker``, ``initialized``) where ``broker`` is
415
+ an instance of :class:`swift.container.backend.ContainerBroker` and
416
+ ``initialized`` is True if the db file was initialized, False
417
+ otherwise.
356
418
  """
357
419
  hsh = hash_path(account, container)
358
420
  db_dir = storage_directory(DATADIR, part, hsh)
@@ -360,19 +422,19 @@ class ContainerBroker(DatabaseBroker):
360
422
  os.path.join(device_path, db_dir, hsh + '.db'), epoch)
361
423
  broker = ContainerBroker(db_path, account=account, container=container,
362
424
  logger=logger)
425
+ initialized = False
363
426
  if not os.path.exists(broker.db_file):
364
427
  try:
365
428
  broker.initialize(put_timestamp, storage_policy_index)
429
+ initialized = True
366
430
  except DatabaseAlreadyExists:
367
431
  pass
368
- return broker
432
+ return broker, initialized
369
433
 
370
434
  def get_db_state(self):
371
435
  """
372
436
  Returns the current state of on disk db files.
373
437
  """
374
- if self._db_file == ':memory:':
375
- return UNSHARDED
376
438
  if not self.db_files:
377
439
  return NOTFOUND
378
440
  if len(self.db_files) > 1:
@@ -380,9 +442,9 @@ class ContainerBroker(DatabaseBroker):
380
442
  if self.db_epoch is None:
381
443
  # never been sharded
382
444
  return UNSHARDED
383
- if self.db_epoch != self._own_shard_range().epoch:
445
+ if self.db_epoch != self.get_own_shard_range().epoch:
384
446
  return UNSHARDED
385
- if not self.get_shard_ranges():
447
+ if not self.has_other_shard_ranges():
386
448
  return COLLAPSED
387
449
  return SHARDED
388
450
 
@@ -392,10 +454,8 @@ class ContainerBroker(DatabaseBroker):
392
454
  for sharding to have been initiated, False otherwise.
393
455
  """
394
456
  own_shard_range = self.get_own_shard_range()
395
- if own_shard_range.state in (ShardRange.SHARDING,
396
- ShardRange.SHRINKING,
397
- ShardRange.SHARDED):
398
- return bool(self.get_shard_ranges())
457
+ if own_shard_range.state in ShardRange.CLEAVING_STATES:
458
+ return self.has_other_shard_ranges()
399
459
  return False
400
460
 
401
461
  def sharding_required(self):
@@ -415,8 +475,6 @@ class ContainerBroker(DatabaseBroker):
415
475
  """
416
476
  Reloads the cached list of valid on disk db files for this broker.
417
477
  """
418
- if self._db_file == ':memory:':
419
- return
420
478
  # reset connection so the next access will use the correct DB file
421
479
  self.conn = None
422
480
  self._db_files = get_db_files(self._init_db_file)
@@ -463,7 +521,7 @@ class ContainerBroker(DatabaseBroker):
463
521
  def storage_policy_index(self):
464
522
  if not hasattr(self, '_storage_policy_index'):
465
523
  self._storage_policy_index = \
466
- self.get_info()['storage_policy_index']
524
+ self._get_info()['storage_policy_index']
467
525
  return self._storage_policy_index
468
526
 
469
527
  @property
@@ -553,7 +611,7 @@ class ContainerBroker(DatabaseBroker):
553
611
  put_timestamp, status_changed_at, storage_policy_index)
554
612
  VALUES (?, ?, ?, ?, ?, ?, ?);
555
613
  """, (self.account, self.container, Timestamp.now().internal,
556
- str(uuid4()), put_timestamp, put_timestamp,
614
+ self._new_db_id(), put_timestamp, put_timestamp,
557
615
  storage_policy_index))
558
616
 
559
617
  def create_policy_stat_table(self, conn, storage_policy_index=0):
@@ -593,7 +651,9 @@ class ContainerBroker(DatabaseBroker):
593
651
  deleted INTEGER DEFAULT 0,
594
652
  state INTEGER,
595
653
  state_timestamp TEXT,
596
- epoch TEXT
654
+ epoch TEXT,
655
+ reported INTEGER DEFAULT 0,
656
+ tombstones INTEGER DEFAULT -1
597
657
  );
598
658
  """ % SHARD_RANGE_TABLE)
599
659
 
@@ -624,20 +684,6 @@ class ContainerBroker(DatabaseBroker):
624
684
  SET reported_put_timestamp = 0, reported_delete_timestamp = 0,
625
685
  reported_object_count = 0, reported_bytes_used = 0''')
626
686
 
627
- def _delete_db(self, conn, timestamp):
628
- """
629
- Mark the DB as deleted
630
-
631
- :param conn: DB connection object
632
- :param timestamp: timestamp to mark as deleted
633
- """
634
- conn.execute("""
635
- UPDATE container_stat
636
- SET delete_timestamp = ?,
637
- status = 'DELETED',
638
- status_changed_at = ?
639
- WHERE delete_timestamp < ? """, (timestamp, timestamp, timestamp))
640
-
641
687
  def _commit_puts_load(self, item_list, entry):
642
688
  """See :func:`swift.common.db.DatabaseBroker._commit_puts_load`"""
643
689
  (name, timestamp, size, content_type, etag, deleted) = entry[:6]
@@ -809,16 +855,24 @@ class ContainerBroker(DatabaseBroker):
809
855
  info.update(self._get_alternate_object_stats()[1])
810
856
  return self._is_deleted_info(**info)
811
857
 
812
- def is_reclaimable(self, now, reclaim_age):
858
+ def is_old_enough_to_reclaim(self, now, reclaim_age):
813
859
  with self.get() as conn:
814
860
  info = conn.execute('''
815
861
  SELECT put_timestamp, delete_timestamp
816
862
  FROM container_stat''').fetchone()
817
- if (Timestamp(now - reclaim_age) >
818
- Timestamp(info['delete_timestamp']) >
819
- Timestamp(info['put_timestamp'])):
820
- return self.empty()
821
- return False
863
+ return (Timestamp(now - reclaim_age) >
864
+ Timestamp(info['delete_timestamp']) >
865
+ Timestamp(info['put_timestamp']))
866
+
867
+ def is_empty_enough_to_reclaim(self):
868
+ if self.is_root_container() and (self.has_other_shard_ranges() or
869
+ self.get_db_state() == SHARDING):
870
+ return False
871
+ return self.empty()
872
+
873
+ def is_reclaimable(self, now, reclaim_age):
874
+ return self.is_old_enough_to_reclaim(now, reclaim_age) and \
875
+ self.is_empty_enough_to_reclaim()
822
876
 
823
877
  def get_info_is_deleted(self):
824
878
  """
@@ -827,7 +881,7 @@ class ContainerBroker(DatabaseBroker):
827
881
  :returns: a tuple, in the form (info, is_deleted) info is a dict as
828
882
  returned by get_info and is_deleted is a boolean.
829
883
  """
830
- if self.db_file != ':memory:' and not os.path.exists(self.db_file):
884
+ if not os.path.exists(self.db_file):
831
885
  return {}, True
832
886
  info = self.get_info()
833
887
  return info, self._is_deleted_info(**info)
@@ -846,7 +900,7 @@ class ContainerBroker(DatabaseBroker):
846
900
  try:
847
901
  data = conn.execute(('''
848
902
  SELECT account, container, created_at, put_timestamp,
849
- delete_timestamp, status_changed_at,
903
+ delete_timestamp, status, status_changed_at,
850
904
  object_count, bytes_used,
851
905
  reported_put_timestamp, reported_delete_timestamp,
852
906
  reported_object_count, reported_bytes_used, hash,
@@ -885,23 +939,23 @@ class ContainerBroker(DatabaseBroker):
885
939
  self._do_get_info_query(conn)
886
940
 
887
941
  def _get_alternate_object_stats(self):
888
- state = self.get_db_state()
889
- if state == SHARDING:
942
+ db_state = self.get_db_state()
943
+ if db_state == SHARDING:
890
944
  other_info = self.get_brokers()[0]._get_info()
891
945
  stats = {'object_count': other_info['object_count'],
892
946
  'bytes_used': other_info['bytes_used']}
893
- elif state == SHARDED and self.is_root_container():
947
+ elif db_state == SHARDED and self.is_root_container():
894
948
  stats = self.get_shard_usage()
895
949
  else:
896
950
  stats = {}
897
- return state, stats
951
+ return db_state, stats
898
952
 
899
953
  def get_info(self):
900
954
  """
901
955
  Get global data for the container.
902
956
 
903
957
  :returns: dict with keys: account, container, created_at,
904
- put_timestamp, delete_timestamp, status_changed_at,
958
+ put_timestamp, delete_timestamp, status, status_changed_at,
905
959
  object_count, bytes_used, reported_put_timestamp,
906
960
  reported_delete_timestamp, reported_object_count,
907
961
  reported_bytes_used, hash, id, x_container_sync_point1,
@@ -1040,7 +1094,8 @@ class ContainerBroker(DatabaseBroker):
1040
1094
  def list_objects_iter(self, limit, marker, end_marker, prefix, delimiter,
1041
1095
  path=None, storage_policy_index=0, reverse=False,
1042
1096
  include_deleted=False, since_row=None,
1043
- transform_func=None, all_policies=False):
1097
+ transform_func=None, all_policies=False,
1098
+ allow_reserved=False):
1044
1099
  """
1045
1100
  Get a list of objects sorted by name starting at marker onward, up
1046
1101
  to limit entries. Entries will begin with the prefix and will not
@@ -1066,6 +1121,8 @@ class ContainerBroker(DatabaseBroker):
1066
1121
  :meth:`~_transform_record`; defaults to :meth:`~_transform_record`.
1067
1122
  :param all_policies: if True, include objects for all storage policies
1068
1123
  ignoring any value given for ``storage_policy_index``
1124
+ :param allow_reserved: exclude names with reserved-byte by default
1125
+
1069
1126
  :returns: list of tuples of (name, created_at, size, content_type,
1070
1127
  etag, deleted)
1071
1128
  """
@@ -1079,9 +1136,6 @@ class ContainerBroker(DatabaseBroker):
1079
1136
  if transform_func is None:
1080
1137
  transform_func = self._transform_record
1081
1138
  delim_force_gte = False
1082
- if six.PY2:
1083
- (marker, end_marker, prefix, delimiter, path) = utf8encode(
1084
- marker, end_marker, prefix, delimiter, path)
1085
1139
  self._commit_puts_stale_ok()
1086
1140
  if reverse:
1087
1141
  # Reverse the markers if we are reversing the listing.
@@ -1122,6 +1176,9 @@ class ContainerBroker(DatabaseBroker):
1122
1176
  elif prefix:
1123
1177
  query_conditions.append('name >= ?')
1124
1178
  query_args.append(prefix)
1179
+ if not allow_reserved:
1180
+ query_conditions.append('name >= ?')
1181
+ query_args.append(chr(ord(RESERVED_BYTE) + 1))
1125
1182
  query_conditions.append(deleted_key + deleted_arg)
1126
1183
  if since_row:
1127
1184
  query_conditions.append('ROWID > ?')
@@ -1240,7 +1297,7 @@ class ContainerBroker(DatabaseBroker):
1240
1297
  limit, marker, end_marker, prefix=None, delimiter=None, path=None,
1241
1298
  reverse=False, include_deleted=include_deleted,
1242
1299
  transform_func=self._record_to_dict, since_row=since_row,
1243
- all_policies=True
1300
+ all_policies=True, allow_reserved=True
1244
1301
  )
1245
1302
 
1246
1303
  def _transform_record(self, record):
@@ -1273,9 +1330,7 @@ class ContainerBroker(DatabaseBroker):
1273
1330
  :param source: if defined, update incoming_sync with the source
1274
1331
  """
1275
1332
  for item in item_list:
1276
- if six.PY2 and isinstance(item['name'], six.text_type):
1277
- item['name'] = item['name'].encode('utf-8')
1278
- elif not six.PY2 and isinstance(item['name'], six.binary_type):
1333
+ if isinstance(item['name'], bytes):
1279
1334
  item['name'] = item['name'].decode('utf-8')
1280
1335
 
1281
1336
  def _really_really_merge_items(conn):
@@ -1363,7 +1418,7 @@ class ContainerBroker(DatabaseBroker):
1363
1418
  """
1364
1419
  if not shard_ranges:
1365
1420
  return
1366
- if not isinstance(shard_ranges, list):
1421
+ if not isinstance(shard_ranges, (list, ShardRangeList)):
1367
1422
  shard_ranges = [shard_ranges]
1368
1423
 
1369
1424
  item_list = []
@@ -1371,9 +1426,7 @@ class ContainerBroker(DatabaseBroker):
1371
1426
  if isinstance(item, ShardRange):
1372
1427
  item = dict(item)
1373
1428
  for col in ('name', 'lower', 'upper'):
1374
- if six.PY2 and isinstance(item[col], six.text_type):
1375
- item[col] = item[col].encode('utf-8')
1376
- elif not six.PY2 and isinstance(item[col], six.binary_type):
1429
+ if isinstance(item[col], bytes):
1377
1430
  item[col] = item[col].decode('utf-8')
1378
1431
  item_list.append(item)
1379
1432
 
@@ -1388,28 +1441,14 @@ class ContainerBroker(DatabaseBroker):
1388
1441
  chunk = [record['name'] for record
1389
1442
  in item_list[offset:offset + SQLITE_ARG_LIMIT]]
1390
1443
  records.update(
1391
- (rec[0], rec) for rec in curs.execute(
1444
+ (rec[0], dict(zip(SHARD_RANGE_KEYS, rec)))
1445
+ for rec in curs.execute(
1392
1446
  'SELECT %s FROM %s '
1393
1447
  'WHERE deleted IN (0, 1) AND name IN (%s)' %
1394
1448
  (', '.join(SHARD_RANGE_KEYS), SHARD_RANGE_TABLE,
1395
1449
  ','.join('?' * len(chunk))), chunk))
1396
1450
 
1397
- # Sort item_list into things that need adding and deleting
1398
- to_delete = set()
1399
- to_add = {}
1400
- for item in item_list:
1401
- item_ident = item['name']
1402
- existing = records.get(item_ident)
1403
- if existing:
1404
- existing = dict(zip(SHARD_RANGE_KEYS, existing))
1405
- if merge_shards(item, existing):
1406
- # exists with older timestamp
1407
- if item_ident in records:
1408
- to_delete.add(item_ident)
1409
- # duplicate entries in item_list
1410
- if (item_ident not in to_add or
1411
- merge_shards(item, to_add[item_ident])):
1412
- to_add[item_ident] = item
1451
+ to_add, to_delete = sift_shard_ranges(item_list, records)
1413
1452
 
1414
1453
  if to_delete:
1415
1454
  curs.executemany(
@@ -1422,22 +1461,37 @@ class ContainerBroker(DatabaseBroker):
1422
1461
  'INSERT INTO %s (%s) VALUES (%s)' %
1423
1462
  (SHARD_RANGE_TABLE, ','.join(SHARD_RANGE_KEYS), vals),
1424
1463
  tuple([item[k] for k in SHARD_RANGE_KEYS]
1425
- for item in to_add.values()))
1464
+ for item in to_add))
1426
1465
  conn.commit()
1427
1466
 
1467
+ migrations = {
1468
+ 'no such column: reported':
1469
+ self._migrate_add_shard_range_reported,
1470
+ 'no such column: tombstones':
1471
+ self._migrate_add_shard_range_tombstones,
1472
+ ('no such table: %s' % SHARD_RANGE_TABLE):
1473
+ self.create_shard_range_table,
1474
+ }
1475
+ migrations_done = set()
1428
1476
  with self.get() as conn:
1429
- try:
1430
- return _really_merge_items(conn)
1431
- except sqlite3.OperationalError as err:
1432
- # Without the rollback, new enough (>= py37) python/sqlite3
1433
- # will panic:
1434
- # sqlite3.OperationalError: cannot start a transaction
1435
- # within a transaction
1436
- conn.rollback()
1437
- if ('no such table: %s' % SHARD_RANGE_TABLE) not in str(err):
1438
- raise
1439
- self.create_shard_range_table(conn)
1440
- return _really_merge_items(conn)
1477
+ while True:
1478
+ try:
1479
+ return _really_merge_items(conn)
1480
+ except sqlite3.OperationalError as err:
1481
+ # Without the rollback, new enough (>= py37) python/sqlite3
1482
+ # will panic:
1483
+ # sqlite3.OperationalError: cannot start a transaction
1484
+ # within a transaction
1485
+ conn.rollback()
1486
+ for err_str, migration in migrations.items():
1487
+ if err_str in migrations_done:
1488
+ continue
1489
+ if err_str in str(err):
1490
+ migration(conn)
1491
+ migrations_done.add(err_str)
1492
+ break
1493
+ else:
1494
+ raise
1441
1495
 
1442
1496
  def get_reconciler_sync(self):
1443
1497
  with self.get() as conn:
@@ -1585,6 +1639,28 @@ class ContainerBroker(DatabaseBroker):
1585
1639
  CONTAINER_STAT_VIEW_SCRIPT +
1586
1640
  'COMMIT;')
1587
1641
 
1642
+ def _migrate_add_shard_range_reported(self, conn):
1643
+ """
1644
+ Add the reported column to the 'shard_range' table.
1645
+ """
1646
+ conn.executescript('''
1647
+ BEGIN;
1648
+ ALTER TABLE %s
1649
+ ADD COLUMN reported INTEGER DEFAULT 0;
1650
+ COMMIT;
1651
+ ''' % SHARD_RANGE_TABLE)
1652
+
1653
+ def _migrate_add_shard_range_tombstones(self, conn):
1654
+ """
1655
+ Add the tombstones column to the 'shard_range' table.
1656
+ """
1657
+ conn.executescript('''
1658
+ BEGIN;
1659
+ ALTER TABLE %s
1660
+ ADD COLUMN tombstones INTEGER DEFAULT -1;
1661
+ COMMIT;
1662
+ ''' % SHARD_RANGE_TABLE)
1663
+
1588
1664
  def _reclaim_other_stuff(self, conn, age_timestamp, sync_timestamp):
1589
1665
  super(ContainerBroker, self)._reclaim_other_stuff(
1590
1666
  conn, age_timestamp, sync_timestamp)
@@ -1600,9 +1676,130 @@ class ContainerBroker(DatabaseBroker):
1600
1676
  if ('no such table: %s' % SHARD_RANGE_TABLE) not in str(err):
1601
1677
  raise
1602
1678
 
1603
- def _get_shard_range_rows(self, connection=None, include_deleted=False,
1604
- states=None, include_own=False,
1605
- exclude_others=False):
1679
+ def _make_filler_shard_range(self, namespaces, marker, end_marker):
1680
+ if namespaces and namespaces[-1].upper == Namespace.MAX:
1681
+ return None
1682
+
1683
+ # Insert a modified copy of own shard range to fill any gap between the
1684
+ # end of any found and the upper bound of own shard range. Gaps
1685
+ # enclosed within the found shard ranges are not filled.
1686
+ own_shard_range = self.get_own_shard_range()
1687
+ if namespaces:
1688
+ last_upper = namespaces[-1].upper
1689
+ else:
1690
+ last_upper = max(marker or own_shard_range.lower,
1691
+ own_shard_range.lower)
1692
+ required_upper = min(end_marker or own_shard_range.upper,
1693
+ own_shard_range.upper)
1694
+ if required_upper > last_upper:
1695
+ filler_sr = own_shard_range
1696
+ filler_sr.lower = last_upper
1697
+ filler_sr.upper = required_upper
1698
+ return filler_sr
1699
+ else:
1700
+ return None
1701
+
1702
+ def get_namespaces(self, marker=None, end_marker=None, includes=None,
1703
+ reverse=False, states=None, fill_gaps=False):
1704
+ """
1705
+ Returns a list of persisted namespaces per input parameters.
1706
+
1707
+ :param marker: restricts the returned list to shard ranges whose
1708
+ namespace includes or is greater than the marker value. If
1709
+ ``reverse=True`` then ``marker`` is treated as ``end_marker``.
1710
+ ``marker`` is ignored if ``includes`` is specified.
1711
+ :param end_marker: restricts the returned list to shard ranges whose
1712
+ namespace includes or is less than the end_marker value. If
1713
+ ``reverse=True`` then ``end_marker`` is treated as ``marker``.
1714
+ ``end_marker`` is ignored if ``includes`` is specified.
1715
+ :param includes: restricts the returned list to the shard range that
1716
+ includes the given value; if ``includes`` is specified then
1717
+ ``fill_gaps``, ``marker`` and ``end_marker`` are ignored.
1718
+ :param reverse: reverse the result order.
1719
+ :param states: if specified, restricts the returned list to namespaces
1720
+ that have one of the given states; should be a list of ints.
1721
+ :param fill_gaps: if True, insert a modified copy of own shard range to
1722
+ fill any gap between the end of any found shard ranges and the
1723
+ upper bound of own shard range. Gaps enclosed within the found
1724
+ shard ranges are not filled.
1725
+ :return: a list of Namespace objects.
1726
+ """
1727
+ if includes is None and (marker == Namespace.MAX
1728
+ or end_marker == Namespace.MIN):
1729
+ return []
1730
+
1731
+ if reverse:
1732
+ marker, end_marker = end_marker, marker
1733
+ if marker and end_marker and marker >= end_marker:
1734
+ return []
1735
+
1736
+ included_states = set(states) if states else None
1737
+ with self.get() as conn:
1738
+ # Namespace only needs 'name', 'lower' and 'upper', but the query
1739
+ # also need to include 'state' to be used when subesequently
1740
+ # sorting the rows. And the sorting can't be done within SQLite
1741
+ # since the value for maximum upper bound is an empty string.
1742
+
1743
+ conditions = ['deleted = 0', 'name != ?']
1744
+ params = [self.path]
1745
+ if included_states:
1746
+ conditions.append('state in (%s)' % ','.join(
1747
+ '?' * len(included_states)))
1748
+ params.extend(included_states)
1749
+ if includes is None:
1750
+ if end_marker:
1751
+ conditions.append('lower < ?')
1752
+ params.append(end_marker)
1753
+ if marker:
1754
+ conditions.append("(upper = '' OR upper > ?)")
1755
+ params.append(marker)
1756
+ else:
1757
+ conditions.extend(('lower < ?', "(upper = '' OR upper >= ?)"))
1758
+ params.extend((includes, includes))
1759
+ condition = ' WHERE ' + ' AND '.join(conditions)
1760
+ sql = '''
1761
+ SELECT name, lower, upper, state FROM %s%s
1762
+ ''' % (SHARD_RANGE_TABLE, condition)
1763
+ try:
1764
+ data = conn.execute(sql, params)
1765
+ data.row_factory = None
1766
+ namespaces = [row for row in data]
1767
+ except sqlite3.OperationalError as err:
1768
+ if ('no such table: %s' % SHARD_RANGE_TABLE) in str(err):
1769
+ return []
1770
+ else:
1771
+ raise
1772
+
1773
+ # Sort those namespaces in order, note that each namespace record also
1774
+ # include additional attribute 'state'.
1775
+ def sort_key(namespace):
1776
+ return ShardRange.sort_key_order(name=namespace[0],
1777
+ lower=namespace[1],
1778
+ upper=namespace[2],
1779
+ state=namespace[3])
1780
+ namespaces.sort(key=sort_key)
1781
+ # Convert the record tuples to Namespace objects.
1782
+ namespaces = [Namespace(row[0], row[1], row[2]) for row in namespaces]
1783
+ if includes:
1784
+ return namespaces[:1] if namespaces else []
1785
+
1786
+ if fill_gaps:
1787
+ filler_sr = self._make_filler_shard_range(
1788
+ namespaces, marker, end_marker)
1789
+ if filler_sr:
1790
+ namespaces.append(Namespace(filler_sr.name,
1791
+ filler_sr.lower,
1792
+ filler_sr.upper))
1793
+ if reverse:
1794
+ namespaces.reverse()
1795
+
1796
+ return namespaces
1797
+
1798
+ def _get_shard_range_rows(self, connection=None, marker=None,
1799
+ end_marker=None, includes=None,
1800
+ include_deleted=False, states=None,
1801
+ include_own=False, exclude_others=False,
1802
+ limit=None):
1606
1803
  """
1607
1804
  Returns a list of shard range rows.
1608
1805
 
@@ -1611,30 +1808,49 @@ class ContainerBroker(DatabaseBroker):
1611
1808
  ``exclude_others=True``.
1612
1809
 
1613
1810
  :param connection: db connection
1614
- :param include_deleted: include rows marked as deleted
1615
- :param states: include only rows matching the given state(s); can be an
1616
- int or a list of ints.
1811
+ :param marker: restricts the returned list to rows whose namespace
1812
+ includes or is greater than the marker value. ``marker`` is ignored
1813
+ if ``includes`` is specified.
1814
+ :param end_marker: restricts the returned list to rows whose namespace
1815
+ includes or is less than the end_marker value. ``end_marker`` is
1816
+ ignored if ``includes`` is specified.
1817
+ :param includes: restricts the returned list to the shard range that
1818
+ includes the given value; if ``includes`` is specified then
1819
+ ``marker`` and ``end_marker`` are ignored, but other constraints
1820
+ are applied (e.g. ``exclude_others`` and ``include_deleted``).
1821
+ :param include_deleted: include rows marked as deleted.
1822
+ :param states: include only rows matching the given states; should be
1823
+ a list of ints.
1617
1824
  :param include_own: boolean that governs whether the row whose name
1618
1825
  matches the broker's path is included in the returned list. If
1619
- True, that row is included, otherwise it is not included. Default
1620
- is False.
1826
+ True, that row is included unless it is excluded by other
1827
+ constraints (e.g. ``marker``, ``end_marker``, ``includes``). If
1828
+ False, that row is not included. Default is False.
1621
1829
  :param exclude_others: boolean that governs whether the rows whose
1622
1830
  names do not match the broker's path are included in the returned
1623
1831
  list. If True, those rows are not included, otherwise they are
1624
1832
  included. Default is False.
1833
+ :param limit: restricts the returned list to the given number of rows.
1834
+ Should be a whole number; negative values will be ignored.
1835
+ The ``limit`` parameter is useful to optimise a search
1836
+ when the maximum number of expected matching rows is known, and
1837
+ particularly when that maximum number is much less than the total
1838
+ number of rows in the DB. However, the DB search is not ordered and
1839
+ the subset of rows returned when ``limit`` is less than all
1840
+ possible matching rows is therefore unpredictable.
1625
1841
  :return: a list of tuples.
1626
1842
  """
1627
1843
 
1628
1844
  if exclude_others and not include_own:
1629
1845
  return []
1630
1846
 
1631
- included_states = set()
1632
- if isinstance(states, (list, tuple, set)):
1633
- included_states.update(states)
1634
- elif states is not None:
1635
- included_states.add(states)
1847
+ included_states = set(states) if states else None
1848
+
1849
+ # defaults to be used when legacy db's are missing columns
1850
+ default_values = {'reported': 0,
1851
+ 'tombstones': -1}
1636
1852
 
1637
- def do_query(conn):
1853
+ def do_query(conn, defaults=None):
1638
1854
  condition = ''
1639
1855
  conditions = []
1640
1856
  params = []
@@ -1650,23 +1866,56 @@ class ContainerBroker(DatabaseBroker):
1650
1866
  if exclude_others:
1651
1867
  conditions.append('name = ?')
1652
1868
  params.append(self.path)
1869
+ if includes is None:
1870
+ if end_marker:
1871
+ conditions.append('lower < ?')
1872
+ params.append(end_marker)
1873
+ if marker:
1874
+ conditions.append("(upper = '' OR upper > ?)")
1875
+ params.append(marker)
1876
+ else:
1877
+ conditions.extend(('lower < ?', "(upper = '' OR upper >= ?)"))
1878
+ params.extend((includes, includes))
1653
1879
  if conditions:
1654
1880
  condition = ' WHERE ' + ' AND '.join(conditions)
1881
+ if limit is not None and limit >= 0:
1882
+ condition += ' LIMIT %d' % limit
1883
+ columns = SHARD_RANGE_KEYS[:-2]
1884
+ for column in SHARD_RANGE_KEYS[-2:]:
1885
+ if column in defaults:
1886
+ columns += (('%s as %s' %
1887
+ (default_values[column], column)),)
1888
+ else:
1889
+ columns += (column,)
1655
1890
  sql = '''
1656
1891
  SELECT %s
1657
1892
  FROM %s%s;
1658
- ''' % (', '.join(SHARD_RANGE_KEYS), SHARD_RANGE_TABLE, condition)
1893
+ ''' % (', '.join(columns), SHARD_RANGE_TABLE, condition)
1659
1894
  data = conn.execute(sql, params)
1660
1895
  data.row_factory = None
1661
1896
  return [row for row in data]
1662
1897
 
1663
- try:
1664
- with self.maybe_get(connection) as conn:
1665
- return do_query(conn)
1666
- except sqlite3.OperationalError as err:
1667
- if ('no such table: %s' % SHARD_RANGE_TABLE) not in str(err):
1668
- raise
1669
- return []
1898
+ with self.maybe_get(connection) as conn:
1899
+ defaults = set()
1900
+ attempts = len(default_values) + 1
1901
+ while attempts:
1902
+ attempts -= 1
1903
+ try:
1904
+ return do_query(conn, defaults)
1905
+ except sqlite3.OperationalError as err:
1906
+ if ('no such table: %s' % SHARD_RANGE_TABLE) in str(err):
1907
+ return []
1908
+ if not attempts:
1909
+ raise
1910
+ new_defaults = set()
1911
+ for column in default_values.keys():
1912
+ if 'no such column: %s' % column in str(err):
1913
+ new_defaults.add(column)
1914
+ if not new_defaults:
1915
+ raise
1916
+ if new_defaults.intersection(defaults):
1917
+ raise
1918
+ defaults.update(new_defaults)
1670
1919
 
1671
1920
  @classmethod
1672
1921
  def resolve_shard_range_states(cls, states):
@@ -1677,7 +1926,10 @@ class ContainerBroker(DatabaseBroker):
1677
1926
 
1678
1927
  The following alias values are supported: 'listing' maps to all states
1679
1928
  that are considered valid when listing objects; 'updating' maps to all
1680
- states that are considered valid for redirecting an object update.
1929
+ states that are considered valid for redirecting an object update;
1930
+ 'auditing' maps to all states that are considered valid for a shard
1931
+ container that is updating its own shard range table from a root (this
1932
+ currently maps to all states except FOUND).
1681
1933
 
1682
1934
  :param states: a list of values each of which may be the name of a
1683
1935
  state, the number of a state, or an alias
@@ -1692,6 +1944,8 @@ class ContainerBroker(DatabaseBroker):
1692
1944
  resolved_states.update(SHARD_LISTING_STATES)
1693
1945
  elif state == 'updating':
1694
1946
  resolved_states.update(SHARD_UPDATE_STATES)
1947
+ elif state == 'auditing':
1948
+ resolved_states.update(SHARD_AUDITING_STATES)
1695
1949
  else:
1696
1950
  resolved_states.add(ShardRange.resolve_state(state)[0])
1697
1951
  return resolved_states
@@ -1699,42 +1953,47 @@ class ContainerBroker(DatabaseBroker):
1699
1953
 
1700
1954
  def get_shard_ranges(self, marker=None, end_marker=None, includes=None,
1701
1955
  reverse=False, include_deleted=False, states=None,
1702
- include_own=False,
1703
- exclude_others=False, fill_gaps=False):
1956
+ include_own=False, exclude_others=False,
1957
+ fill_gaps=False):
1704
1958
  """
1705
1959
  Returns a list of persisted shard ranges.
1706
1960
 
1707
1961
  :param marker: restricts the returned list to shard ranges whose
1708
- namespace includes or is greater than the marker value.
1962
+ namespace includes or is greater than the marker value. If
1963
+ ``reverse=True`` then ``marker`` is treated as ``end_marker``.
1964
+ ``marker`` is ignored if ``includes`` is specified.
1709
1965
  :param end_marker: restricts the returned list to shard ranges whose
1710
- namespace includes or is less than the end_marker value.
1966
+ namespace includes or is less than the end_marker value. If
1967
+ ``reverse=True`` then ``end_marker`` is treated as ``marker``.
1968
+ ``end_marker`` is ignored if ``includes`` is specified.
1711
1969
  :param includes: restricts the returned list to the shard range that
1712
1970
  includes the given value; if ``includes`` is specified then
1713
- ``marker`` and ``end_marker`` are ignored.
1971
+ ``fill_gaps``, ``marker`` and ``end_marker`` are ignored, but other
1972
+ constraints are applied (e.g. ``exclude_others`` and
1973
+ ``include_deleted``).
1714
1974
  :param reverse: reverse the result order.
1715
- :param include_deleted: include items that have the delete marker set
1975
+ :param include_deleted: include items that have the delete marker set.
1716
1976
  :param states: if specified, restricts the returned list to shard
1717
- ranges that have the given state(s); can be a list of ints or a
1718
- single int.
1977
+ ranges that have one of the given states; should be a list of ints.
1719
1978
  :param include_own: boolean that governs whether the row whose name
1720
1979
  matches the broker's path is included in the returned list. If
1721
- True, that row is included, otherwise it is not included. Default
1722
- is False.
1980
+ True, that row is included unless it is excluded by other
1981
+ constraints (e.g. ``marker``, ``end_marker``, ``includes``). If
1982
+ False, that row is not included. Default is False.
1723
1983
  :param exclude_others: boolean that governs whether the rows whose
1724
1984
  names do not match the broker's path are included in the returned
1725
1985
  list. If True, those rows are not included, otherwise they are
1726
1986
  included. Default is False.
1727
- :param fill_gaps: if True, insert own shard range to fill any gaps in
1728
- at the tail of other shard ranges.
1729
- :return: a list of instances of :class:`swift.common.utils.ShardRange`
1730
- """
1731
- def shard_range_filter(sr):
1732
- end = start = True
1733
- if end_marker:
1734
- end = end_marker > sr.lower
1735
- if marker:
1736
- start = marker < sr.upper
1737
- return start and end
1987
+ :param fill_gaps: if True, insert a modified copy of own shard range to
1988
+ fill any gap between the end of any found shard ranges and the
1989
+ upper bound of own shard range. Gaps enclosed within the found
1990
+ shard ranges are not filled. ``fill_gaps`` is ignored if
1991
+ ``includes`` is specified.
1992
+ :return: a list of instances of :class:`swift.common.utils.ShardRange`.
1993
+ """
1994
+ if includes is None and (marker == Namespace.MAX
1995
+ or end_marker == Namespace.MIN):
1996
+ return []
1738
1997
 
1739
1998
  if reverse:
1740
1999
  marker, end_marker = end_marker, marker
@@ -1744,28 +2003,17 @@ class ContainerBroker(DatabaseBroker):
1744
2003
  shard_ranges = [
1745
2004
  ShardRange(*row)
1746
2005
  for row in self._get_shard_range_rows(
2006
+ marker=marker, end_marker=end_marker, includes=includes,
1747
2007
  include_deleted=include_deleted, states=states,
1748
- include_own=include_own,
1749
- exclude_others=exclude_others)]
1750
- # note if this ever changes to *not* sort by upper first then it breaks
1751
- # a key assumption for bisect, which is used by utils.find_shard_ranges
1752
- shard_ranges.sort(key=lambda sr: (sr.upper, sr.state, sr.lower))
2008
+ include_own=include_own, exclude_others=exclude_others)]
2009
+ shard_ranges.sort(key=ShardRange.sort_key)
1753
2010
  if includes:
1754
- shard_range = find_shard_range(includes, shard_ranges)
1755
- return [shard_range] if shard_range else []
2011
+ return shard_ranges[:1] if shard_ranges else []
1756
2012
 
1757
- if marker or end_marker:
1758
- shard_ranges = list(filter(shard_range_filter, shard_ranges))
1759
2013
  if fill_gaps:
1760
- if shard_ranges:
1761
- last_upper = shard_ranges[-1].upper
1762
- else:
1763
- last_upper = marker or ShardRange.MIN
1764
- required_upper = end_marker or ShardRange.MAX
1765
- if required_upper > last_upper:
1766
- filler_sr = self.get_own_shard_range()
1767
- filler_sr.lower = last_upper
1768
- filler_sr.upper = required_upper
2014
+ filler_sr = self._make_filler_shard_range(
2015
+ shard_ranges, marker, end_marker)
2016
+ if filler_sr:
1769
2017
  shard_ranges.append(filler_sr)
1770
2018
 
1771
2019
  if reverse:
@@ -1773,40 +2021,33 @@ class ContainerBroker(DatabaseBroker):
1773
2021
 
1774
2022
  return shard_ranges
1775
2023
 
1776
- def _own_shard_range(self, no_default=False):
1777
- shard_ranges = self.get_shard_ranges(include_own=True,
1778
- include_deleted=True,
1779
- exclude_others=True)
1780
- if shard_ranges:
1781
- own_shard_range = shard_ranges[0]
1782
- elif no_default:
1783
- return None
1784
- else:
1785
- own_shard_range = ShardRange(
1786
- self.path, Timestamp.now(), ShardRange.MIN, ShardRange.MAX,
1787
- state=ShardRange.ACTIVE)
1788
- return own_shard_range
1789
-
1790
2024
  def get_own_shard_range(self, no_default=False):
1791
2025
  """
1792
2026
  Returns a shard range representing this broker's own shard range. If no
1793
2027
  such range has been persisted in the broker's shard ranges table then a
1794
2028
  default shard range representing the entire namespace will be returned.
1795
2029
 
1796
- The returned shard range will be updated with the current object stats
1797
- for this broker and a meta timestamp set to the current time. For these
1798
- values to be persisted the caller must merge the shard range.
2030
+ The ``object_count`` and ``bytes_used`` of the returned shard range are
2031
+ not guaranteed to be up-to-date with the current object stats for this
2032
+ broker. Callers that require up-to-date stats should use the
2033
+ ``get_info`` method.
1799
2034
 
1800
2035
  :param no_default: if True and the broker's own shard range is not
1801
2036
  found in the shard ranges table then None is returned, otherwise a
1802
2037
  default shard range is returned.
1803
2038
  :return: an instance of :class:`~swift.common.utils.ShardRange`
1804
2039
  """
1805
- own_shard_range = self._own_shard_range(no_default=no_default)
1806
- if own_shard_range:
1807
- info = self.get_info()
1808
- own_shard_range.update_meta(
1809
- info['object_count'], info['bytes_used'])
2040
+ rows = self._get_shard_range_rows(
2041
+ include_own=True, include_deleted=True, exclude_others=True,
2042
+ limit=1)
2043
+ if rows:
2044
+ own_shard_range = ShardRange(*rows[0])
2045
+ elif no_default:
2046
+ own_shard_range = None
2047
+ else:
2048
+ own_shard_range = ShardRange(
2049
+ self.path, Timestamp.now(), ShardRange.MIN, ShardRange.MAX,
2050
+ state=ShardRange.ACTIVE)
1810
2051
  return own_shard_range
1811
2052
 
1812
2053
  def is_own_shard_range(self, shard_range):
@@ -1820,7 +2061,7 @@ class ContainerBroker(DatabaseBroker):
1820
2061
  :param epoch: a :class:`~swift.utils.common.Timestamp`
1821
2062
  :return: the broker's updated own shard range.
1822
2063
  """
1823
- own_shard_range = self._own_shard_range()
2064
+ own_shard_range = self.get_own_shard_range()
1824
2065
  own_shard_range.update_state(ShardRange.SHARDING, epoch)
1825
2066
  own_shard_range.epoch = epoch
1826
2067
  self.merge_shard_ranges(own_shard_range)
@@ -1833,9 +2074,41 @@ class ContainerBroker(DatabaseBroker):
1833
2074
 
1834
2075
  :return: a dict with keys {bytes_used, object_count}
1835
2076
  """
1836
- shard_ranges = self.get_shard_ranges(states=SHARD_STATS_STATES)
1837
- return {'bytes_used': sum(sr.bytes_used for sr in shard_ranges),
1838
- 'object_count': sum(sr.object_count for sr in shard_ranges)}
2077
+ with self.get() as conn:
2078
+ sql = '''
2079
+ SELECT COALESCE(SUM(bytes_used), 0),
2080
+ COALESCE(SUM(object_count), 0)
2081
+ FROM %s
2082
+ WHERE state in (%s)
2083
+ AND deleted = 0
2084
+ AND name != ?
2085
+ ''' % (SHARD_RANGE_TABLE, ','.join('?' * len(SHARD_STATS_STATES)))
2086
+ cur = conn.execute(sql, SHARD_STATS_STATES + [self.path])
2087
+ bytes_used, object_count = cur.fetchone()
2088
+ return {'bytes_used': bytes_used,
2089
+ 'object_count': object_count}
2090
+
2091
+ def has_other_shard_ranges(self):
2092
+ """
2093
+ This function tells if there is any shard range other than the
2094
+ broker's own shard range, that is not marked as deleted.
2095
+
2096
+ :return: A boolean value as described above.
2097
+ """
2098
+ with self.get() as conn:
2099
+ sql = '''
2100
+ SELECT 1 FROM %s
2101
+ WHERE deleted = 0 AND name != ? LIMIT 1
2102
+ ''' % (SHARD_RANGE_TABLE)
2103
+ try:
2104
+ data = conn.execute(sql, [self.path])
2105
+ data.row_factory = None
2106
+ return True if [row for row in data] else False
2107
+ except sqlite3.OperationalError as err:
2108
+ if ('no such table: %s' % SHARD_RANGE_TABLE) in str(err):
2109
+ return False
2110
+ else:
2111
+ raise
1839
2112
 
1840
2113
  def get_all_shard_range_data(self):
1841
2114
  """
@@ -1862,10 +2135,10 @@ class ContainerBroker(DatabaseBroker):
1862
2135
  self.logger.warning("Container '%s' cannot be set to sharding "
1863
2136
  "state: missing epoch", self.path)
1864
2137
  return False
1865
- state = self.get_db_state()
1866
- if not state == UNSHARDED:
2138
+ db_state = self.get_db_state()
2139
+ if not db_state == UNSHARDED:
1867
2140
  self.logger.warning("Container '%s' cannot be set to sharding "
1868
- "state while in %s state", self.path, state)
2141
+ "state while in %s state", self.path, db_state)
1869
2142
  return False
1870
2143
 
1871
2144
  info = self.get_info()
@@ -1909,17 +2182,25 @@ class ContainerBroker(DatabaseBroker):
1909
2182
  self.path, err)
1910
2183
  return False
1911
2184
 
1912
- # Set the created_at and hash in the container_info table the same
1913
- # in both brokers
2185
+ # sync the retiring container stat into the fresh db. At least the
2186
+ # things that either aren't covered through the normal
2187
+ # broker api, and things that wont just be regenerated.
1914
2188
  try:
1915
- fresh_broker_conn.execute(
1916
- 'UPDATE container_stat SET created_at=?',
1917
- (info['created_at'],))
2189
+ sql = 'UPDATE container_stat SET created_at=?, '
2190
+ sql += 'delete_timestamp=?, status=?, status_changed_at=?'
2191
+ sql_data = (info['created_at'], info['delete_timestamp'],
2192
+ info['status'], info['status_changed_at'])
2193
+ # 'reported_*' items are not sync'd because this is consistent
2194
+ # with when a new DB is created after rsync'ing to another
2195
+ # node (see _newid()). 'hash' should not be sync'd because
2196
+ # this DB has no object rows.
2197
+ fresh_broker_conn.execute(sql, sql_data)
1918
2198
  fresh_broker_conn.commit()
1919
2199
  except sqlite3.OperationalError as err:
1920
- self.logger.error('Failed to set matching created_at time in '
1921
- 'the fresh database for %s: %s',
1922
- self.path, err)
2200
+ self.logger.error(
2201
+ 'Failed to sync the container_stat table/view with the '
2202
+ 'fresh database for %s: %s',
2203
+ self.path, err)
1923
2204
  return False
1924
2205
 
1925
2206
  # Rename to the new database
@@ -1935,11 +2216,11 @@ class ContainerBroker(DatabaseBroker):
1935
2216
  :return: True if the retiring DB was successfully unlinked, False
1936
2217
  otherwise.
1937
2218
  """
1938
- state = self.get_db_state()
1939
- if not state == SHARDING:
2219
+ db_state = self.get_db_state()
2220
+ if not db_state == SHARDING:
1940
2221
  self.logger.warning("Container %r cannot be set to sharded "
1941
2222
  "state while in %s state",
1942
- self.path, state)
2223
+ self.path, db_state)
1943
2224
  return False
1944
2225
 
1945
2226
  self.reload_db_files()
@@ -1993,7 +2274,7 @@ class ContainerBroker(DatabaseBroker):
1993
2274
 
1994
2275
  def set_sharding_sysmeta(self, key, value):
1995
2276
  """
1996
- Updates the broker's metadata metadata stored under the given key
2277
+ Updates the broker's metadata stored under the given key
1997
2278
  prefixed with a sharding specific namespace.
1998
2279
 
1999
2280
  :param key: metadata key in the sharding metadata namespace.
@@ -2033,6 +2314,21 @@ class ContainerBroker(DatabaseBroker):
2033
2314
  else:
2034
2315
  return {k: v[0] for k, v in info.items()}
2035
2316
 
2317
+ def _get_root_meta(self):
2318
+ """
2319
+ Get the (unquoted) root path, plus the header the info came from.
2320
+ If no info available, returns ``(None, None)``
2321
+ """
2322
+ path = self.get_sharding_sysmeta('Quoted-Root')
2323
+ if path:
2324
+ return 'X-Container-Sysmeta-Shard-Quoted-Root', unquote(path)
2325
+
2326
+ path = self.get_sharding_sysmeta('Root')
2327
+ if path:
2328
+ return 'X-Container-Sysmeta-Shard-Root', path
2329
+
2330
+ return None, None
2331
+
2036
2332
  def _load_root_info(self):
2037
2333
  """
2038
2334
  Load the root container name and account for the container represented
@@ -2045,7 +2341,8 @@ class ContainerBroker(DatabaseBroker):
2045
2341
  ``container`` attributes respectively.
2046
2342
 
2047
2343
  """
2048
- path = self.get_sharding_sysmeta('Root')
2344
+ hdr, path = self._get_root_meta()
2345
+
2049
2346
  if not path:
2050
2347
  # Ensure account/container get populated
2051
2348
  self._populate_instance_cache()
@@ -2057,8 +2354,8 @@ class ContainerBroker(DatabaseBroker):
2057
2354
  self._root_account, self._root_container = split_path(
2058
2355
  '/' + path, 2, 2)
2059
2356
  except ValueError:
2060
- raise ValueError("Expected X-Container-Sysmeta-Shard-Root to be "
2061
- "of the form 'account/container', got %r" % path)
2357
+ raise ValueError("Expected %s to be of the form "
2358
+ "'account/container', got %r" % (hdr, path))
2062
2359
 
2063
2360
  @property
2064
2361
  def root_account(self):
@@ -2083,9 +2380,26 @@ class ContainerBroker(DatabaseBroker):
2083
2380
  A root container is a container that is not a shard of another
2084
2381
  container.
2085
2382
  """
2086
- self._populate_instance_cache()
2087
- return (self.root_account == self.account and
2088
- self.root_container == self.container)
2383
+ _, path = self._get_root_meta()
2384
+ if path is not None:
2385
+ # We have metadata telling us where the root is; it's
2386
+ # authoritative; shards should always have this metadata even when
2387
+ # deleted
2388
+ return self.path == path
2389
+
2390
+ # Else, we're either a root or a legacy deleted shard whose sharding
2391
+ # sysmeta was deleted
2392
+ own_shard_range = self.get_own_shard_range(no_default=True)
2393
+ if not own_shard_range:
2394
+ return True # Never been sharded
2395
+
2396
+ if own_shard_range.deleted:
2397
+ # When shard ranges shrink, they get marked deleted
2398
+ return False
2399
+ else:
2400
+ # But even when a root collapses, empties, and gets deleted, its
2401
+ # own_shard_range is left alive
2402
+ return True
2089
2403
 
2090
2404
  def _get_next_shard_range_upper(self, shard_size, last_upper=None):
2091
2405
  """
@@ -2110,7 +2424,8 @@ class ContainerBroker(DatabaseBroker):
2110
2424
  row = connection.execute(sql, args).fetchone()
2111
2425
  return row['name'] if row else None
2112
2426
 
2113
- def find_shard_ranges(self, shard_size, limit=-1, existing_ranges=None):
2427
+ def find_shard_ranges(self, shard_size, limit=-1, existing_ranges=None,
2428
+ minimum_shard_size=1):
2114
2429
  """
2115
2430
  Scans the container db for shard ranges. Scanning will start at the
2116
2431
  upper bound of the any ``existing_ranges`` that are given, otherwise
@@ -2129,6 +2444,10 @@ class ContainerBroker(DatabaseBroker):
2129
2444
  given, this list should be sorted in order of upper bounds; the
2130
2445
  scan for new shard ranges will start at the upper bound of the last
2131
2446
  existing ShardRange.
2447
+ :param minimum_shard_size: Minimum size of the final shard range. If
2448
+ this is greater than one then the final shard range may be extended
2449
+ to more than shard_size in order to avoid a further shard range
2450
+ with less minimum_shard_size rows.
2132
2451
  :return: a tuple; the first value in the tuple is a list of
2133
2452
  dicts each having keys {'index', 'lower', 'upper', 'object_count'}
2134
2453
  in order of ascending 'upper'; the second value in the tuple is a
@@ -2136,8 +2455,9 @@ class ContainerBroker(DatabaseBroker):
2136
2455
  otherwise.
2137
2456
  """
2138
2457
  existing_ranges = existing_ranges or []
2458
+ minimum_shard_size = max(minimum_shard_size, 1)
2139
2459
  object_count = self.get_info().get('object_count', 0)
2140
- if shard_size >= object_count:
2460
+ if shard_size + minimum_shard_size > object_count:
2141
2461
  # container not big enough to shard
2142
2462
  return [], False
2143
2463
 
@@ -2168,9 +2488,10 @@ class ContainerBroker(DatabaseBroker):
2168
2488
  sub_broker = self.get_brokers()[0]
2169
2489
  index = len(existing_ranges)
2170
2490
  while limit is None or limit < 0 or len(found_ranges) < limit:
2171
- if progress + shard_size >= object_count:
2172
- # next shard point is at or beyond final object name so don't
2173
- # bother with db query
2491
+ if progress + shard_size + minimum_shard_size > object_count:
2492
+ # next shard point is within minimum_size rows of the final
2493
+ # object name, or beyond it, so don't bother with db query.
2494
+ # This shard will have <= shard_size + (minimum_size - 1) rows.
2174
2495
  next_shard_upper = None
2175
2496
  else:
2176
2497
  try:
@@ -2194,10 +2515,14 @@ class ContainerBroker(DatabaseBroker):
2194
2515
  # object_count
2195
2516
  shard_size = object_count - progress
2196
2517
 
2197
- # NB shard ranges are created with a non-zero object count so that
2198
- # the apparent container object count remains constant, and the
2199
- # container is non-deletable while shards have been found but not
2200
- # yet cleaved
2518
+ # NB shard ranges are created with a non-zero object count for a
2519
+ # few reasons:
2520
+ # 1. so that the apparent container object count remains
2521
+ # consistent;
2522
+ # 2. the container is non-deletable while shards have been found
2523
+ # but not yet cleaved; and
2524
+ # 3. So we have a rough idea of size of the shards should be
2525
+ # while cleaving.
2201
2526
  found_ranges.append(
2202
2527
  {'index': index,
2203
2528
  'lower': str(last_shard_upper),