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