swift 2.23.3__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 (206) 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.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +154 -14
  17. swift/cli/manage_shard_ranges.py +705 -37
  18. swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.3.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 +195 -128
  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 +390 -145
  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 -95
  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 +51 -18
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +191 -173
  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.3.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} +2163 -2772
  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 +553 -535
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +490 -231
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +358 -165
  135. swift/container/sharder.py +1540 -684
  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 +207 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +652 -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 +433 -92
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1000 -489
  156. swift/proxy/server.py +185 -112
  157. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
  158. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
  159. swift-2.35.0.dist-info/RECORD +201 -0
  160. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  161. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  162. swift-2.35.0.dist-info/pbr.json +1 -0
  163. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  164. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  165. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  166. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  167. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  168. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  169. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  170. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  171. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  172. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  173. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  174. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  175. swift-2.23.3.data/scripts/swift-account-auditor +0 -23
  176. swift-2.23.3.data/scripts/swift-account-info +0 -51
  177. swift-2.23.3.data/scripts/swift-account-reaper +0 -23
  178. swift-2.23.3.data/scripts/swift-account-replicator +0 -34
  179. swift-2.23.3.data/scripts/swift-account-server +0 -23
  180. swift-2.23.3.data/scripts/swift-container-auditor +0 -23
  181. swift-2.23.3.data/scripts/swift-container-info +0 -55
  182. swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
  183. swift-2.23.3.data/scripts/swift-container-replicator +0 -34
  184. swift-2.23.3.data/scripts/swift-container-sharder +0 -37
  185. swift-2.23.3.data/scripts/swift-container-sync +0 -23
  186. swift-2.23.3.data/scripts/swift-container-updater +0 -23
  187. swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
  188. swift-2.23.3.data/scripts/swift-form-signature +0 -20
  189. swift-2.23.3.data/scripts/swift-init +0 -119
  190. swift-2.23.3.data/scripts/swift-object-auditor +0 -29
  191. swift-2.23.3.data/scripts/swift-object-expirer +0 -33
  192. swift-2.23.3.data/scripts/swift-object-info +0 -60
  193. swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
  194. swift-2.23.3.data/scripts/swift-object-relinker +0 -41
  195. swift-2.23.3.data/scripts/swift-object-replicator +0 -37
  196. swift-2.23.3.data/scripts/swift-object-server +0 -27
  197. swift-2.23.3.data/scripts/swift-object-updater +0 -23
  198. swift-2.23.3.data/scripts/swift-proxy-server +0 -23
  199. swift-2.23.3.data/scripts/swift-recon +0 -24
  200. swift-2.23.3.data/scripts/swift-ring-builder +0 -24
  201. swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
  202. swift-2.23.3.data/scripts/swift-ring-composer +0 -22
  203. swift-2.23.3.dist-info/RECORD +0 -220
  204. swift-2.23.3.dist-info/pbr.json +0 -1
  205. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
  206. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
@@ -20,9 +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
25
- from six.moves.urllib.parse import unquote
23
+ from urllib.parse import unquote
26
24
  import sqlite3
27
25
  from eventlet import tpool
28
26
 
@@ -30,9 +28,10 @@ from swift.common.constraints import CONTAINER_LISTING_LIMIT
30
28
  from swift.common.exceptions import LockTimeout
31
29
  from swift.common.utils import Timestamp, encode_timestamps, \
32
30
  decode_timestamps, extract_swift_bytes, storage_directory, hash_path, \
33
- ShardRange, renamer, find_shard_range, MD5_OF_EMPTY_STRING, mkdirs, \
34
- get_db_files, parse_db_filename, make_db_file_path, split_path
35
- 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, \
36
35
  zero_like, DatabaseAlreadyExists, SQLITE_ARG_LIMIT
37
36
 
38
37
  DATADIR = 'containers'
@@ -53,13 +52,25 @@ SHARD_STATS_STATES = [ShardRange.ACTIVE, ShardRange.SHARDING,
53
52
  SHARD_LISTING_STATES = SHARD_STATS_STATES + [ShardRange.CLEAVED]
54
53
  SHARD_UPDATE_STATES = [ShardRange.CREATED, ShardRange.CLEAVED,
55
54
  ShardRange.ACTIVE, ShardRange.SHARDING]
56
-
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]
57
68
 
58
69
  # attribute names in order used when transforming shard ranges from dicts to
59
70
  # tuples and vice-versa
60
71
  SHARD_RANGE_KEYS = ('name', 'timestamp', 'lower', 'upper', 'object_count',
61
72
  'bytes_used', 'meta_timestamp', 'deleted', 'state',
62
- 'state_timestamp', 'epoch', 'reported')
73
+ 'state_timestamp', 'epoch', 'reported', 'tombstones')
63
74
 
