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/common/db.py
CHANGED
@@ -17,17 +17,13 @@
|
|
17
17
|
|
18
18
|
from contextlib import contextmanager, closing
|
19
19
|
import base64
|
20
|
-
import hashlib
|
21
20
|
import json
|
22
21
|
import logging
|
23
22
|
import os
|
24
23
|
from uuid import uuid4
|
25
|
-
import sys
|
26
24
|
import time
|
27
25
|
import errno
|
28
|
-
import
|
29
|
-
import six.moves.cPickle as pickle
|
30
|
-
from swift import gettext_ as _
|
26
|
+
import pickle # nosec: B403
|
31
27
|
from tempfile import mkstemp
|
32
28
|
|
33
29
|
from eventlet import sleep, Timeout
|
@@ -36,13 +32,15 @@ import sqlite3
|
|
36
32
|
from swift.common.constraints import MAX_META_COUNT, MAX_META_OVERALL_SIZE, \
|
37
33
|
check_utf8
|
38
34
|
from swift.common.utils import Timestamp, renamer, \
|
39
|
-
mkdirs, lock_parent_directory, fallocate
|
35
|
+
mkdirs, lock_parent_directory, fallocate, md5
|
40
36
|
from swift.common.exceptions import LockTimeout
|
41
37
|
from swift.common.swob import HTTPBadRequest
|
42
38
|
|
43
39
|
|
44
40
|
#: Whether calls will be made to preallocate disk space for database files.
|
45
41
|
DB_PREALLOCATION = False
|
42
|
+
#: Whether calls will be made to log queries (py3 only)
|
43
|
+
QUERY_LOGGING = False
|
46
44
|
#: Timeout for trying to connect to a DB
|
47
45
|
BROKER_TIMEOUT = 25
|
48
46
|
#: Pickle protocol to use
|
@@ -55,28 +53,14 @@ SQLITE_ARG_LIMIT = 999
|
|
55
53
|
RECLAIM_PAGE_SIZE = 10000
|
56
54
|
|
57
55
|
|
58
|
-
def utf8encode(*args):
|
59
|
-
return [(s.encode('utf8') if isinstance(s, six.text_type) else s)
|
60
|
-
for s in args]
|
61
|
-
|
62
|
-
|
63
56
|
def native_str_keys_and_values(metadata):
|
64
|
-
if
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
for x in sv]
|
72
|
-
else:
|
73
|
-
bin_keys = [k for k in metadata if isinstance(k, six.binary_type)]
|
74
|
-
for k in bin_keys:
|
75
|
-
sv = metadata[k]
|
76
|
-
del metadata[k]
|
77
|
-
metadata[k.decode('utf-8')] = [
|
78
|
-
x.decode('utf-8') if isinstance(x, six.binary_type) else x
|
79
|
-
for x in sv]
|
57
|
+
bin_keys = [k for k in metadata if isinstance(k, bytes)]
|
58
|
+
for k in bin_keys:
|
59
|
+
sv = metadata[k]
|
60
|
+
del metadata[k]
|
61
|
+
metadata[k.decode('utf-8')] = [
|
62
|
+
x.decode('utf-8') if isinstance(x, bytes) else x
|
63
|
+
for x in sv]
|
80
64
|
|
81
65
|
|
82
66
|
ZERO_LIKE_VALUES = {None, '', 0, '0'}
|
@@ -129,19 +113,30 @@ class DatabaseAlreadyExists(sqlite3.DatabaseError):
|
|
129
113
|
|
130
114
|
class GreenDBConnection(sqlite3.Connection):
|
131
115
|
"""SQLite DB Connection handler that plays well with eventlet."""
|
116
|
+
# slots are needed for python 3.11.0 (there's an issue fixed in 3.11.1,
|
117
|
+
# see https://github.com/python/cpython/issues/99886)
|
118
|
+
__slots__ = ('timeout', 'db_file')
|
132
119
|
|
133
120
|
def __init__(self, database, timeout=None, *args, **kwargs):
|
134
121
|
if timeout is None:
|
135
122
|
timeout = BROKER_TIMEOUT
|
136
123
|
self.timeout = timeout
|
137
124
|
self.db_file = database
|
138
|
-
super(GreenDBConnection, self).__init__(
|
125
|
+
super(GreenDBConnection, self).__init__(
|
126
|
+
database, timeout=0, *args, **kwargs)
|
139
127
|
|
140
128
|
def cursor(self, cls=None):
|
141
129
|
if cls is None:
|
142
130
|
cls = GreenDBCursor
|
143
131
|
return sqlite3.Connection.cursor(self, cls)
|
144
132
|
|
133
|
+
def execute(self, *args, **kwargs):
|
134
|
+
# py311 stopped calling self.cursor() to get the cursor;
|
135
|
+
# see https://github.com/python/cpython/pull/31351
|
136
|
+
curs = self.cursor()
|
137
|
+
curs.execute(*args, **kwargs)
|
138
|
+
return curs
|
139
|
+
|
145
140
|
def commit(self):
|
146
141
|
return _db_timeout(
|
147
142
|
self.timeout, self.db_file,
|
@@ -150,6 +145,9 @@ class GreenDBConnection(sqlite3.Connection):
|
|
150
145
|
|
151
146
|
class GreenDBCursor(sqlite3.Cursor):
|
152
147
|
"""SQLite Cursor handler that plays well with eventlet."""
|
148
|
+
# slots are needed for python 3.11.0 (there's an issue fixed in 3.11.1,
|
149
|
+
# see https://github.com/python/cpython/issues/99886)
|
150
|
+
__slots__ = ('timeout', 'db_file')
|
153
151
|
|
154
152
|
def __init__(self, *args, **kwargs):
|
155
153
|
self.timeout = args[0].timeout
|
@@ -161,6 +159,9 @@ class GreenDBCursor(sqlite3.Cursor):
|
|
161
159
|
self.timeout, self.db_file, lambda: sqlite3.Cursor.execute(
|
162
160
|
self, *args, **kwargs))
|
163
161
|
|
162
|
+
# NB: executemany and executescript are *not* greened, and never have been
|
163
|
+
# (as far as I can tell)
|
164
|
+
|
164
165
|
|
165
166
|
def dict_factory(crs, row):
|
166
167
|
"""
|
@@ -184,11 +185,12 @@ def chexor(old, name, timestamp):
|
|
184
185
|
"""
|
185
186
|
if name is None:
|
186
187
|
raise Exception('name is None!')
|
187
|
-
new =
|
188
|
+
new = md5(('%s-%s' % (name, timestamp)).encode('utf8'),
|
189
|
+
usedforsecurity=False).hexdigest()
|
188
190
|
return '%032x' % (int(old, 16) ^ int(new, 16))
|
189
191
|
|
190
192
|
|
191
|
-
def get_db_connection(path, timeout=30, okay_to_create=False):
|
193
|
+
def get_db_connection(path, timeout=30, logger=None, okay_to_create=False):
|
192
194
|
"""
|
193
195
|
Returns a properly configured SQLite database connection.
|
194
196
|
|
@@ -201,7 +203,9 @@ def get_db_connection(path, timeout=30, okay_to_create=False):
|
|
201
203
|
connect_time = time.time()
|
202
204
|
conn = sqlite3.connect(path, check_same_thread=False,
|
203
205
|
factory=GreenDBConnection, timeout=timeout)
|
204
|
-
if
|
206
|
+
if QUERY_LOGGING and logger:
|
207
|
+
conn.set_trace_callback(logger.debug)
|
208
|
+
if not okay_to_create:
|
205
209
|
# attempt to detect and fail when connect creates the db file
|
206
210
|
stat = os.stat(path)
|
207
211
|
if stat.st_size == 0 and stat.st_ctime >= connect_time:
|
@@ -223,9 +227,87 @@ def get_db_connection(path, timeout=30, okay_to_create=False):
|
|
223
227
|
return conn
|
224
228
|
|
225
229
|
|
230
|
+
class TombstoneReclaimer(object):
|
231
|
+
"""Encapsulates reclamation of deleted rows in a database."""
|
232
|
+
def __init__(self, broker, age_timestamp):
|
233
|
+
"""
|
234
|
+
Encapsulates reclamation of deleted rows in a database.
|
235
|
+
|
236
|
+
:param broker: an instance of :class:`~swift.common.db.DatabaseBroker`.
|
237
|
+
:param age_timestamp: a float timestamp: tombstones older than this
|
238
|
+
time will be deleted.
|
239
|
+
"""
|
240
|
+
self.broker = broker
|
241
|
+
self.age_timestamp = age_timestamp
|
242
|
+
self.marker = ''
|
243
|
+
self.remaining_tombstones = self.reclaimed = 0
|
244
|
+
self.finished = False
|
245
|
+
# limit 1 offset N gives back the N+1th matching row; that row is used
|
246
|
+
# as an exclusive end_marker for a batch of deletes, so a batch
|
247
|
+
# comprises rows satisfying self.marker <= name < end_marker.
|
248
|
+
self.batch_query = '''
|
249
|
+
SELECT name FROM %s WHERE deleted = 1
|
250
|
+
AND name >= ?
|
251
|
+
ORDER BY NAME LIMIT 1 OFFSET ?
|
252
|
+
''' % self.broker.db_contains_type
|
253
|
+
self.clean_batch_query = '''
|
254
|
+
DELETE FROM %s WHERE deleted = 1
|
255
|
+
AND name >= ? AND %s < %s
|
256
|
+
''' % (self.broker.db_contains_type, self.broker.db_reclaim_timestamp,
|
257
|
+
self.age_timestamp)
|
258
|
+
|
259
|
+
def _reclaim(self, conn):
|
260
|
+
curs = conn.execute(self.batch_query, (self.marker, RECLAIM_PAGE_SIZE))
|
261
|
+
row = curs.fetchone()
|
262
|
+
end_marker = row[0] if row else ''
|
263
|
+
if end_marker:
|
264
|
+
# do a single book-ended DELETE and bounce out
|
265
|
+
curs = conn.execute(self.clean_batch_query + ' AND name < ?',
|
266
|
+
(self.marker, end_marker))
|
267
|
+
self.marker = end_marker
|
268
|
+
self.reclaimed += curs.rowcount
|
269
|
+
self.remaining_tombstones += RECLAIM_PAGE_SIZE - curs.rowcount
|
270
|
+
else:
|
271
|
+
# delete off the end
|
272
|
+
curs = conn.execute(self.clean_batch_query, (self.marker,))
|
273
|
+
self.finished = True
|
274
|
+
self.reclaimed += curs.rowcount
|
275
|
+
|
276
|
+
def reclaim(self):
|
277
|
+
"""
|
278
|
+
Perform reclaim of deleted rows older than ``age_timestamp``.
|
279
|
+
"""
|
280
|
+
while not self.finished:
|
281
|
+
with self.broker.get() as conn:
|
282
|
+
self._reclaim(conn)
|
283
|
+
conn.commit()
|
284
|
+
|
285
|
+
def get_tombstone_count(self):
|
286
|
+
"""
|
287
|
+
Return the number of remaining tombstones newer than ``age_timestamp``.
|
288
|
+
Executes the ``reclaim`` method if it has not already been called on
|
289
|
+
this instance.
|
290
|
+
|
291
|
+
:return: The number of tombstones in the ``broker`` that are newer than
|
292
|
+
``age_timestamp``.
|
293
|
+
"""
|
294
|
+
if not self.finished:
|
295
|
+
self.reclaim()
|
296
|
+
with self.broker.get() as conn:
|
297
|
+
curs = conn.execute('''
|
298
|
+
SELECT COUNT(*) FROM %s WHERE deleted = 1
|
299
|
+
AND name >= ?
|
300
|
+
''' % (self.broker.db_contains_type,), (self.marker,))
|
301
|
+
tombstones = curs.fetchone()[0]
|
302
|
+
self.remaining_tombstones += tombstones
|
303
|
+
return self.remaining_tombstones
|
304
|
+
|
305
|
+
|
226
306
|
class DatabaseBroker(object):
|
227
307
|
"""Encapsulates working with a database."""
|
228
308
|
|
309
|
+
delete_meta_whitelist = []
|
310
|
+
|
229
311
|
def __init__(self, db_file, timeout=BROKER_TIMEOUT, logger=None,
|
230
312
|
account=None, container=None, pending_timeout=None,
|
231
313
|
stale_reads_ok=False, skip_commits=False):
|
@@ -277,15 +359,13 @@ class DatabaseBroker(object):
|
|
277
359
|
:param put_timestamp: internalized timestamp of initial PUT request
|
278
360
|
:param storage_policy_index: only required for containers
|
279
361
|
"""
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
conn = sqlite3.connect(tmp_db_file, check_same_thread=False,
|
288
|
-
factory=GreenDBConnection, timeout=0)
|
362
|
+
mkdirs(self.db_dir)
|
363
|
+
fd, tmp_db_file = mkstemp(suffix='.tmp', dir=self.db_dir)
|
364
|
+
os.close(fd)
|
365
|
+
conn = sqlite3.connect(tmp_db_file, check_same_thread=False,
|
366
|
+
factory=GreenDBConnection, timeout=0)
|
367
|
+
if QUERY_LOGGING:
|
368
|
+
conn.set_trace_callback(self.logger.debug)
|
289
369
|
# creating dbs implicitly does a lot of transactions, so we
|
290
370
|
# pick fast, unsafe options here and do a big fsync at the end.
|
291
371
|
with closing(conn.cursor()) as cur:
|
@@ -346,7 +426,8 @@ class DatabaseBroker(object):
|
|
346
426
|
# of the system were "racing" each other.
|
347
427
|
raise DatabaseAlreadyExists(self.db_file)
|
348
428
|
renamer(tmp_db_file, self.db_file)
|
349
|
-
self.conn = get_db_connection(self.db_file, self.timeout
|
429
|
+
self.conn = get_db_connection(self.db_file, self.timeout,
|
430
|
+
self.logger)
|
350
431
|
else:
|
351
432
|
self.conn = conn
|
352
433
|
|
@@ -359,11 +440,20 @@ class DatabaseBroker(object):
|
|
359
440
|
# first, clear the metadata
|
360
441
|
cleared_meta = {}
|
361
442
|
for k in self.metadata:
|
443
|
+
if k.lower() in self.delete_meta_whitelist:
|
444
|
+
continue
|
362
445
|
cleared_meta[k] = ('', timestamp)
|
363
446
|
self.update_metadata(cleared_meta)
|
364
447
|
# then mark the db as deleted
|
365
448
|
with self.get() as conn:
|
366
|
-
|
449
|
+
conn.execute(
|
450
|
+
"""
|
451
|
+
UPDATE %s_stat
|
452
|
+
SET delete_timestamp = ?,
|
453
|
+
status = 'DELETED',
|
454
|
+
status_changed_at = ?
|
455
|
+
WHERE delete_timestamp < ? """ % self.db_type,
|
456
|
+
(timestamp, timestamp, timestamp))
|
367
457
|
conn.commit()
|
368
458
|
|
369
459
|
@property
|
@@ -392,32 +482,32 @@ class DatabaseBroker(object):
|
|
392
482
|
raise
|
393
483
|
quar_path = "%s-%s" % (quar_path, uuid4().hex)
|
394
484
|
renamer(self.db_dir, quar_path, fsync=False)
|
395
|
-
detail =
|
396
|
-
|
397
|
-
|
398
|
-
|
485
|
+
detail = ('Quarantined %(db_dir)s to %(quar_path)s due to '
|
486
|
+
'%(reason)s') % {'db_dir': self.db_dir,
|
487
|
+
'quar_path': quar_path,
|
488
|
+
'reason': reason}
|
399
489
|
self.logger.error(detail)
|
400
490
|
raise sqlite3.DatabaseError(detail)
|
401
491
|
|
402
|
-
def possibly_quarantine(self,
|
492
|
+
def possibly_quarantine(self, err):
|
403
493
|
"""
|
404
494
|
Checks the exception info to see if it indicates a quarantine situation
|
405
495
|
(malformed or corrupted database). If not, the original exception will
|
406
496
|
be reraised. If so, the database will be quarantined and a new
|
407
497
|
sqlite3.DatabaseError will be raised indicating the action taken.
|
408
498
|
"""
|
409
|
-
if 'database disk image is malformed' in str(
|
499
|
+
if 'database disk image is malformed' in str(err):
|
410
500
|
exc_hint = 'malformed database'
|
411
|
-
elif 'malformed database schema' in str(
|
501
|
+
elif 'malformed database schema' in str(err):
|
412
502
|
exc_hint = 'malformed database'
|
413
|
-
elif ' is not a database' in str(
|
503
|
+
elif ' is not a database' in str(err):
|
414
504
|
# older versions said 'file is not a database'
|
415
505
|
# now 'file is encrypted or is not a database'
|
416
506
|
exc_hint = 'corrupted database'
|
417
|
-
elif 'disk I/O error' in str(
|
507
|
+
elif 'disk I/O error' in str(err):
|
418
508
|
exc_hint = 'disk error while accessing database'
|
419
509
|
else:
|
420
|
-
|
510
|
+
raise err
|
421
511
|
|
422
512
|
self.quarantine(exc_hint)
|
423
513
|
|
@@ -447,11 +537,12 @@ class DatabaseBroker(object):
|
|
447
537
|
def get(self):
|
448
538
|
"""Use with the "with" statement; returns a database connection."""
|
449
539
|
if not self.conn:
|
450
|
-
if
|
540
|
+
if os.path.exists(self.db_file):
|
451
541
|
try:
|
452
|
-
self.conn = get_db_connection(self.db_file, self.timeout
|
453
|
-
|
454
|
-
|
542
|
+
self.conn = get_db_connection(self.db_file, self.timeout,
|
543
|
+
self.logger)
|
544
|
+
except (sqlite3.DatabaseError, DatabaseConnectionError) as e:
|
545
|
+
self.possibly_quarantine(e)
|
455
546
|
else:
|
456
547
|
raise DatabaseConnectionError(self.db_file, "DB doesn't exist")
|
457
548
|
conn = self.conn
|
@@ -460,12 +551,12 @@ class DatabaseBroker(object):
|
|
460
551
|
yield conn
|
461
552
|
conn.rollback()
|
462
553
|
self.conn = conn
|
463
|
-
except sqlite3.DatabaseError:
|
554
|
+
except sqlite3.DatabaseError as e:
|
464
555
|
try:
|
465
556
|
conn.close()
|
466
557
|
except Exception:
|
467
558
|
pass
|
468
|
-
self.possibly_quarantine(
|
559
|
+
self.possibly_quarantine(e)
|
469
560
|
except (Exception, Timeout):
|
470
561
|
conn.close()
|
471
562
|
raise
|
@@ -474,8 +565,9 @@ class DatabaseBroker(object):
|
|
474
565
|
def lock(self):
|
475
566
|
"""Use with the "with" statement; locks a database."""
|
476
567
|
if not self.conn:
|
477
|
-
if
|
478
|
-
self.conn = get_db_connection(self.db_file, self.timeout
|
568
|
+
if os.path.exists(self.db_file):
|
569
|
+
self.conn = get_db_connection(self.db_file, self.timeout,
|
570
|
+
self.logger)
|
479
571
|
else:
|
480
572
|
raise DatabaseConnectionError(self.db_file, "DB doesn't exist")
|
481
573
|
conn = self.conn
|
@@ -485,16 +577,19 @@ class DatabaseBroker(object):
|
|
485
577
|
conn.execute('BEGIN IMMEDIATE')
|
486
578
|
try:
|
487
579
|
yield True
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
580
|
+
finally:
|
581
|
+
try:
|
582
|
+
conn.execute('ROLLBACK')
|
583
|
+
conn.isolation_level = orig_isolation_level
|
584
|
+
self.conn = conn
|
585
|
+
except (Exception, Timeout):
|
586
|
+
logging.exception(
|
587
|
+
'Broker error trying to rollback locked connection')
|
588
|
+
conn.close()
|
589
|
+
|
590
|
+
def _new_db_id(self):
|
591
|
+
device_name = os.path.basename(self.get_device_path())
|
592
|
+
return "%s-%s" % (str(uuid4()), device_name)
|
498
593
|
|
499
594
|
def newid(self, remote_id):
|
500
595
|
"""
|
@@ -505,7 +600,7 @@ class DatabaseBroker(object):
|
|
505
600
|
with self.get() as conn:
|
506
601
|
row = conn.execute('''
|
507
602
|
UPDATE %s_stat SET id=?
|
508
|
-
''' % self.db_type, (
|
603
|
+
''' % self.db_type, (self._new_db_id(),))
|
509
604
|
row = conn.execute('''
|
510
605
|
SELECT ROWID FROM %s ORDER BY ROWID DESC LIMIT 1
|
511
606
|
''' % self.db_contains_type).fetchone()
|
@@ -537,7 +632,7 @@ class DatabaseBroker(object):
|
|
537
632
|
|
538
633
|
:returns: True if the DB is considered to be deleted, False otherwise
|
539
634
|
"""
|
540
|
-
if
|
635
|
+
if not os.path.exists(self.db_file):
|
541
636
|
return True
|
542
637
|
self._commit_puts_stale_ok()
|
543
638
|
with self.get() as conn:
|
@@ -614,22 +709,26 @@ class DatabaseBroker(object):
|
|
614
709
|
return -1
|
615
710
|
return row['sync_point']
|
616
711
|
|
617
|
-
def get_syncs(self, incoming=True):
|
712
|
+
def get_syncs(self, incoming=True, include_timestamp=False):
|
618
713
|
"""
|
619
714
|
Get a serialized copy of the sync table.
|
620
715
|
|
621
716
|
:param incoming: if True, get the last incoming sync, otherwise get
|
622
717
|
the last outgoing sync
|
623
|
-
:
|
718
|
+
:param include_timestamp: If True include the updated_at timestamp
|
719
|
+
:returns: list of {'remote_id', 'sync_point'} or
|
720
|
+
{'remote_id', 'sync_point', 'updated_at'}
|
721
|
+
if include_timestamp is True.
|
624
722
|
"""
|
625
723
|
with self.get() as conn:
|
724
|
+
columns = 'remote_id, sync_point'
|
725
|
+
if include_timestamp:
|
726
|
+
columns += ', updated_at'
|
626
727
|
curs = conn.execute('''
|
627
|
-
SELECT
|
628
|
-
''' % ('incoming' if incoming else 'outgoing'))
|
629
|
-
|
630
|
-
for
|
631
|
-
result.append({'remote_id': row[0], 'sync_point': row[1]})
|
632
|
-
return result
|
728
|
+
SELECT %s FROM %s_sync
|
729
|
+
''' % (columns, 'incoming' if incoming else 'outgoing'))
|
730
|
+
curs.row_factory = dict_factory
|
731
|
+
return [r for r in curs]
|
633
732
|
|
634
733
|
def get_max_row(self, table=None):
|
635
734
|
if not table:
|
@@ -670,8 +769,8 @@ class DatabaseBroker(object):
|
|
670
769
|
"""
|
671
770
|
Put a record into the DB. If the DB has an associated pending file with
|
672
771
|
space then the record is appended to that file and a commit to the DB
|
673
|
-
is deferred. If
|
674
|
-
|
772
|
+
is deferred. If its pending file is full then the record will be
|
773
|
+
committed immediately.
|
675
774
|
|
676
775
|
:param record: a record to be added to the DB.
|
677
776
|
:raises DatabaseConnectionError: if the DB file does not exist or if
|
@@ -679,9 +778,6 @@ class DatabaseBroker(object):
|
|
679
778
|
:raises LockTimeout: if a timeout occurs while waiting to take a lock
|
680
779
|
to write to the pending file.
|
681
780
|
"""
|
682
|
-
if self._db_file == ':memory:':
|
683
|
-
self.merge_items([record])
|
684
|
-
return
|
685
781
|
if not os.path.exists(self.db_file):
|
686
782
|
raise DatabaseConnectionError(self.db_file, "DB doesn't exist")
|
687
783
|
if self.skip_commits:
|
@@ -707,8 +803,7 @@ class DatabaseBroker(object):
|
|
707
803
|
fp.flush()
|
708
804
|
|
709
805
|
def _skip_commit_puts(self):
|
710
|
-
return
|
711
|
-
os.path.exists(self.pending_file))
|
806
|
+
return self.skip_commits or not os.path.exists(self.pending_file)
|
712
807
|
|
713
808
|
def _commit_puts(self, item_list=None):
|
714
809
|
"""
|
@@ -737,15 +832,12 @@ class DatabaseBroker(object):
|
|
737
832
|
for entry in fp.read().split(b':'):
|
738
833
|
if entry:
|
739
834
|
try:
|
740
|
-
|
741
|
-
|
742
|
-
else:
|
743
|
-
data = pickle.loads(base64.b64decode(entry),
|
744
|
-
encoding='utf8')
|
835
|
+
data = pickle.loads(base64.b64decode(entry),
|
836
|
+
encoding='utf8') # nosec: B301
|
745
837
|
self._commit_puts_load(item_list, data)
|
746
838
|
except Exception:
|
747
839
|
self.logger.exception(
|
748
|
-
|
840
|
+
'Invalid pending entry %(file)s: %(entry)s',
|
749
841
|
{'file': self.pending_file, 'entry': entry})
|
750
842
|
if item_list:
|
751
843
|
self.merge_items(item_list)
|
@@ -822,7 +914,7 @@ class DatabaseBroker(object):
|
|
822
914
|
within 512k of a boundary, it allocates to the next boundary.
|
823
915
|
Boundaries are 2m, 5m, 10m, 25m, 50m, then every 50m after.
|
824
916
|
"""
|
825
|
-
if not DB_PREALLOCATION
|
917
|
+
if not DB_PREALLOCATION:
|
826
918
|
return
|
827
919
|
MB = (1024 * 1024)
|
828
920
|
|
@@ -968,47 +1060,22 @@ class DatabaseBroker(object):
|
|
968
1060
|
with lock_parent_directory(self.pending_file,
|
969
1061
|
self.pending_timeout):
|
970
1062
|
self._commit_puts()
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
self._reclaim_other_stuff(
|
979
|
-
conn, age_timestamp, sync_timestamp)
|
980
|
-
conn.commit()
|
1063
|
+
|
1064
|
+
tombstone_reclaimer = TombstoneReclaimer(self, age_timestamp)
|
1065
|
+
tombstone_reclaimer.reclaim()
|
1066
|
+
with self.get() as conn:
|
1067
|
+
self._reclaim_other_stuff(conn, age_timestamp, sync_timestamp)
|
1068
|
+
conn.commit()
|
1069
|
+
return tombstone_reclaimer
|
981
1070
|
|
982
1071
|
def _reclaim_other_stuff(self, conn, age_timestamp, sync_timestamp):
|
983
1072
|
"""
|
984
|
-
This is only called once at the end of reclaim after
|
985
|
-
|
1073
|
+
This is only called once at the end of reclaim after tombstone reclaim
|
1074
|
+
has been completed.
|
986
1075
|
"""
|
987
1076
|
self._reclaim_sync(conn, sync_timestamp)
|
988
1077
|
self._reclaim_metadata(conn, age_timestamp)
|
989
1078
|
|
990
|
-
def _reclaim(self, conn, age_timestamp, marker):
|
991
|
-
clean_batch_qry = '''
|
992
|
-
DELETE FROM %s WHERE deleted = 1
|
993
|
-
AND name > ? AND %s < ?
|
994
|
-
''' % (self.db_contains_type, self.db_reclaim_timestamp)
|
995
|
-
curs = conn.execute('''
|
996
|
-
SELECT name FROM %s WHERE deleted = 1
|
997
|
-
AND name > ?
|
998
|
-
ORDER BY NAME LIMIT 1 OFFSET ?
|
999
|
-
''' % (self.db_contains_type,), (marker, RECLAIM_PAGE_SIZE))
|
1000
|
-
row = curs.fetchone()
|
1001
|
-
if row:
|
1002
|
-
# do a single book-ended DELETE and bounce out
|
1003
|
-
end_marker = row[0]
|
1004
|
-
conn.execute(clean_batch_qry + ' AND name <= ?', (
|
1005
|
-
marker, age_timestamp, end_marker))
|
1006
|
-
else:
|
1007
|
-
# delete off the end and reset marker to indicate we're done
|
1008
|
-
end_marker = ''
|
1009
|
-
conn.execute(clean_batch_qry, (marker, age_timestamp))
|
1010
|
-
return end_marker
|
1011
|
-
|
1012
1079
|
def _reclaim_sync(self, conn, sync_timestamp):
|
1013
1080
|
try:
|
1014
1081
|
conn.execute('''
|