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/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,24 +53,14 @@ SQLITE_ARG_LIMIT = 999
|
|
55
53
|
RECLAIM_PAGE_SIZE = 10000
|
56
54
|
|
57
55
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
for k in uni_keys:
|
67
|
-
sv = metadata[k]
|
68
|
-
del metadata[k]
|
69
|
-
metadata[k.encode('utf-8')] = sv
|
70
|
-
else:
|
71
|
-
bin_keys = [k for k in metadata if isinstance(k, six.binary_type)]
|
72
|
-
for k in bin_keys:
|
73
|
-
sv = metadata[k]
|
74
|
-
del metadata[k]
|
75
|
-
metadata[k.decode('utf-8')] = sv
|
56
|
+
def native_str_keys_and_values(metadata):
|
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]
|
76
64
|
|
77
65
|
|
78
66
|
ZERO_LIKE_VALUES = {None, '', 0, '0'}
|
@@ -125,19 +113,30 @@ class DatabaseAlreadyExists(sqlite3.DatabaseError):
|
|
125
113
|
|
126
114
|
class GreenDBConnection(sqlite3.Connection):
|
127
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')
|
128
119
|
|
129
120
|
def __init__(self, database, timeout=None, *args, **kwargs):
|
130
121
|
if timeout is None:
|
131
122
|
timeout = BROKER_TIMEOUT
|
132
123
|
self.timeout = timeout
|
133
124
|
self.db_file = database
|
134
|
-
super(GreenDBConnection, self).__init__(
|
125
|
+
super(GreenDBConnection, self).__init__(
|
126
|
+
database, timeout=0, *args, **kwargs)
|
135
127
|
|
136
128
|
def cursor(self, cls=None):
|
137
129
|
if cls is None:
|
138
130
|
cls = GreenDBCursor
|
139
131
|
return sqlite3.Connection.cursor(self, cls)
|
140
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
|
+
|
141
140
|
def commit(self):
|
142
141
|
return _db_timeout(
|
143
142
|
self.timeout, self.db_file,
|
@@ -146,6 +145,9 @@ class GreenDBConnection(sqlite3.Connection):
|
|
146
145
|
|
147
146
|
class GreenDBCursor(sqlite3.Cursor):
|
148
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')
|
149
151
|
|
150
152
|
def __init__(self, *args, **kwargs):
|
151
153
|
self.timeout = args[0].timeout
|
@@ -157,6 +159,9 @@ class GreenDBCursor(sqlite3.Cursor):
|
|
157
159
|
self.timeout, self.db_file, lambda: sqlite3.Cursor.execute(
|
158
160
|
self, *args, **kwargs))
|
159
161
|
|
162
|
+
# NB: executemany and executescript are *not* greened, and never have been
|
163
|
+
# (as far as I can tell)
|
164
|
+
|
160
165
|
|
161
166
|
def dict_factory(crs, row):
|
162
167
|
"""
|
@@ -180,11 +185,12 @@ def chexor(old, name, timestamp):
|
|
180
185
|
"""
|
181
186
|
if name is None:
|
182
187
|
raise Exception('name is None!')
|
183
|
-
new =
|
188
|
+
new = md5(('%s-%s' % (name, timestamp)).encode('utf8'),
|
189
|
+
usedforsecurity=False).hexdigest()
|
184
190
|
return '%032x' % (int(old, 16) ^ int(new, 16))
|
185
191
|
|
186
192
|
|
187
|
-
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):
|
188
194
|
"""
|
189
195
|
Returns a properly configured SQLite database connection.
|
190
196
|
|
@@ -197,7 +203,9 @@ def get_db_connection(path, timeout=30, okay_to_create=False):
|
|
197
203
|
connect_time = time.time()
|
198
204
|
conn = sqlite3.connect(path, check_same_thread=False,
|
199
205
|
factory=GreenDBConnection, timeout=timeout)
|
200
|
-
if
|
206
|
+
if QUERY_LOGGING and logger:
|
207
|
+
conn.set_trace_callback(logger.debug)
|
208
|
+
if not okay_to_create:
|
201
209
|
# attempt to detect and fail when connect creates the db file
|
202
210
|
stat = os.stat(path)
|
203
211
|
if stat.st_size == 0 and stat.st_ctime >= connect_time:
|
@@ -219,9 +227,87 @@ def get_db_connection(path, timeout=30, okay_to_create=False):
|
|
219
227
|
return conn
|
220
228
|
|
221
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
|
+
|
222
306
|
class DatabaseBroker(object):
|
223
307
|
"""Encapsulates working with a database."""
|
224
308
|
|
309
|
+
delete_meta_whitelist = []
|
310
|
+
|
225
311
|
def __init__(self, db_file, timeout=BROKER_TIMEOUT, logger=None,
|
226
312
|
account=None, container=None, pending_timeout=None,
|
227
313
|
stale_reads_ok=False, skip_commits=False):
|
@@ -273,15 +359,13 @@ class DatabaseBroker(object):
|
|
273
359
|
:param put_timestamp: internalized timestamp of initial PUT request
|
274
360
|
:param storage_policy_index: only required for containers
|
275
361
|
"""
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
conn = sqlite3.connect(tmp_db_file, check_same_thread=False,
|
284
|
-
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)
|
285
369
|
# creating dbs implicitly does a lot of transactions, so we
|
286
370
|
# pick fast, unsafe options here and do a big fsync at the end.
|
287
371
|
with closing(conn.cursor()) as cur:
|
@@ -342,7 +426,8 @@ class DatabaseBroker(object):
|
|
342
426
|
# of the system were "racing" each other.
|
343
427
|
raise DatabaseAlreadyExists(self.db_file)
|
344
428
|
renamer(tmp_db_file, self.db_file)
|
345
|
-
self.conn = get_db_connection(self.db_file, self.timeout
|
429
|
+
self.conn = get_db_connection(self.db_file, self.timeout,
|
430
|
+
self.logger)
|
346
431
|
else:
|
347
432
|
self.conn = conn
|
348
433
|
|
@@ -355,11 +440,20 @@ class DatabaseBroker(object):
|
|
355
440
|
# first, clear the metadata
|
356
441
|
cleared_meta = {}
|
357
442
|
for k in self.metadata:
|
443
|
+
if k.lower() in self.delete_meta_whitelist:
|
444
|
+
continue
|
358
445
|
cleared_meta[k] = ('', timestamp)
|
359
446
|
self.update_metadata(cleared_meta)
|
360
447
|
# then mark the db as deleted
|
361
448
|
with self.get() as conn:
|
362
|
-
|
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))
|
363
457
|
conn.commit()
|
364
458
|
|
365
459
|
@property
|
@@ -388,32 +482,32 @@ class DatabaseBroker(object):
|
|
388
482
|
raise
|
389
483
|
quar_path = "%s-%s" % (quar_path, uuid4().hex)
|
390
484
|
renamer(self.db_dir, quar_path, fsync=False)
|
391
|
-
detail =
|
392
|
-
|
393
|
-
|
394
|
-
|
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}
|
395
489
|
self.logger.error(detail)
|
396
490
|
raise sqlite3.DatabaseError(detail)
|
397
491
|
|
398
|
-
def possibly_quarantine(self,
|
492
|
+
def possibly_quarantine(self, err):
|
399
493
|
"""
|
400
494
|
Checks the exception info to see if it indicates a quarantine situation
|
401
495
|
(malformed or corrupted database). If not, the original exception will
|
402
496
|
be reraised. If so, the database will be quarantined and a new
|
403
497
|
sqlite3.DatabaseError will be raised indicating the action taken.
|
404
498
|
"""
|
405
|
-
if 'database disk image is malformed' in str(
|
499
|
+
if 'database disk image is malformed' in str(err):
|
406
500
|
exc_hint = 'malformed database'
|
407
|
-
elif 'malformed database schema' in str(
|
501
|
+
elif 'malformed database schema' in str(err):
|
408
502
|
exc_hint = 'malformed database'
|
409
|
-
elif ' is not a database' in str(
|
503
|
+
elif ' is not a database' in str(err):
|
410
504
|
# older versions said 'file is not a database'
|
411
505
|
# now 'file is encrypted or is not a database'
|
412
506
|
exc_hint = 'corrupted database'
|
413
|
-
elif 'disk I/O error' in str(
|
507
|
+
elif 'disk I/O error' in str(err):
|
414
508
|
exc_hint = 'disk error while accessing database'
|
415
509
|
else:
|
416
|
-
|
510
|
+
raise err
|
417
511
|
|
418
512
|
self.quarantine(exc_hint)
|
419
513
|
|
@@ -443,11 +537,12 @@ class DatabaseBroker(object):
|
|
443
537
|
def get(self):
|
444
538
|
"""Use with the "with" statement; returns a database connection."""
|
445
539
|
if not self.conn:
|
446
|
-
if
|
540
|
+
if os.path.exists(self.db_file):
|
447
541
|
try:
|
448
|
-
self.conn = get_db_connection(self.db_file, self.timeout
|
449
|
-
|
450
|
-
|
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)
|
451
546
|
else:
|
452
547
|
raise DatabaseConnectionError(self.db_file, "DB doesn't exist")
|
453
548
|
conn = self.conn
|
@@ -456,12 +551,12 @@ class DatabaseBroker(object):
|
|
456
551
|
yield conn
|
457
552
|
conn.rollback()
|
458
553
|
self.conn = conn
|
459
|
-
except sqlite3.DatabaseError:
|
554
|
+
except sqlite3.DatabaseError as e:
|
460
555
|
try:
|
461
556
|
conn.close()
|
462
557
|
except Exception:
|
463
558
|
pass
|
464
|
-
self.possibly_quarantine(
|
559
|
+
self.possibly_quarantine(e)
|
465
560
|
except (Exception, Timeout):
|
466
561
|
conn.close()
|
467
562
|
raise
|
@@ -470,8 +565,9 @@ class DatabaseBroker(object):
|
|
470
565
|
def lock(self):
|
471
566
|
"""Use with the "with" statement; locks a database."""
|
472
567
|
if not self.conn:
|
473
|
-
if
|
474
|
-
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)
|
475
571
|
else:
|
476
572
|
raise DatabaseConnectionError(self.db_file, "DB doesn't exist")
|
477
573
|
conn = self.conn
|
@@ -481,16 +577,19 @@ class DatabaseBroker(object):
|
|
481
577
|
conn.execute('BEGIN IMMEDIATE')
|
482
578
|
try:
|
483
579
|
yield True
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
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)
|
494
593
|
|
495
594
|
def newid(self, remote_id):
|
496
595
|
"""
|
@@ -501,7 +600,7 @@ class DatabaseBroker(object):
|
|
501
600
|
with self.get() as conn:
|
502
601
|
row = conn.execute('''
|
503
602
|
UPDATE %s_stat SET id=?
|
504
|
-
''' % self.db_type, (
|
603
|
+
''' % self.db_type, (self._new_db_id(),))
|
505
604
|
row = conn.execute('''
|
506
605
|
SELECT ROWID FROM %s ORDER BY ROWID DESC LIMIT 1
|
507
606
|
''' % self.db_contains_type).fetchone()
|
@@ -533,7 +632,7 @@ class DatabaseBroker(object):
|
|
533
632
|
|
534
633
|
:returns: True if the DB is considered to be deleted, False otherwise
|
535
634
|
"""
|
536
|
-
if
|
635
|
+
if not os.path.exists(self.db_file):
|
537
636
|
return True
|
538
637
|
self._commit_puts_stale_ok()
|
539
638
|
with self.get() as conn:
|
@@ -610,22 +709,26 @@ class DatabaseBroker(object):
|
|
610
709
|
return -1
|
611
710
|
return row['sync_point']
|
612
711
|
|
613
|
-
def get_syncs(self, incoming=True):
|
712
|
+
def get_syncs(self, incoming=True, include_timestamp=False):
|
614
713
|
"""
|
615
714
|
Get a serialized copy of the sync table.
|
616
715
|
|
617
716
|
:param incoming: if True, get the last incoming sync, otherwise get
|
618
717
|
the last outgoing sync
|
619
|
-
:
|
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.
|
620
722
|
"""
|
621
723
|
with self.get() as conn:
|
724
|
+
columns = 'remote_id, sync_point'
|
725
|
+
if include_timestamp:
|
726
|
+
columns += ', updated_at'
|
622
727
|
curs = conn.execute('''
|
623
|
-
SELECT
|
624
|
-
''' % ('incoming' if incoming else 'outgoing'))
|
625
|
-
|
626
|
-
for
|
627
|
-
result.append({'remote_id': row[0], 'sync_point': row[1]})
|
628
|
-
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]
|
629
732
|
|
630
733
|
def get_max_row(self, table=None):
|
631
734
|
if not table:
|
@@ -666,8 +769,8 @@ class DatabaseBroker(object):
|
|
666
769
|
"""
|
667
770
|
Put a record into the DB. If the DB has an associated pending file with
|
668
771
|
space then the record is appended to that file and a commit to the DB
|
669
|
-
is deferred. If
|
670
|
-
|
772
|
+
is deferred. If its pending file is full then the record will be
|
773
|
+
committed immediately.
|
671
774
|
|
672
775
|
:param record: a record to be added to the DB.
|
673
776
|
:raises DatabaseConnectionError: if the DB file does not exist or if
|
@@ -675,9 +778,6 @@ class DatabaseBroker(object):
|
|
675
778
|
:raises LockTimeout: if a timeout occurs while waiting to take a lock
|
676
779
|
to write to the pending file.
|
677
780
|
"""
|
678
|
-
if self._db_file == ':memory:':
|
679
|
-
self.merge_items([record])
|
680
|
-
return
|
681
781
|
if not os.path.exists(self.db_file):
|
682
782
|
raise DatabaseConnectionError(self.db_file, "DB doesn't exist")
|
683
783
|
if self.skip_commits:
|
@@ -703,8 +803,7 @@ class DatabaseBroker(object):
|
|
703
803
|
fp.flush()
|
704
804
|
|
705
805
|
def _skip_commit_puts(self):
|
706
|
-
return
|
707
|
-
os.path.exists(self.pending_file))
|
806
|
+
return self.skip_commits or not os.path.exists(self.pending_file)
|
708
807
|
|
709
808
|
def _commit_puts(self, item_list=None):
|
710
809
|
"""
|
@@ -733,15 +832,12 @@ class DatabaseBroker(object):
|
|
733
832
|
for entry in fp.read().split(b':'):
|
734
833
|
if entry:
|
735
834
|
try:
|
736
|
-
|
737
|
-
|
738
|
-
else:
|
739
|
-
data = pickle.loads(base64.b64decode(entry),
|
740
|
-
encoding='utf8')
|
835
|
+
data = pickle.loads(base64.b64decode(entry),
|
836
|
+
encoding='utf8') # nosec: B301
|
741
837
|
self._commit_puts_load(item_list, data)
|
742
838
|
except Exception:
|
743
839
|
self.logger.exception(
|
744
|
-
|
840
|
+
'Invalid pending entry %(file)s: %(entry)s',
|
745
841
|
{'file': self.pending_file, 'entry': entry})
|
746
842
|
if item_list:
|
747
843
|
self.merge_items(item_list)
|
@@ -818,7 +914,7 @@ class DatabaseBroker(object):
|
|
818
914
|
within 512k of a boundary, it allocates to the next boundary.
|
819
915
|
Boundaries are 2m, 5m, 10m, 25m, 50m, then every 50m after.
|
820
916
|
"""
|
821
|
-
if not DB_PREALLOCATION
|
917
|
+
if not DB_PREALLOCATION:
|
822
918
|
return
|
823
919
|
MB = (1024 * 1024)
|
824
920
|
|
@@ -865,7 +961,7 @@ class DatabaseBroker(object):
|
|
865
961
|
metadata = self.get_raw_metadata()
|
866
962
|
if metadata:
|
867
963
|
metadata = json.loads(metadata)
|
868
|
-
|
964
|
+
native_str_keys_and_values(metadata)
|
869
965
|
else:
|
870
966
|
metadata = {}
|
871
967
|
return metadata
|
@@ -927,7 +1023,7 @@ class DatabaseBroker(object):
|
|
927
1023
|
self.db_type)
|
928
1024
|
md = row[0]
|
929
1025
|
md = json.loads(md) if md else {}
|
930
|
-
|
1026
|
+
native_str_keys_and_values(md)
|
931
1027
|
except sqlite3.OperationalError as err:
|
932
1028
|
if 'no such column: metadata' not in str(err):
|
933
1029
|
raise
|
@@ -964,47 +1060,22 @@ class DatabaseBroker(object):
|
|
964
1060
|
with lock_parent_directory(self.pending_file,
|
965
1061
|
self.pending_timeout):
|
966
1062
|
self._commit_puts()
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
self._reclaim_other_stuff(
|
975
|
-
conn, age_timestamp, sync_timestamp)
|
976
|
-
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
|
977
1070
|
|
978
1071
|
def _reclaim_other_stuff(self, conn, age_timestamp, sync_timestamp):
|
979
1072
|
"""
|
980
|
-
This is only called once at the end of reclaim after
|
981
|
-
|
1073
|
+
This is only called once at the end of reclaim after tombstone reclaim
|
1074
|
+
has been completed.
|
982
1075
|
"""
|
983
1076
|
self._reclaim_sync(conn, sync_timestamp)
|
984
1077
|
self._reclaim_metadata(conn, age_timestamp)
|
985
1078
|
|
986
|
-
def _reclaim(self, conn, age_timestamp, marker):
|
987
|
-
clean_batch_qry = '''
|
988
|
-
DELETE FROM %s WHERE deleted = 1
|
989
|
-
AND name > ? AND %s < ?
|
990
|
-
''' % (self.db_contains_type, self.db_reclaim_timestamp)
|
991
|
-
curs = conn.execute('''
|
992
|
-
SELECT name FROM %s WHERE deleted = 1
|
993
|
-
AND name > ?
|
994
|
-
ORDER BY NAME LIMIT 1 OFFSET ?
|
995
|
-
''' % (self.db_contains_type,), (marker, RECLAIM_PAGE_SIZE))
|
996
|
-
row = curs.fetchone()
|
997
|
-
if row:
|
998
|
-
# do a single book-ended DELETE and bounce out
|
999
|
-
end_marker = row[0]
|
1000
|
-
conn.execute(clean_batch_qry + ' AND name <= ?', (
|
1001
|
-
marker, age_timestamp, end_marker))
|
1002
|
-
else:
|
1003
|
-
# delete off the end and reset marker to indicate we're done
|
1004
|
-
end_marker = ''
|
1005
|
-
conn.execute(clean_batch_qry, (marker, age_timestamp))
|
1006
|
-
return end_marker
|
1007
|
-
|
1008
1079
|
def _reclaim_sync(self, conn, sync_timestamp):
|
1009
1080
|
try:
|
1010
1081
|
conn.execute('''
|