64
75
  POLICY_STAT_TABLE_CREATE = '''
65
76
  CREATE TABLE policy_stat (
@@ -280,6 +291,7 @@ def merge_shards(shard_data, existing):
280
291
  if existing['meta_timestamp'] >= shard_data['meta_timestamp']:
281
292
  for k in ('object_count', 'bytes_used', 'meta_timestamp'):
282
293
  shard_data[k] = existing[k]
294
+ shard_data['tombstones'] = existing.get('tombstones', -1)
283
295
  else:
284
296
  new_content = True
285
297
 
@@ -287,6 +299,7 @@ def merge_shards(shard_data, existing):
287
299
  if existing['reported'] and \
288
300
  existing['object_count'] == shard_data['object_count'] and \
289
301
  existing['bytes_used'] == shard_data['bytes_used'] and \
302
+ existing.get('tombstones', -1) == shard_data['tombstones'] and \
290
303
  existing['state'] == shard_data['state'] and \
291
304
  existing['epoch'] == shard_data['epoch']:
292
305
  shard_data['reported'] = 1
@@ -306,6 +319,38 @@ def merge_shards(shard_data, existing):
306
319
  return new_content
307
320
 
308
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
+
309
354
  class ContainerBroker(DatabaseBroker):
310
355
  """
311
356
  Encapsulates working with a container database.
@@ -313,34 +358,34 @@ class ContainerBroker(DatabaseBroker):
313
358
  Note that this may involve multiple on-disk DB files if the container
314
359
  becomes sharded:
315
360
 
316
- * :attr:`_db_file` is the path to the legacy container DB name, i.e.
317
- ``<hash>.db``. This file should exist for an initialised broker that
318
- has never been sharded, but will not exist once a container has been
319
- sharded.
320
- * :attr:`db_files` is a list of existing db files for the broker. This
321
- list should have at least one entry for an initialised broker, and
322
- should have two entries while a broker is in SHARDING state.
323
- * :attr:`db_file` is the path to whichever db is currently authoritative
324
- for the container. Depending on the container's state, this may not be
325
- the same as the ``db_file`` argument given to :meth:`~__init__`, unless
326
- ``force_db_file`` is True in which case :attr:`db_file` is always equal
327
- to the ``db_file`` argument given to :meth:`~__init__`.
328
- * :attr:`pending_file` is always equal to :attr:`_db_file` extended with
329
- ``.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``.
330
375
  """
331
376
  db_type = 'container'
332
377
  db_contains_type = 'object'
333
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']
334
382
 
335
383
  def __init__(self, db_file, timeout=BROKER_TIMEOUT, logger=None,
336
384
  account=None, container=None, pending_timeout=None,
337
385
  stale_reads_ok=False, skip_commits=False,
338
386
  force_db_file=False):
339
387
  self._init_db_file = db_file
340
- if db_file == ':memory:':
341
- base_db_file = db_file
342
- else:
343
- base_db_file = make_db_file_path(db_file, None)
388
+ base_db_file = make_db_file_path(db_file, None)
344
389
  super(ContainerBroker, self).__init__(
345
390
  base_db_file, timeout, logger, account, container, pending_timeout,
346
391
  stale_reads_ok, skip_commits=skip_commits)
@@ -366,7 +411,10 @@ class ContainerBroker(DatabaseBroker):
366
411
  :param put_timestamp: initial timestamp if broker needs to be
367
412
  initialized
368
413
  :param storage_policy_index: the storage policy index
369
- :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.
370
418
  """
371
419
  hsh = hash_path(account, container)
372
420
  db_dir = storage_directory(DATADIR, part, hsh)
@@ -374,19 +422,19 @@ class ContainerBroker(DatabaseBroker):
374
422
  os.path.join(device_path, db_dir, hsh + '.db'), epoch)
375
423
  broker = ContainerBroker(db_path, account=account, container=container,
376
424
  logger=logger)
425
+ initialized = False
377
426
  if not os.path.exists(broker.db_file):
378
427
  try:
379
428
  broker.initialize(put_timestamp, storage_policy_index)
429
+ initialized = True
380
430
  except DatabaseAlreadyExists:
381
431
  pass
382
- return broker
432
+ return broker, initialized
383
433
 
384
434
  def get_db_state(self):
385
435
  """
386
436
  Returns the current state of on disk db files.
387
437
  """
388
- if self._db_file == ':memory:':
389
- return UNSHARDED
390
438
  if not self.db_files:
391
439
  return NOTFOUND
392
440
  if len(self.db_files) > 1:
@@ -394,9 +442,9 @@ class ContainerBroker(DatabaseBroker):
394
442
  if self.db_epoch is None:
395
443
  # never been sharded
396
444
  return UNSHARDED
397
- if self.db_epoch != self._own_shard_range().epoch:
445
+ if self.db_epoch != self.get_own_shard_range().epoch:
398
446
  return UNSHARDED
399
- if not self.get_shard_ranges():
447
+ if not self.has_other_shard_ranges():
400
448
  return COLLAPSED
401
449
  return SHARDED
402
450
 
@@ -406,11 +454,8 @@ class ContainerBroker(DatabaseBroker):
406
454
  for sharding to have been initiated, False otherwise.
407
455
  """
408
456
  own_shard_range = self.get_own_shard_range()
409
- if own_shard_range.state in (ShardRange.SHARDING,
410
- ShardRange.SHRINKING,
411
- ShardRange.SHARDED,
412
- ShardRange.SHRUNK):
413
- return bool(self.get_shard_ranges())
457
+ if own_shard_range.state in ShardRange.CLEAVING_STATES:
458
+ return self.has_other_shard_ranges()
414
459
  return False
415
460
 
416
461
  def sharding_required(self):
@@ -430,8 +475,6 @@ class ContainerBroker(DatabaseBroker):
430
475
  """
431
476
  Reloads the cached list of valid on disk db files for this broker.
432
477
  """
433
- if self._db_file == ':memory:':
434
- return
435
478
  # reset connection so the next access will use the correct DB file
436
479
  self.conn = None
437
480
  self._db_files = get_db_files(self._init_db_file)
@@ -478,7 +521,7 @@ class ContainerBroker(DatabaseBroker):
478
521
  def storage_policy_index(self):
479
522
  if not hasattr(self, '_storage_policy_index'):
480
523
  self._storage_policy_index = \
481
- self.get_info()['storage_policy_index']
524
+ self._get_info()['storage_policy_index']
482
525
  return self._storage_policy_index
483
526
 
484
527
  @property
@@ -568,7 +611,7 @@ class ContainerBroker(DatabaseBroker):
568
611
  put_timestamp, status_changed_at, storage_policy_index)
569
612
  VALUES (?, ?, ?, ?, ?, ?, ?);
570
613
  """, (self.account, self.container, Timestamp.now().internal,
571
- str(uuid4()), put_timestamp, put_timestamp,
614
+ self._new_db_id(), put_timestamp, put_timestamp,
572
615
  storage_policy_index))
573
616
 
574
617
  def create_policy_stat_table(self, conn, storage_policy_index=0):
@@ -609,7 +652,8 @@ class ContainerBroker(DatabaseBroker):
609
652
  state INTEGER,
610
653
  state_timestamp TEXT,
611
654
  epoch TEXT,
612
- reported INTEGER DEFAULT 0
655
+ reported INTEGER DEFAULT 0,
656
+ tombstones INTEGER DEFAULT -1
613
657
  );
614
658
  """ % SHARD_RANGE_TABLE)
615
659
 
@@ -640,20 +684,6 @@ class ContainerBroker(DatabaseBroker):
640
684
  SET reported_put_timestamp = 0, reported_delete_timestamp = 0,
641
685
  reported_object_count = 0, reported_bytes_used = 0''')
