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.
- swift/__init__.py +29 -50
- swift/account/auditor.py +21 -118
- swift/account/backend.py +33 -28
- swift/account/reaper.py +37 -28
- swift/account/replicator.py +22 -0
- swift/account/server.py +60 -26
- swift/account/utils.py +28 -11
- swift-2.23.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
- swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
- swift/cli/container_deleter.py +5 -11
- swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
- swift/cli/dispersion_report.py +10 -9
- swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
- swift/cli/form_signature.py +3 -7
- swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
- swift/cli/info.py +154 -14
- swift/cli/manage_shard_ranges.py +705 -37
- swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
- swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
- swift/cli/recon.py +196 -67
- swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
- swift-2.23.3.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
- swift/cli/relinker.py +807 -126
- swift/cli/reload.py +135 -0
- swift/cli/ringbuilder.py +217 -20
- swift/cli/ringcomposer.py +0 -1
- swift/cli/shard-info.py +4 -3
- swift/common/base_storage_server.py +9 -20
- swift/common/bufferedhttp.py +48 -74
- swift/common/constraints.py +20 -15
- swift/common/container_sync_realms.py +9 -11
- swift/common/daemon.py +25 -8
- swift/common/db.py +195 -128
- swift/common/db_auditor.py +168 -0
- swift/common/db_replicator.py +95 -55
- swift/common/digest.py +141 -0
- swift/common/direct_client.py +144 -33
- swift/common/error_limiter.py +93 -0
- swift/common/exceptions.py +25 -1
- swift/common/header_key_dict.py +2 -9
- swift/common/http_protocol.py +373 -0
- swift/common/internal_client.py +129 -59
- swift/common/linkat.py +3 -4
- swift/common/manager.py +284 -67
- swift/common/memcached.py +390 -145
- swift/common/middleware/__init__.py +4 -0
- swift/common/middleware/account_quotas.py +211 -46
- swift/common/middleware/acl.py +3 -8
- swift/common/middleware/backend_ratelimit.py +230 -0
- swift/common/middleware/bulk.py +22 -34
- swift/common/middleware/catch_errors.py +1 -3
- swift/common/middleware/cname_lookup.py +6 -11
- swift/common/middleware/container_quotas.py +1 -1
- swift/common/middleware/container_sync.py +39 -17
- swift/common/middleware/copy.py +12 -0
- swift/common/middleware/crossdomain.py +22 -9
- swift/common/middleware/crypto/__init__.py +2 -1
- swift/common/middleware/crypto/crypto_utils.py +11 -15
- swift/common/middleware/crypto/decrypter.py +28 -11
- swift/common/middleware/crypto/encrypter.py +12 -17
- swift/common/middleware/crypto/keymaster.py +8 -15
- swift/common/middleware/crypto/kms_keymaster.py +2 -1
- swift/common/middleware/dlo.py +15 -11
- swift/common/middleware/domain_remap.py +5 -4
- swift/common/middleware/etag_quoter.py +128 -0
- swift/common/middleware/formpost.py +73 -70
- swift/common/middleware/gatekeeper.py +8 -1
- swift/common/middleware/keystoneauth.py +33 -3
- swift/common/middleware/list_endpoints.py +4 -4
- swift/common/middleware/listing_formats.py +85 -49
- swift/common/middleware/memcache.py +4 -95
- swift/common/middleware/name_check.py +3 -2
- swift/common/middleware/proxy_logging.py +160 -92
- swift/common/middleware/ratelimit.py +17 -10
- swift/common/middleware/read_only.py +6 -4
- swift/common/middleware/recon.py +59 -22
- swift/common/middleware/s3api/acl_handlers.py +25 -3
- swift/common/middleware/s3api/acl_utils.py +6 -1
- swift/common/middleware/s3api/controllers/__init__.py +6 -0
- swift/common/middleware/s3api/controllers/acl.py +3 -2
- swift/common/middleware/s3api/controllers/bucket.py +242 -137
- swift/common/middleware/s3api/controllers/logging.py +2 -2
- swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
- swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
- swift/common/middleware/s3api/controllers/obj.py +112 -8
- swift/common/middleware/s3api/controllers/object_lock.py +44 -0
- swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
- swift/common/middleware/s3api/controllers/tagging.py +57 -0
- swift/common/middleware/s3api/controllers/versioning.py +36 -7
- swift/common/middleware/s3api/etree.py +22 -9
- swift/common/middleware/s3api/exception.py +0 -4
- swift/common/middleware/s3api/s3api.py +113 -41
- swift/common/middleware/s3api/s3request.py +384 -218
- swift/common/middleware/s3api/s3response.py +126 -23
- swift/common/middleware/s3api/s3token.py +16 -17
- swift/common/middleware/s3api/schema/delete.rng +1 -1
- swift/common/middleware/s3api/subresource.py +7 -10
- swift/common/middleware/s3api/utils.py +27 -10
- swift/common/middleware/slo.py +665 -358
- swift/common/middleware/staticweb.py +64 -37
- swift/common/middleware/symlink.py +51 -18
- swift/common/middleware/tempauth.py +76 -58
- swift/common/middleware/tempurl.py +191 -173
- swift/common/middleware/versioned_writes/__init__.py +51 -0
- swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
- swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
- swift/common/middleware/x_profile/exceptions.py +1 -4
- swift/common/middleware/x_profile/html_viewer.py +18 -19
- swift/common/middleware/x_profile/profile_model.py +1 -2
- swift/common/middleware/xprofile.py +10 -10
- swift-2.23.3.data/scripts/swift-container-server → swift/common/recon.py +13 -8
- swift/common/registry.py +147 -0
- swift/common/request_helpers.py +324 -57
- swift/common/ring/builder.py +67 -25
- swift/common/ring/composite_builder.py +1 -1
- swift/common/ring/ring.py +177 -51
- swift/common/ring/utils.py +1 -1
- swift/common/splice.py +10 -6
- swift/common/statsd_client.py +205 -0
- swift/common/storage_policy.py +49 -44
- swift/common/swob.py +86 -102
- swift/common/{utils.py → utils/__init__.py} +2163 -2772
- swift/common/utils/base.py +131 -0
- swift/common/utils/config.py +433 -0
- swift/common/utils/ipaddrs.py +256 -0
- swift/common/utils/libc.py +345 -0
- swift/common/utils/logs.py +859 -0
- swift/common/utils/timestamp.py +412 -0
- swift/common/wsgi.py +553 -535
- swift/container/auditor.py +14 -100
- swift/container/backend.py +490 -231
- swift/container/reconciler.py +126 -37
- swift/container/replicator.py +96 -22
- swift/container/server.py +358 -165
- swift/container/sharder.py +1540 -684
- swift/container/sync.py +94 -88
- swift/container/updater.py +53 -32
- swift/obj/auditor.py +153 -35
- swift/obj/diskfile.py +466 -217
- swift/obj/expirer.py +406 -124
- swift/obj/mem_diskfile.py +7 -4
- swift/obj/mem_server.py +1 -0
- swift/obj/reconstructor.py +523 -262
- swift/obj/replicator.py +249 -188
- swift/obj/server.py +207 -122
- swift/obj/ssync_receiver.py +145 -85
- swift/obj/ssync_sender.py +113 -54
- swift/obj/updater.py +652 -139
- swift/obj/watchers/__init__.py +0 -0
- swift/obj/watchers/dark_data.py +213 -0
- swift/proxy/controllers/account.py +11 -11
- swift/proxy/controllers/base.py +848 -604
- swift/proxy/controllers/container.py +433 -92
- swift/proxy/controllers/info.py +3 -2
- swift/proxy/controllers/obj.py +1000 -489
- swift/proxy/server.py +185 -112
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
- swift-2.35.0.dist-info/RECORD +201 -0
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
- swift-2.35.0.dist-info/pbr.json +1 -0
- swift/locale/de/LC_MESSAGES/swift.po +0 -1216
- swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
- swift/locale/es/LC_MESSAGES/swift.po +0 -1085
- swift/locale/fr/LC_MESSAGES/swift.po +0 -909
- swift/locale/it/LC_MESSAGES/swift.po +0 -894
- swift/locale/ja/LC_MESSAGES/swift.po +0 -965
- swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
- swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
- swift/locale/ru/LC_MESSAGES/swift.po +0 -891
- swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
- swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
- swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
- swift-2.23.3.data/scripts/swift-account-auditor +0 -23
- swift-2.23.3.data/scripts/swift-account-info +0 -51
- swift-2.23.3.data/scripts/swift-account-reaper +0 -23
- swift-2.23.3.data/scripts/swift-account-replicator +0 -34
- swift-2.23.3.data/scripts/swift-account-server +0 -23
- swift-2.23.3.data/scripts/swift-container-auditor +0 -23
- swift-2.23.3.data/scripts/swift-container-info +0 -55
- swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
- swift-2.23.3.data/scripts/swift-container-replicator +0 -34
- swift-2.23.3.data/scripts/swift-container-sharder +0 -37
- swift-2.23.3.data/scripts/swift-container-sync +0 -23
- swift-2.23.3.data/scripts/swift-container-updater +0 -23
- swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
- swift-2.23.3.data/scripts/swift-form-signature +0 -20
- swift-2.23.3.data/scripts/swift-init +0 -119
- swift-2.23.3.data/scripts/swift-object-auditor +0 -29
- swift-2.23.3.data/scripts/swift-object-expirer +0 -33
- swift-2.23.3.data/scripts/swift-object-info +0 -60
- swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
- swift-2.23.3.data/scripts/swift-object-relinker +0 -41
- swift-2.23.3.data/scripts/swift-object-replicator +0 -37
- swift-2.23.3.data/scripts/swift-object-server +0 -27
- swift-2.23.3.data/scripts/swift-object-updater +0 -23
- swift-2.23.3.data/scripts/swift-proxy-server +0 -23
- swift-2.23.3.data/scripts/swift-recon +0 -24
- swift-2.23.3.data/scripts/swift-ring-builder +0 -24
- swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
- swift-2.23.3.data/scripts/swift-ring-composer +0 -22
- swift-2.23.3.dist-info/RECORD +0 -220
- swift-2.23.3.dist-info/pbr.json +0 -1
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
swift/container/backend.py
CHANGED
@@ -20,9 +20,7 @@ import errno
|
|
20
20
|
import os
|
21
21
|
from uuid import uuid4
|
22
22
|
|
23
|
-
import
|
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,
|
34
|
-
|
35
|
-
|
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
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
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
|
-
|
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
|
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.
|
445
|
+
if self.db_epoch != self.get_own_shard_range().epoch:
|
398
446
|
return UNSHARDED
|
399
|
-
if not self.
|
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
|
410
|
-
|
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.
|
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
|
-
|
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
|
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
|
-
|
834
|
-
|
835
|
-
Timestamp(info['put_timestamp']))
|
836
|
-
|
837
|
-
|
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
|
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
|
-
|
905
|
-
if
|
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
|
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
|
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
|
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
|
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)
|
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
|
-
|
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
|
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
|
-
|
1446
|
-
|
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
|
-
|
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
|
1634
|
-
|
1635
|
-
|
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
|
1645
|
-
|
1646
|
-
|
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
|
1650
|
-
|
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
|
-
|
1663
|
-
|
1664
|
-
|
1665
|
-
|
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,
|
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
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
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
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
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
|
-
|
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
|
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
|
1758
|
-
|
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
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
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
|
-
|
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
|
-
|
1795
|
-
|
1796
|
-
|
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
|
1831
|
-
|
1832
|
-
|
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
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1843
|
-
|
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.
|
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
|
-
|
1871
|
-
|
1872
|
-
|
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
|
-
|
1900
|
-
if not
|
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,
|
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
|
-
#
|
1947
|
-
#
|
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
|
-
|
1950
|
-
|
1951
|
-
|
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(
|
1955
|
-
|
1956
|
-
|
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
|
-
|
1973
|
-
if not
|
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,
|
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
|
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
|
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
|
-
|
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
|
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
|
2238
|
-
# next shard point is
|
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
|
2264
|
-
#
|
2265
|
-
#
|
2266
|
-
#
|
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),
|