642
686
 
643
- def _delete_db(self, conn, timestamp):
644
- """
645
- Mark the DB as deleted
646
-
647
- :param conn: DB connection object
648
- :param timestamp: timestamp to mark as deleted
649
- """
650
- conn.execute("""
651
- UPDATE container_stat
652
- SET delete_timestamp = ?,
653
- status = 'DELETED',
654
- status_changed_at = ?
655
- WHERE delete_timestamp < ? """, (timestamp, timestamp, timestamp))
656
-
657
687
  def _commit_puts_load(self, item_list, entry):
658
688
  """See :func:`swift.common.db.DatabaseBroker._commit_puts_load`"""
659
689
  (name, timestamp, size, content_type, etag, deleted) = entry[:6]
@@ -825,16 +855,24 @@ class ContainerBroker(DatabaseBroker):
825
855
  info.update(self._get_alternate_object_stats()[1])
826
856
  return self._is_deleted_info(**info)
827
857
 
828
- def is_reclaimable(self, now, reclaim_age):
858
+ def is_old_enough_to_reclaim(self, now, reclaim_age):
829
859
  with self.get() as conn:
830
860
  info = conn.execute('''
831
861
  SELECT put_timestamp, delete_timestamp
832
862
  FROM container_stat''').fetchone()
833
- if (Timestamp(now - reclaim_age) >
834
- Timestamp(info['delete_timestamp']) >
835
- Timestamp(info['put_timestamp'])):
836
- return self.empty()
837
- 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()
838
876
 
839
877
  def get_info_is_deleted(self):
840
878
  """
@@ -843,7 +881,7 @@ class ContainerBroker(DatabaseBroker):
843
881
  :returns: a tuple, in the form (info, is_deleted) info is a dict as
844
882
  returned by get_info and is_deleted is a boolean.
845
883
  """
846
- if self.db_file != ':memory:' and not os.path.exists(self.db_file):
884
+ if not os.path.exists(self.db_file):
847
885
  return {}, True
848
886
  info = self.get_info()
849
887
  return info, self._is_deleted_info(**info)
@@ -862,7 +900,7 @@ class ContainerBroker(DatabaseBroker):
862
900
  try:
863
901
  data = conn.execute(('''
864
902
  SELECT account, container, created_at, put_timestamp,
865
- delete_timestamp, status_changed_at,
903
+ delete_timestamp, status, status_changed_at,
866
904
  object_count, bytes_used,
867
905
  reported_put_timestamp, reported_delete_timestamp,
868
906
  reported_object_count, reported_bytes_used, hash,
@@ -901,23 +939,23 @@ class ContainerBroker(DatabaseBroker):
901
939
  self._do_get_info_query(conn)
902
940
 
903
941
  def _get_alternate_object_stats(self):
904
- state = self.get_db_state()
905
- if state == SHARDING:
942
+ db_state = self.get_db_state()
943
+ if db_state == SHARDING:
906
944
  other_info = self.get_brokers()[0]._get_info()
907
945
  stats = {'object_count': other_info['object_count'],
908
946
  'bytes_used': other_info['bytes_used']}
909
- elif state == SHARDED and self.is_root_container():
947
+ elif db_state == SHARDED and self.is_root_container():
910
948
  stats = self.get_shard_usage()
911
949
  else:
912
950
  stats = {}
913
- return state, stats
951
+ return db_state, stats
914
952
 
915
953
  def get_info(self):
916
954
  """
917
955
  Get global data for the container.
918
956
 
919
957
  :returns: dict with keys: account, container, created_at,
920
- put_timestamp, delete_timestamp, status_changed_at,
958
+ put_timestamp, delete_timestamp, status, status_changed_at,
921
959
  object_count, bytes_used, reported_put_timestamp,
922
960
  reported_delete_timestamp, reported_object_count,
923
961
  reported_bytes_used, hash, id, x_container_sync_point1,
@@ -1056,7 +1094,8 @@ class ContainerBroker(DatabaseBroker):
1056
1094
  def list_objects_iter(self, limit, marker, end_marker, prefix, delimiter,
1057
1095
  path=None, storage_policy_index=0, reverse=False,
1058
1096
  include_deleted=False, since_row=None,
1059
- transform_func=None, all_policies=False):
1097
+ transform_func=None, all_policies=False,
1098
+ allow_reserved=False):
1060
1099
  """
1061
1100
  Get a list of objects sorted by name starting at marker onward, up
1062
1101
  to limit entries. Entries will begin with the prefix and will not
@@ -1082,6 +1121,8 @@ class ContainerBroker(DatabaseBroker):
1082
1121
  :meth:`~_transform_record`; defaults to :meth:`~_transform_record`.
1083
1122
  :param all_policies: if True, include objects for all storage policies
1084
1123
  ignoring any value given for ``storage_policy_index``
1124
+ :param allow_reserved: exclude names with reserved-byte by default
1125
+
1085
1126
  :returns: list of tuples of (name, created_at, size, content_type,
1086
1127
  etag, deleted)
1087
1128
  """
@@ -1095,9 +1136,6 @@ class ContainerBroker(DatabaseBroker):
1095
1136
  if transform_func is None:
1096
1137
  transform_func = self._transform_record
1097
1138
  delim_force_gte = False
1098
- if six.PY2:
1099
- (marker, end_marker, prefix, delimiter, path) = utf8encode(
1100
- marker, end_marker, prefix, delimiter, path)
1101
1139
  self._commit_puts_stale_ok()
1102
1140
  if reverse:
1103
1141
  # Reverse the markers if we are reversing the listing.
@@ -1138,6 +1176,9 @@ class ContainerBroker(DatabaseBroker):
1138
1176
  elif prefix:
1139
1177
  query_conditions.append('name >= ?')
1140
1178
  query_args.append(prefix)
1179
+ if not allow_reserved:
1180
+ query_conditions.append('name >= ?')
1181
+ query_args.append(chr(ord(RESERVED_BYTE) + 1))
1141
1182
  query_conditions.append(deleted_key + deleted_arg)
1142
1183
  if since_row:
1143
1184
  query_conditions.append('ROWID > ?')
@@ -1256,7 +1297,7 @@ class ContainerBroker(DatabaseBroker):
1256
1297
  limit, marker, end_marker, prefix=None, delimiter=None, path=None,
1257
1298
  reverse=False, include_deleted=include_deleted,
1258
1299
  transform_func=self._record_to_dict, since_row=since_row,
1259
- all_policies=True
1300
+ all_policies=True, allow_reserved=True
1260
1301
  )
1261
1302
 
1262
1303
  def _transform_record(self, record):
@@ -1289,9 +1330,7 @@ class ContainerBroker(DatabaseBroker):
1289
1330
  :param source: if defined, update incoming_sync with the source
1290
1331
  """
1291
1332
  for item in item_list:
1292
- if six.PY2 and isinstance(item['name'], six.text_type):
1293
- item['name'] = item['name'].encode('utf-8')
1294
- elif not six.PY2 and isinstance(item['name'], six.binary_type):
1333
+ if isinstance(item['name'], bytes):
1295
1334
  item['name'] = item['name'].decode('utf-8')
1296
1335
 
1297
1336
  def _really_really_merge_items(conn):
@@ -1379,7 +1418,7 @@ class ContainerBroker(DatabaseBroker):
1379
1418
  """
1380
1419
  if not shard_ranges:
1381
1420
  return
1382
- if not isinstance(shard_ranges, list):
1421
+ if not isinstance(shard_ranges, (list, ShardRangeList)):
1383
1422
  shard_ranges = [shard_ranges]
1384
1423
 
1385
1424
  item_list = []
@@ -1387,9 +1426,7 @@ class ContainerBroker(DatabaseBroker):
1387
1426
  if isinstance(item, ShardRange):
1388
1427
  item = dict(item)
1389
1428
  for col in ('name', 'lower', 'upper'):
1390
- if six.PY2 and isinstance(item[col], six.text_type):
1391
- item[col] = item[col].encode('utf-8')
1392
- elif not six.PY2 and isinstance(item[col], six.binary_type):
1429
+ if isinstance(item[col], bytes):
1393
1430
  item[col] = item[col].decode('utf-8')
1394
1431
  item_list.append(item)
1395
1432
 
@@ -1404,28 +1441,14 @@ class ContainerBroker(DatabaseBroker):
1404
1441
  chunk = [record['name'] for record
1405
1442
  in item_list[offset:offset + SQLITE_ARG_LIMIT]]
1406
1443
  records.update(
1407
- (rec[0], rec) for rec in curs.execute(
1444
+ (rec[0], dict(zip(SHARD_RANGE_KEYS, rec)))
1445
+ for rec in curs.execute(
1408
1446
  'SELECT %s FROM %s '
1409
1447
  'WHERE deleted IN (0, 1) AND name IN (%s)' %
1410
1448
  (', '.join(SHARD_RANGE_KEYS), SHARD_RANGE_TABLE,
1411
1449
  ','.join('?' * len(chunk))), chunk))
1412
1450
 
1413
- # Sort item_list into things that need adding and deleting
1414
- to_delete = set()
1415
- to_add = {}
1416
- for item in item_list:
1417
- item_ident = item['name']
1418
- existing = records.get(item_ident)
1419
- if existing:
1420
- existing = dict(zip(SHARD_RANGE_KEYS, existing))
1421
- if merge_shards(item, existing):
1422
- # exists with older timestamp
1423
- if item_ident in records:
1424
- to_delete.add(item_ident)
1425
- # duplicate entries in item_list
1426
- if (item_ident not in to_add or
1427
- merge_shards(item, to_add[item_ident])):
1428
- to_add[item_ident] = item
1451
+ to_add, to_delete = sift_shard_ranges(item_list, records)
1429
1452
 
1430
1453
  if to_delete:
1431
1454
  curs.executemany(
@@ -1438,25 +1461,37 @@ class ContainerBroker(DatabaseBroker):
1438
1461
  'INSERT INTO %s (%s) VALUES (%s)' %
1439
1462
  (SHARD_RANGE_TABLE, ','.join(SHARD_RANGE_KEYS), vals),
1440
1463
  tuple([item[k] for k in SHARD_RANGE_KEYS]
1441
- for item in to_add.values()))
1464
+ for item in to_add))
1442
1465
  conn.commit()
1443
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()
1444
1476
  with self.get() as conn:
1445
- try:
1446
- return _really_merge_items(conn)
1447
- except sqlite3.OperationalError as err:
1448
- # Without the rollback, new enough (>= py37) python/sqlite3
1449
- # will panic:
1450
- # sqlite3.OperationalError: cannot start a transaction
1451
- # within a transaction
1452
- conn.rollback()
1453
- if 'no such column: reported' in str(err):
1454
- self._migrate_add_shard_range_reported(conn)
1455
- return _really_merge_items(conn)
1456
- if ('no such table: %s' % SHARD_RANGE_TABLE) in str(err):
1457
- self.create_shard_range_table(conn)
1477
+ while True:
1478
+ try:
1458
1479
  return _really_merge_items(conn)
1459
- raise
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
1460
1495
 
1461
1496
  def get_reconciler_sync(self):
1462
1497
  with self.get() as conn:
@@ -1615,6 +1650,17 @@ class ContainerBroker(DatabaseBroker):
1615
1650
  COMMIT;
1616
1651
  ''' % SHARD_RANGE_TABLE)
1617
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
+
1618
1664
  def _reclaim_other_stuff(self, conn, age_timestamp, sync_timestamp):
1619
1665
  super(ContainerBroker, self)._reclaim_other_stuff(
1620
1666
  conn, age_timestamp, sync_timestamp)
@@ -1630,9 +1676,130 @@ class ContainerBroker(DatabaseBroker):
1630
1676
  if ('no such table: %s' % SHARD_RANGE_TABLE) not in str(err):
1631
1677
  raise
1632
1678
 
1633
- def _get_shard_range_rows(self, connection=None, include_deleted=False,
1634
- states=None, include_own=False,
1635
- 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):
1636
1803
  """
1637
1804
  Returns a list of shard range rows.
1638
1805
 
@@ -1641,30 +1808,49 @@ class ContainerBroker(DatabaseBroker):
1641
1808
  ``exclude_others=True``.
1642
1809
 
1643
1810
  :param connection: db connection
1644
- :param include_deleted: include rows marked as deleted
1645
- :param states: include only rows matching the given state(s); can be an
1646
- 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.
1647
1824
  :param include_own: boolean that governs whether the row whose name
1648
1825
  matches the broker's path is included in the returned list. If
1649
- True, that row is included, otherwise it is not included. Default
1650
- 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.
1651
1829
  :param exclude_others: boolean that governs whether the rows whose
1652
1830
  names do not match the broker's path are included in the returned
1653
1831
  list. If True, those rows are not included, otherwise they are
1654
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.
1655
1841
  :return: a list of tuples.
1656
1842
  """
1657
1843
 
1658
1844
  if exclude_others and not include_own:
1659
1845
  return []
1660
1846
 
1661
- included_states = set()
1662
- if isinstance(states, (list, tuple, set)):
1663
- included_states.update(states)
1664
- elif states is not None:
1665
- 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}
1666
1852
 
1667
- def do_query(conn, use_reported_column=True):
1853
+ def do_query(conn, defaults=None):
1668
1854
  condition = ''
1669
1855
  conditions = []
1670
1856
  params = []
@@ -1680,12 +1866,27 @@ class ContainerBroker(DatabaseBroker):
1680
1866
  if exclude_others:
1681
1867
  conditions.append('name = ?')
1682
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))
1683
1879
  if conditions:
1684
1880
  condition = ' WHERE ' + ' AND '.join(conditions)
1685
- if use_reported_column:
1686
- columns = SHARD_RANGE_KEYS
1687
- else:
1688
- columns = SHARD_RANGE_KEYS[:-1] + ('0 as reported', )
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,)
1689
1890
  sql = '''
1690
1891
  SELECT %s
1691
1892
  FROM %s%s;
@@ -1695,14 +1896,26 @@ class ContainerBroker(DatabaseBroker):
1695
1896
  return [row for row in data]
1696
1897
 
1697
1898
  with self.maybe_get(connection) as conn:
1698
- try:
1699
- return do_query(conn)
1700
- except sqlite3.OperationalError as err:
1701
- if ('no such table: %s' % SHARD_RANGE_TABLE) in str(err):
1702
- return []
1703
- if 'no such column: reported' in str(err):
1704
- return do_query(conn, use_reported_column=False)
1705
- raise
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)
1706
1919
 
1707
1920
  @classmethod
1708
1921
  def resolve_shard_range_states(cls, states):
@@ -1713,7 +1926,10 @@ class ContainerBroker(DatabaseBroker):
1713
1926
 
1714
1927
  The following alias values are supported: 'listing' maps to all states
1715
1928
  that are considered valid when listing objects; 'updating' maps to all
1716
- 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).
1717
1933
 
1718
1934
  :param states: a list of values each of which may be the name of a
1719
1935
  state, the number of a state, or an alias
@@ -1728,6 +1944,8 @@ class ContainerBroker(DatabaseBroker):
1728
1944
  resolved_states.update(SHARD_LISTING_STATES)
1729
1945
  elif state == 'updating':
1730
1946
  resolved_states.update(SHARD_UPDATE_STATES)
1947
+ elif state == 'auditing':
1948
+ resolved_states.update(SHARD_AUDITING_STATES)
1731
1949
  else:
1732
1950
  resolved_states.add(ShardRange.resolve_state(state)[0])
1733
1951
  return resolved_states
@@ -1735,42 +1953,47 @@ class ContainerBroker(DatabaseBroker):
1735
1953
 
1736
1954
  def get_shard_ranges(self, marker=None, end_marker=None, includes=None,
1737
1955
  reverse=False, include_deleted=False, states=None,
1738
- include_own=False,
1739
- exclude_others=False, fill_gaps=False):
1956
+ include_own=False, exclude_others=False,
1957
+ fill_gaps=False):
1740
1958
  """
1741
1959
  Returns a list of persisted shard ranges.
1742
1960
 
1743
1961
  :param marker: restricts the returned list to shard ranges whose
1744
- 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.
1745
1965
  :param end_marker: restricts the returned list to shard ranges whose
1746
- 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.
1747
1969
  :param includes: restricts the returned list to the shard range that
1748
1970
  includes the given value; if ``includes`` is specified then
1749
- ``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``).
1750
1974
  :param reverse: reverse the result order.
1751
- :param include_deleted: include items that have the delete marker set
1975
+ :param include_deleted: include items that have the delete marker set.
1752
1976
  :param states: if specified, restricts the returned list to shard
1753
- ranges that have the given state(s); can be a list of ints or a
1754
- single int.
1977
+ ranges that have one of the given states; should be a list of ints.
1755
1978
  :param include_own: boolean that governs whether the row whose name
1756
1979
  matches the broker's path is included in the returned list. If
1757
- True, that row is included, otherwise it is not included. Default
1758
- 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.
1759
1983
  :param exclude_others: boolean that governs whether the rows whose
1760
1984
  names do not match the broker's path are included in the returned
1761
1985
  list. If True, those rows are not included, otherwise they are
1762
1986
  included. Default is False.
1763
- :param fill_gaps: if True, insert own shard range to fill any gaps in
1764
- at the tail of other shard ranges.
1765
- :return: a list of instances of :class:`swift.common.utils.ShardRange`
1766
- """
1767
- def shard_range_filter(sr):
1768
- end = start = True
1769
- if end_marker:
1770
- end = end_marker > sr.lower
1771
- if marker:
1772
- start = marker < sr.upper
1773
- 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 []
1774
1997
 
1775
1998
  if reverse:
1776
1999
  marker, end_marker = end_marker, marker
@@ -1780,26 +2003,17 @@ class ContainerBroker(DatabaseBroker):
1780
2003
  shard_ranges = [
1781
2004
  ShardRange(*row)
1782
2005
  for row in self._get_shard_range_rows(
2006
+ marker=marker, end_marker=end_marker, includes=includes,
1783
2007
  include_deleted=include_deleted, states=states,
1784
- include_own=include_own,
1785
- exclude_others=exclude_others)]
2008
+ include_own=include_own, exclude_others=exclude_others)]
1786
2009
  shard_ranges.sort(key=ShardRange.sort_key)
1787
2010
  if includes:
1788
- shard_range = find_shard_range(includes, shard_ranges)
1789
- return [shard_range] if shard_range else []
2011
+ return shard_ranges[:1] if shard_ranges else []
1790
2012
 
1791
- if marker or end_marker:
1792
- shard_ranges = list(filter(shard_range_filter, shard_ranges))
1793
2013
  if fill_gaps:
1794
- if shard_ranges:
1795
- last_upper = shard_ranges[-1].upper
1796
- else:
1797
- last_upper = marker or ShardRange.MIN
1798
- required_upper = end_marker or ShardRange.MAX
1799
- if required_upper > last_upper:
1800
- filler_sr = self.get_own_shard_range()
1801
- filler_sr.lower = last_upper
1802
- filler_sr.upper = required_upper
2014
+ filler_sr = self._make_filler_shard_range(
2015
+ shard_ranges, marker, end_marker)
2016
+ if filler_sr:
1803
2017
  shard_ranges.append(filler_sr)
1804
2018
 
1805
2019
  if reverse:
@@ -1807,40 +2021,33 @@ class ContainerBroker(DatabaseBroker):
1807
2021
 
1808
2022
  return shard_ranges
1809
2023
 
1810
- def _own_shard_range(self, no_default=False):
1811
- shard_ranges = self.get_shard_ranges(include_own=True,
1812
- include_deleted=True,
1813
- exclude_others=True)
1814
- if shard_ranges:
1815
- own_shard_range = shard_ranges[0]
1816
- elif no_default:
1817
- return None
1818
- else:
1819
- own_shard_range = ShardRange(
1820
- self.path, Timestamp.now(), ShardRange.MIN, ShardRange.MAX,
1821
- state=ShardRange.ACTIVE)
1822
- return own_shard_range
1823
-
1824
2024
  def get_own_shard_range(self, no_default=False):
1825
2025
  """
1826
2026
  Returns a shard range representing this broker's own shard range. If no
1827
2027
  such range has been persisted in the broker's shard ranges table then a
1828
2028
  default shard range representing the entire namespace will be returned.
1829
2029
 
1830
- The returned shard range will be updated with the current object stats
1831
- for this broker and a meta timestamp set to the current time. For these
1832
- 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.
1833
2034
 
1834
2035
  :param no_default: if True and the broker's own shard range is not
1835
2036
  found in the shard ranges table then None is returned, otherwise a
1836
2037
  default shard range is returned.
1837
2038
  :return: an instance of :class:`~swift.common.utils.ShardRange`
1838
2039
  """
1839
- own_shard_range = self._own_shard_range(no_default=no_default)
1840
- if own_shard_range:
1841
- info = self.get_info()
1842
- own_shard_range.update_meta(
1843
- 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)
1844
2051
  return own_shard_range
1845
2052
 
1846
2053
  def is_own_shard_range(self, shard_range):
@@ -1854,7 +2061,7 @@ class ContainerBroker(DatabaseBroker):
1854
2061
  :param epoch: a :class:`~swift.utils.common.Timestamp`
1855
2062
  :return: the broker's updated own shard range.
1856
2063
  """
1857
- own_shard_range = self._own_shard_range()
2064
+ own_shard_range = self.get_own_shard_range()
1858
2065
  own_shard_range.update_state(ShardRange.SHARDING, epoch)
1859
2066
  own_shard_range.epoch = epoch
1860
2067
  self.merge_shard_ranges(own_shard_range)
@@ -1867,9 +2074,41 @@ class ContainerBroker(DatabaseBroker):
1867
2074
 
1868
2075
  :return: a dict with keys {bytes_used, object_count}
1869
2076
  """
1870
- shard_ranges = self.get_shard_ranges(states=SHARD_STATS_STATES)
1871
- return {'bytes_used': sum(sr.bytes_used for sr in shard_ranges),
1872
- '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
1873
2112
 
1874
2113
  def get_all_shard_range_data(self):
1875
2114
  """
@@ -1896,10 +2135,10 @@ class ContainerBroker(DatabaseBroker):
1896
2135
  self.logger.warning("Container '%s' cannot be set to sharding "
1897
2136
  "state: missing epoch", self.path)
1898
2137
  return False
1899
- state = self.get_db_state()
1900
- if not state == UNSHARDED:
2138
+ db_state = self.get_db_state()
2139
+ if not db_state == UNSHARDED:
1901
2140
  self.logger.warning("Container '%s' cannot be set to sharding "
1902
- "state while in %s state", self.path, state)
2141
+ "state while in %s state", self.path, db_state)
1903
2142
  return False
1904
2143
 
1905
2144
  info = self.get_info()
@@ -1943,17 +2182,25 @@ class ContainerBroker(DatabaseBroker):
1943
2182
  self.path, err)
1944
2183
  return False
1945
2184
 
1946
- # Set the created_at and hash in the container_info table the same
1947
- # 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.
1948
2188
  try:
1949
- fresh_broker_conn.execute(
1950
- 'UPDATE container_stat SET created_at=?',
1951
- (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)
1952
2198
  fresh_broker_conn.commit()
1953
2199
  except sqlite3.OperationalError as err:
1954
- self.logger.error('Failed to set matching created_at time in '
1955
- 'the fresh database for %s: %s',
1956
- 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)
1957
2204
  return False
1958
2205
 
1959
2206
  # Rename to the new database
@@ -1969,11 +2216,11 @@ class ContainerBroker(DatabaseBroker):
1969
2216
  :return: True if the retiring DB was successfully unlinked, False
1970
2217
  otherwise.
1971
2218
  """
1972
- state = self.get_db_state()
1973
- if not state == SHARDING:
2219
+ db_state = self.get_db_state()
2220
+ if not db_state == SHARDING:
1974
2221
  self.logger.warning("Container %r cannot be set to sharded "
1975
2222
  "state while in %s state",
1976
- self.path, state)
2223
+ self.path, db_state)
1977
2224
  return False
1978
2225
 
1979
2226
  self.reload_db_files()
@@ -2027,7 +2274,7 @@ class ContainerBroker(DatabaseBroker):
2027
2274
 
2028
2275
  def set_sharding_sysmeta(self, key, value):
2029
2276
  """
2030
- Updates the broker's metadata metadata stored under the given key
2277
+ Updates the broker's metadata stored under the given key
2031
2278
  prefixed with a sharding specific namespace.
2032
2279
 
2033
2280
  :param key: metadata key in the sharding metadata namespace.
@@ -2135,13 +2382,14 @@ class ContainerBroker(DatabaseBroker):
2135
2382
  """
2136
2383
  _, path = self._get_root_meta()
2137
2384
  if path is not None:
2138
- # We have metadata telling us where the root is; it's authoritative
2385
+ # We have metadata telling us where the root is; it's
2386
+ # authoritative; shards should always have this metadata even when
2387
+ # deleted
2139
2388
  return self.path == path
2140
2389
 
2141
- # Else, we're either a root or a deleted shard.
2142
-
2143
- # Use internal method so we don't try to update stats.
2144
- own_shard_range = self._own_shard_range(no_default=True)
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)
2145
2393
  if not own_shard_range:
2146
2394
  return True # Never been sharded
2147
2395
 
@@ -2176,7 +2424,8 @@ class ContainerBroker(DatabaseBroker):
2176
2424
  row = connection.execute(sql, args).fetchone()
2177
2425
  return row['name'] if row else None
2178
2426
 
2179
- 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):
2180
2429
  """
2181
2430
  Scans the container db for shard ranges. Scanning will start at the
2182
2431
  upper bound of the any ``existing_ranges`` that are given, otherwise
@@ -2195,6 +2444,10 @@ class ContainerBroker(DatabaseBroker):
2195
2444
  given, this list should be sorted in order of upper bounds; the
2196
2445
  scan for new shard ranges will start at the upper bound of the last
2197
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.
2198
2451
  :return: a tuple; the first value in the tuple is a list of
2199
2452
  dicts each having keys {'index', 'lower', 'upper', 'object_count'}
2200
2453
  in order of ascending 'upper'; the second value in the tuple is a
@@ -2202,8 +2455,9 @@ class ContainerBroker(DatabaseBroker):
2202
2455
  otherwise.
2203
2456
  """
2204
2457
  existing_ranges = existing_ranges or []
2458
+ minimum_shard_size = max(minimum_shard_size, 1)
2205
2459
  object_count = self.get_info().get('object_count', 0)
2206
- if shard_size >= object_count:
2460
+ if shard_size + minimum_shard_size > object_count:
2207
2461
  # container not big enough to shard
2208
2462
  return [], False
2209
2463
 
@@ -2234,9 +2488,10 @@ class ContainerBroker(DatabaseBroker):
2234
2488
  sub_broker = self.get_brokers()[0]
2235
2489
  index = len(existing_ranges)
2236
2490
  while limit is None or limit < 0 or len(found_ranges) < limit:
2237
- if progress + shard_size >= object_count:
2238
- # next shard point is at or beyond final object name so don't
2239
- # 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.
2240
2495
  next_shard_upper = None
2241
2496
  else:
2242
2497
  try:
@@ -2260,10 +2515,14 @@ class ContainerBroker(DatabaseBroker):
2260
2515
  # object_count
2261
2516
  shard_size = object_count - progress
2262
2517
 
2263
- # NB shard ranges are created with a non-zero object count so that
2264
- # the apparent container object count remains constant, and the
2265
- # container is non-deletable while shards have been found but not
2266
- # 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.
2267
2526
  found_ranges.append(
2268
2527
  {'index': index,
2269
2528
  'lower': str(last_shard_upper),