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/server.py
CHANGED
@@ -15,14 +15,13 @@
|
|
15
15
|
|
16
16
|
import json
|
17
17
|
import os
|
18
|
+
import sys
|
18
19
|
import time
|
19
20
|
import traceback
|
20
|
-
import math
|
21
|
-
from swift import gettext_ as _
|
22
21
|
|
23
22
|
from eventlet import Timeout
|
24
23
|
|
25
|
-
import
|
24
|
+
from urllib.parse import quote
|
26
25
|
|
27
26
|
import swift.common.db
|
28
27
|
from swift.container.sync_store import ContainerSyncStore
|
@@ -31,16 +30,17 @@ from swift.container.backend import ContainerBroker, DATADIR, \
|
|
31
30
|
from swift.container.replicator import ContainerReplicatorRpc
|
32
31
|
from swift.common.db import DatabaseAlreadyExists
|
33
32
|
from swift.common.container_sync_realms import ContainerSyncRealms
|
34
|
-
from swift.common.request_helpers import
|
35
|
-
|
33
|
+
from swift.common.request_helpers import split_and_validate_path, \
|
34
|
+
is_sys_or_user_meta, validate_internal_container, validate_internal_obj, \
|
35
|
+
validate_container_params
|
36
36
|
from swift.common.utils import get_logger, hash_path, public, \
|
37
37
|
Timestamp, storage_directory, validate_sync_to, \
|
38
38
|
config_true_value, timing_stats, replication, \
|
39
39
|
override_bytes_from_content_type, get_log_line, \
|
40
40
|
config_fallocate_value, fs_has_free_space, list_from_csv, \
|
41
|
-
ShardRange
|
42
|
-
from swift.common.constraints import valid_timestamp, check_utf8,
|
43
|
-
|
41
|
+
ShardRange, parse_options
|
42
|
+
from swift.common.constraints import valid_timestamp, check_utf8, \
|
43
|
+
check_drive, AUTO_CREATE_ACCOUNT_PREFIX
|
44
44
|
from swift.common.bufferedhttp import http_connect
|
45
45
|
from swift.common.exceptions import ConnectionTimeout
|
46
46
|
from swift.common.http import HTTP_NO_CONTENT, HTTP_NOT_FOUND, is_success
|
@@ -53,6 +53,7 @@ from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPConflict, \
|
|
53
53
|
HTTPPreconditionFailed, HTTPMethodNotAllowed, Request, Response, \
|
54
54
|
HTTPInsufficientStorage, HTTPException, HTTPMovedPermanently, \
|
55
55
|
wsgi_to_str, str_to_wsgi
|
56
|
+
from swift.common.wsgi import run_wsgi
|
56
57
|
|
57
58
|
|
58
59
|
def gen_resp_headers(info, is_deleted=False):
|
@@ -83,6 +84,33 @@ def gen_resp_headers(info, is_deleted=False):
|
|
83
84
|
return headers
|
84
85
|
|
85
86
|
|
87
|
+
def get_container_name_and_placement(req):
|
88
|
+
"""
|
89
|
+
Split and validate path for a container.
|
90
|
+
|
91
|
+
:param req: a swob request
|
92
|
+
|
93
|
+
:returns: a tuple of path parts as strings
|
94
|
+
"""
|
95
|
+
drive, part, account, container = split_and_validate_path(req, 4)
|
96
|
+
validate_internal_container(account, container)
|
97
|
+
return drive, part, account, container
|
98
|
+
|
99
|
+
|
100
|
+
def get_obj_name_and_placement(req):
|
101
|
+
"""
|
102
|
+
Split and validate path for an object.
|
103
|
+
|
104
|
+
:param req: a swob request
|
105
|
+
|
106
|
+
:returns: a tuple of path parts as strings
|
107
|
+
"""
|
108
|
+
drive, part, account, container, obj = split_and_validate_path(
|
109
|
+
req, 4, 5, True)
|
110
|
+
validate_internal_obj(account, container, obj)
|
111
|
+
return drive, part, account, container, obj
|
112
|
+
|
113
|
+
|
86
114
|
class ContainerController(BaseStorageServer):
|
87
115
|
"""WSGI Controller for the container server."""
|
88
116
|
|
@@ -114,8 +142,9 @@ class ContainerController(BaseStorageServer):
|
|
114
142
|
self.replicator_rpc = ContainerReplicatorRpc(
|
115
143
|
self.root, DATADIR, ContainerBroker, self.mount_check,
|
116
144
|
logger=self.logger)
|
117
|
-
self.auto_create_account_prefix =
|
118
|
-
|
145
|
+
self.auto_create_account_prefix = AUTO_CREATE_ACCOUNT_PREFIX
|
146
|
+
self.shards_account_prefix = (
|
147
|
+
self.auto_create_account_prefix + 'shards_')
|
119
148
|
if config_true_value(conf.get('allow_versions', 'f')):
|
120
149
|
self.save_headers.append('x-versions-location')
|
121
150
|
if 'allow_versions' in conf:
|
@@ -125,6 +154,8 @@ class ContainerController(BaseStorageServer):
|
|
125
154
|
'be ignored in a future release.')
|
126
155
|
swift.common.db.DB_PREALLOCATION = \
|
127
156
|
config_true_value(conf.get('db_preallocation', 'f'))
|
157
|
+
swift.common.db.QUERY_LOGGING = \
|
158
|
+
config_true_value(conf.get('db_query_logging', 'f'))
|
128
159
|
self.sync_store = ContainerSyncStore(self.root,
|
129
160
|
self.logger,
|
130
161
|
self.mount_check)
|
@@ -196,19 +227,16 @@ class ContainerController(BaseStorageServer):
|
|
196
227
|
if len(account_hosts) != len(account_devices):
|
197
228
|
# This shouldn't happen unless there's a bug in the proxy,
|
198
229
|
# but if there is, we want to know about it.
|
199
|
-
self.logger.error(
|
230
|
+
self.logger.error(
|
200
231
|
'ERROR Account update failed: different '
|
201
232
|
'numbers of hosts and devices in request: '
|
202
|
-
'"%(hosts)s" vs "%(devices)s"'
|
233
|
+
'"%(hosts)s" vs "%(devices)s"', {
|
203
234
|
'hosts': req.headers.get('X-Account-Host', ''),
|
204
235
|
'devices': req.headers.get('X-Account-Device', '')})
|
205
236
|
return HTTPBadRequest(req=req)
|
206
237
|
|
207
238
|
if account_partition:
|
208
|
-
# zip is lazy
|
209
|
-
# On py2 it's an extra list copy, but the list is so small
|
210
|
-
# (one element per replica in account ring, usually 3) that it
|
211
|
-
# doesn't matter.
|
239
|
+
# zip is lazy, but we need a list, so force evaluation.
|
212
240
|
updates = list(zip(account_hosts, account_devices))
|
213
241
|
else:
|
214
242
|
updates = []
|
@@ -242,18 +270,18 @@ class ContainerController(BaseStorageServer):
|
|
242
270
|
if account_response.status == HTTP_NOT_FOUND:
|
243
271
|
account_404s += 1
|
244
272
|
elif not is_success(account_response.status):
|
245
|
-
self.logger.error(
|
273
|
+
self.logger.error(
|
246
274
|
'ERROR Account update failed '
|
247
275
|
'with %(ip)s:%(port)s/%(device)s (will retry '
|
248
|
-
'later): Response %(status)s %(reason)s'
|
276
|
+
'later): Response %(status)s %(reason)s',
|
249
277
|
{'ip': account_ip, 'port': account_port,
|
250
278
|
'device': account_device,
|
251
279
|
'status': account_response.status,
|
252
280
|
'reason': account_response.reason})
|
253
281
|
except (Exception, Timeout):
|
254
|
-
self.logger.exception(
|
282
|
+
self.logger.exception(
|
255
283
|
'ERROR account update failed with '
|
256
|
-
'%(ip)s:%(port)s/%(device)s (will retry later)'
|
284
|
+
'%(ip)s:%(port)s/%(device)s (will retry later)',
|
257
285
|
{'ip': account_ip, 'port': account_port,
|
258
286
|
'device': account_device})
|
259
287
|
if updates and account_404s == len(updates):
|
@@ -282,6 +310,11 @@ class ContainerController(BaseStorageServer):
|
|
282
310
|
"""
|
283
311
|
if not config_true_value(
|
284
312
|
req.headers.get('x-backend-accept-redirect', False)):
|
313
|
+
# We want to avoid fetching shard ranges for the (more
|
314
|
+
# time-sensitive) object-server update, so allow some misplaced
|
315
|
+
# objects to land between when we've started sharding and when the
|
316
|
+
# proxy learns about it. Note that this path is also used by old,
|
317
|
+
# pre-sharding updaters during a rolling upgrade.
|
285
318
|
return None
|
286
319
|
|
287
320
|
shard_ranges = broker.get_shard_ranges(
|
@@ -294,7 +327,15 @@ class ContainerController(BaseStorageServer):
|
|
294
327
|
# in preference to the parent, which is the desired result.
|
295
328
|
containing_range = shard_ranges[0]
|
296
329
|
location = "/%s/%s" % (containing_range.name, obj_name)
|
297
|
-
|
330
|
+
if location != quote(location) and not config_true_value(
|
331
|
+
req.headers.get('x-backend-accept-quoted-location', False)):
|
332
|
+
# Sender expects the destination to be unquoted, but it isn't safe
|
333
|
+
# to send unquoted. Eat the update for now and let the sharder
|
334
|
+
# move it later. Should only come up during rolling upgrades.
|
335
|
+
return None
|
336
|
+
|
337
|
+
headers = {'Location': quote(location),
|
338
|
+
'X-Backend-Location-Is-Quoted': 'true',
|
298
339
|
'X-Backend-Redirect-Timestamp':
|
299
340
|
containing_range.timestamp.internal}
|
300
341
|
|
@@ -311,8 +352,7 @@ class ContainerController(BaseStorageServer):
|
|
311
352
|
@timing_stats()
|
312
353
|
def DELETE(self, req):
|
313
354
|
"""Handle HTTP DELETE request."""
|
314
|
-
drive, part, account, container, obj =
|
315
|
-
req, 4, 5, True)
|
355
|
+
drive, part, account, container, obj = get_obj_name_and_placement(req)
|
316
356
|
req_timestamp = valid_timestamp(req)
|
317
357
|
try:
|
318
358
|
check_drive(self.root, drive, self.mount_check)
|
@@ -322,14 +362,12 @@ class ContainerController(BaseStorageServer):
|
|
322
362
|
# auto create accounts)
|
323
363
|
obj_policy_index = self.get_and_validate_policy_index(req) or 0
|
324
364
|
broker = self._get_container_broker(drive, part, account, container)
|
325
|
-
if
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
except DatabaseAlreadyExists:
|
330
|
-
pass
|
331
|
-
if not os.path.exists(broker.db_file):
|
365
|
+
if obj:
|
366
|
+
self._maybe_autocreate(broker, req_timestamp, account,
|
367
|
+
obj_policy_index, req)
|
368
|
+
elif not os.path.exists(broker.db_file):
|
332
369
|
return HTTPNotFound()
|
370
|
+
|
333
371
|
if obj: # delete object
|
334
372
|
# redirect if a shard range exists for the object name
|
335
373
|
redirect = self._redirect_to_shard(req, broker, obj)
|
@@ -396,11 +434,25 @@ class ContainerController(BaseStorageServer):
|
|
396
434
|
broker.update_status_changed_at(timestamp)
|
397
435
|
return recreated
|
398
436
|
|
437
|
+
def _should_autocreate(self, account, req):
|
438
|
+
auto_create_header = req.headers.get('X-Backend-Auto-Create')
|
439
|
+
if auto_create_header:
|
440
|
+
# If the caller included an explicit X-Backend-Auto-Create header,
|
441
|
+
# assume they know the behavior they want
|
442
|
+
return config_true_value(auto_create_header)
|
443
|
+
if account.startswith(self.shards_account_prefix):
|
444
|
+
# we have to specical case this subset of the
|
445
|
+
# auto_create_account_prefix because we don't want the updater
|
446
|
+
# accidently auto-creating shards; only the sharder creates
|
447
|
+
# shards and it will explicitly tell the server to do so
|
448
|
+
return False
|
449
|
+
return account.startswith(self.auto_create_account_prefix)
|
450
|
+
|
399
451
|
def _maybe_autocreate(self, broker, req_timestamp, account,
|
400
|
-
policy_index):
|
452
|
+
policy_index, req):
|
401
453
|
created = False
|
402
|
-
|
403
|
-
|
454
|
+
should_autocreate = self._should_autocreate(account, req)
|
455
|
+
if should_autocreate and not os.path.exists(broker.db_file):
|
404
456
|
if policy_index is None:
|
405
457
|
raise HTTPBadRequest(
|
406
458
|
'X-Backend-Storage-Policy-Index header is required')
|
@@ -433,8 +485,7 @@ class ContainerController(BaseStorageServer):
|
|
433
485
|
@timing_stats()
|
434
486
|
def PUT(self, req):
|
435
487
|
"""Handle HTTP PUT request."""
|
436
|
-
drive, part, account, container, obj =
|
437
|
-
req, 4, 5, True)
|
488
|
+
drive, part, account, container, obj = get_obj_name_and_placement(req)
|
438
489
|
req_timestamp = valid_timestamp(req)
|
439
490
|
if 'x-container-sync-to' in req.headers:
|
440
491
|
err, sync_to, realm, realm_key = validate_sync_to(
|
@@ -448,59 +499,42 @@ class ContainerController(BaseStorageServer):
|
|
448
499
|
return HTTPInsufficientStorage(drive=drive, request=req)
|
449
500
|
if not self.check_free_space(drive):
|
450
501
|
return HTTPInsufficientStorage(drive=drive, request=req)
|
451
|
-
requested_policy_index = self.get_and_validate_policy_index(req)
|
452
|
-
broker = self._get_container_broker(drive, part, account, container)
|
453
|
-
if obj: # put container object
|
454
|
-
# obj put expects the policy_index header, default is for
|
455
|
-
# legacy support during upgrade.
|
456
|
-
obj_policy_index = requested_policy_index or 0
|
457
|
-
self._maybe_autocreate(broker, req_timestamp, account,
|
458
|
-
obj_policy_index)
|
459
|
-
# redirect if a shard exists for this object name
|
460
|
-
response = self._redirect_to_shard(req, broker, obj)
|
461
|
-
if response:
|
462
|
-
return response
|
463
|
-
|
464
|
-
broker.put_object(obj, req_timestamp.internal,
|
465
|
-
int(req.headers['x-size']),
|
466
|
-
wsgi_to_str(req.headers['x-content-type']),
|
467
|
-
wsgi_to_str(req.headers['x-etag']), 0,
|
468
|
-
obj_policy_index,
|
469
|
-
wsgi_to_str(req.headers.get(
|
470
|
-
'x-content-type-timestamp')),
|
471
|
-
wsgi_to_str(req.headers.get('x-meta-timestamp')))
|
472
|
-
return HTTPCreated(request=req)
|
473
502
|
|
503
|
+
broker = self._get_container_broker(drive, part, account, container)
|
504
|
+
if obj:
|
505
|
+
return self.PUT_object(req, broker, account, obj, req_timestamp)
|
474
506
|
record_type = req.headers.get('x-backend-record-type', '').lower()
|
475
507
|
if record_type == RECORD_TYPE_SHARD:
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
508
|
+
return self.PUT_shard(req, broker, account, req_timestamp)
|
509
|
+
else:
|
510
|
+
return self.PUT_container(req, broker, account,
|
511
|
+
container, req_timestamp)
|
512
|
+
|
513
|
+
@timing_stats()
|
514
|
+
def PUT_object(self, req, broker, account, obj, req_timestamp):
|
515
|
+
"""Put object into container."""
|
516
|
+
# obj put expects the policy_index header, default is for
|
517
|
+
# legacy support during upgrade.
|
518
|
+
requested_policy_index = self.get_and_validate_policy_index(req)
|
519
|
+
obj_policy_index = requested_policy_index or 0
|
520
|
+
self._maybe_autocreate(
|
521
|
+
broker, req_timestamp, account, obj_policy_index, req)
|
522
|
+
# redirect if a shard exists for this object name
|
523
|
+
response = self._redirect_to_shard(req, broker, obj)
|
524
|
+
if response:
|
525
|
+
return response
|
526
|
+
|
527
|
+
broker.put_object(obj, req_timestamp.internal,
|
528
|
+
int(req.headers['x-size']),
|
529
|
+
wsgi_to_str(req.headers['x-content-type']),
|
530
|
+
wsgi_to_str(req.headers['x-etag']), 0,
|
531
|
+
obj_policy_index,
|
532
|
+
wsgi_to_str(req.headers.get(
|
533
|
+
'x-content-type-timestamp')),
|
534
|
+
wsgi_to_str(req.headers.get('x-meta-timestamp')))
|
535
|
+
return HTTPCreated(request=req)
|
536
|
+
|
537
|
+
def _create_ok_resp(self, req, broker, created):
|
504
538
|
if created:
|
505
539
|
return HTTPCreated(request=req,
|
506
540
|
headers={'x-backend-storage-policy-index':
|
@@ -510,12 +544,50 @@ class ContainerController(BaseStorageServer):
|
|
510
544
|
headers={'x-backend-storage-policy-index':
|
511
545
|
broker.storage_policy_index})
|
512
546
|
|
547
|
+
@timing_stats()
|
548
|
+
def PUT_shard(self, req, broker, account, req_timestamp):
|
549
|
+
"""Put shards into container."""
|
550
|
+
requested_policy_index = self.get_and_validate_policy_index(req)
|
551
|
+
try:
|
552
|
+
# validate incoming data...
|
553
|
+
shard_ranges = [ShardRange.from_dict(sr)
|
554
|
+
for sr in json.loads(req.body)]
|
555
|
+
except (ValueError, KeyError, TypeError) as err:
|
556
|
+
return HTTPBadRequest('Invalid body: %r' % err)
|
557
|
+
created = self._maybe_autocreate(
|
558
|
+
broker, req_timestamp, account, requested_policy_index, req)
|
559
|
+
self._update_metadata(req, broker, req_timestamp, 'PUT')
|
560
|
+
if shard_ranges:
|
561
|
+
# TODO: consider writing the shard ranges into the pending
|
562
|
+
# file, but if so ensure an all-or-none semantic for the write
|
563
|
+
broker.merge_shard_ranges(shard_ranges)
|
564
|
+
return self._create_ok_resp(req, broker, created)
|
565
|
+
|
566
|
+
@timing_stats()
|
567
|
+
def PUT_container(self, req, broker, account, container, req_timestamp):
|
568
|
+
"""Update or create container."""
|
569
|
+
requested_policy_index = self.get_and_validate_policy_index(req)
|
570
|
+
if requested_policy_index is None:
|
571
|
+
# use the default index sent by the proxy if available
|
572
|
+
new_container_policy = req.headers.get(
|
573
|
+
'X-Backend-Storage-Policy-Default', int(POLICIES.default))
|
574
|
+
else:
|
575
|
+
new_container_policy = requested_policy_index
|
576
|
+
created = self._update_or_create(req, broker,
|
577
|
+
req_timestamp.internal,
|
578
|
+
new_container_policy,
|
579
|
+
requested_policy_index)
|
580
|
+
self._update_metadata(req, broker, req_timestamp, 'PUT')
|
581
|
+
resp = self.account_update(req, account, container, broker)
|
582
|
+
if resp:
|
583
|
+
return resp
|
584
|
+
return self._create_ok_resp(req, broker, created)
|
585
|
+
|
513
586
|
@public
|
514
587
|
@timing_stats(sample_rate=0.1)
|
515
588
|
def HEAD(self, req):
|
516
589
|
"""Handle HTTP HEAD request."""
|
517
|
-
drive, part, account, container, obj =
|
518
|
-
req, 4, 5, True)
|
590
|
+
drive, part, account, container, obj = get_obj_name_and_placement(req)
|
519
591
|
out_content_type = listing_formats.get_listing_content_type(req)
|
520
592
|
try:
|
521
593
|
check_drive(self.root, drive, self.mount_check)
|
@@ -534,33 +606,46 @@ class ContainerController(BaseStorageServer):
|
|
534
606
|
if value != '' and (key.lower() in self.save_headers or
|
535
607
|
is_sys_or_user_meta('container', key)))
|
536
608
|
headers['Content-Type'] = out_content_type
|
609
|
+
headers['Content-Length'] = 0
|
537
610
|
resp = HTTPNoContent(request=req, headers=headers, charset='utf-8')
|
538
|
-
resp.last_modified =
|
611
|
+
resp.last_modified = Timestamp(headers['X-PUT-Timestamp']).ceil()
|
539
612
|
return resp
|
540
613
|
|
541
|
-
def
|
614
|
+
def update_shard_record(self, record, shard_record_full=True):
|
615
|
+
"""
|
616
|
+
Return the shard_range database record as a dict, the keys will depend
|
617
|
+
on the database fields provided in the record.
|
618
|
+
|
619
|
+
:param record: shard entry record, either ShardRange or Namespace.
|
620
|
+
:param shard_record_full: boolean, when true the timestamp field is
|
621
|
+
added as "last_modified" in iso format.
|
622
|
+
:returns: dict suitable for listing responses
|
623
|
+
"""
|
624
|
+
response = dict(record)
|
625
|
+
if shard_record_full:
|
626
|
+
created = record.timestamp
|
627
|
+
response['last_modified'] = Timestamp(created).isoformat
|
628
|
+
return response
|
629
|
+
|
630
|
+
def update_object_record(self, record):
|
542
631
|
"""
|
543
|
-
Perform
|
544
|
-
|
632
|
+
Perform mutation to container listing records that are common to all
|
633
|
+
serialization formats, and returns it as a dict.
|
545
634
|
|
546
635
|
Converts created time to iso timestamp.
|
547
636
|
Replaces size with 'swift_bytes' content type parameter.
|
548
637
|
|
549
|
-
:
|
638
|
+
:param record: object entry record
|
550
639
|
:returns: modified record
|
551
640
|
"""
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
response = {
|
561
|
-
'bytes': size, 'hash': etag, 'name': name_,
|
562
|
-
'content_type': content_type}
|
563
|
-
override_bytes_from_content_type(response, logger=self.logger)
|
641
|
+
# record is object info
|
642
|
+
(name, created, size, content_type, etag) = record[:5]
|
643
|
+
if content_type is None:
|
644
|
+
return {'subdir': name}
|
645
|
+
response = {
|
646
|
+
'bytes': size, 'hash': etag, 'name': name,
|
647
|
+
'content_type': content_type}
|
648
|
+
override_bytes_from_content_type(response, logger=self.logger)
|
564
649
|
response['last_modified'] = Timestamp(created).isoformat
|
565
650
|
return response
|
566
651
|
|
@@ -582,6 +667,19 @@ class ContainerController(BaseStorageServer):
|
|
582
667
|
``sharded``, then the listing will be a list of shard ranges;
|
583
668
|
otherwise the response body will be a list of objects.
|
584
669
|
|
670
|
+
* Both shard range and object listings may be filtered according to
|
671
|
+
the constraints described below. However, the
|
672
|
+
``X-Backend-Ignore-Shard-Name-Filter`` header may be used to override
|
673
|
+
the application of the ``marker``, ``end_marker``, ``includes`` and
|
674
|
+
``reverse`` parameters to shard range listings. These parameters will
|
675
|
+
be ignored if the header has the value 'sharded' and the current db
|
676
|
+
sharding state is also 'sharded'. Note that this header does not
|
677
|
+
override the ``states`` constraint on shard range listings.
|
678
|
+
|
679
|
+
* The order of both shard range and object listings may be reversed by
|
680
|
+
using a ``reverse`` query string parameter with a
|
681
|
+
value in :attr:`swift.common.utils.TRUE_VALUES`.
|
682
|
+
|
585
683
|
* Both shard range and object listings may be constrained to a name
|
586
684
|
range by the ``marker`` and ``end_marker`` query string parameters.
|
587
685
|
Object listings will only contain objects whose names are greater
|
@@ -610,11 +708,11 @@ class ContainerController(BaseStorageServer):
|
|
610
708
|
either the string or integer representation of
|
611
709
|
:data:`~swift.common.utils.ShardRange.STATES`.
|
612
710
|
|
613
|
-
|
614
|
-
``listing`` will cause the listing to include all shard ranges
|
615
|
-
state suitable for contributing to an object listing
|
616
|
-
will cause the listing to include all shard ranges
|
617
|
-
suitable to accept an object update.
|
711
|
+
Alias values may be used in a ``states`` parameter value. The
|
712
|
+
``listing`` alias will cause the listing to include all shard ranges
|
713
|
+
in a state suitable for contributing to an object listing. The
|
714
|
+
``updating`` alias will cause the listing to include all shard ranges
|
715
|
+
in a state suitable to accept an object update.
|
618
716
|
|
619
717
|
If either of these aliases is used then the shard range listing will
|
620
718
|
if necessary be extended with a synthesised 'filler' range in order
|
@@ -623,6 +721,23 @@ class ContainerController(BaseStorageServer):
|
|
623
721
|
uncovered tail of the requested name range and will point back to the
|
624
722
|
same container.
|
625
723
|
|
724
|
+
The ``auditing`` alias will cause the listing to include all shard
|
725
|
+
ranges in a state useful to the sharder while auditing a shard
|
726
|
+
container. This alias will not cause a 'filler' range to be added,
|
727
|
+
but will cause the container's own shard range to be included in the
|
728
|
+
listing. For now, ``auditing`` is only supported when
|
729
|
+
'X-Backend-Record-Shard-Format' is 'full'.
|
730
|
+
|
731
|
+
* Shard range listings can be simplified to include only Namespace
|
732
|
+
only attributes (name, lower and upper) if the caller send the header
|
733
|
+
``X-Backend-Record-Shard-Format`` with value 'namespace' as a hint
|
734
|
+
that it would prefer namespaces. If this header doesn't exist or the
|
735
|
+
value is 'full', the listings will default to include all attributes
|
736
|
+
of shard ranges. But if params has includes/marker/end_marker then
|
737
|
+
the response will be full shard ranges, regardless the header of
|
738
|
+
``X-Backend-Record-Shard-Format``. The response header
|
739
|
+
``X-Backend-Record-Type`` will tell the user what type it gets back.
|
740
|
+
|
626
741
|
* Listings are not normally returned from a deleted container. However,
|
627
742
|
the ``X-Backend-Override-Deleted`` header may be used with a value in
|
628
743
|
:attr:`swift.common.utils.TRUE_VALUES` to force a shard range
|
@@ -632,23 +747,8 @@ class ContainerController(BaseStorageServer):
|
|
632
747
|
:param req: an instance of :class:`swift.common.swob.Request`
|
633
748
|
:returns: an instance of :class:`swift.common.swob.Response`
|
634
749
|
"""
|
635
|
-
drive, part, account, container, obj =
|
636
|
-
|
637
|
-
path = get_param(req, 'path')
|
638
|
-
prefix = get_param(req, 'prefix')
|
639
|
-
delimiter = get_param(req, 'delimiter')
|
640
|
-
marker = get_param(req, 'marker', '')
|
641
|
-
end_marker = get_param(req, 'end_marker')
|
642
|
-
limit = constraints.CONTAINER_LISTING_LIMIT
|
643
|
-
given_limit = get_param(req, 'limit')
|
644
|
-
reverse = config_true_value(get_param(req, 'reverse'))
|
645
|
-
if given_limit and given_limit.isdigit():
|
646
|
-
limit = int(given_limit)
|
647
|
-
if limit > constraints.CONTAINER_LISTING_LIMIT:
|
648
|
-
return HTTPPreconditionFailed(
|
649
|
-
request=req,
|
650
|
-
body='Maximum limit is %d'
|
651
|
-
% constraints.CONTAINER_LISTING_LIMIT)
|
750
|
+
drive, part, account, container, obj = get_obj_name_and_placement(req)
|
751
|
+
params = validate_container_params(req)
|
652
752
|
out_content_type = listing_formats.get_listing_content_type(req)
|
653
753
|
try:
|
654
754
|
check_drive(self.root, drive, self.mount_check)
|
@@ -659,55 +759,158 @@ class ContainerController(BaseStorageServer):
|
|
659
759
|
stale_reads_ok=True)
|
660
760
|
info, is_deleted = broker.get_info_is_deleted()
|
661
761
|
record_type = req.headers.get('x-backend-record-type', '').lower()
|
662
|
-
|
663
|
-
|
762
|
+
db_state = info.get('db_state')
|
763
|
+
if record_type == 'auto' and db_state in (SHARDING, SHARDED):
|
664
764
|
record_type = 'shard'
|
665
765
|
if record_type == 'shard':
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
766
|
+
return self.GET_shard(req, broker, container, params, info,
|
767
|
+
is_deleted, out_content_type)
|
768
|
+
else:
|
769
|
+
return self.GET_object(req, broker, container, params, info,
|
770
|
+
is_deleted, out_content_type)
|
771
|
+
|
772
|
+
@timing_stats()
|
773
|
+
def GET_shard(self, req, broker, container, params, info,
|
774
|
+
is_deleted, out_content_type):
|
775
|
+
"""
|
776
|
+
Returns a list of persisted shard ranges or namespaces in response.
|
777
|
+
|
778
|
+
:param req: swob.Request object
|
779
|
+
:param broker: container DB broker object
|
780
|
+
:param container: container name
|
781
|
+
:param params: the request params.
|
782
|
+
:param info: the global info for the container
|
783
|
+
:param is_deleted: the is_deleted status for the container.
|
784
|
+
:param out_content_type: content type as a string.
|
785
|
+
:returns: an instance of :class:`swift.common.swob.Response`
|
786
|
+
"""
|
787
|
+
override_deleted = info and config_true_value(
|
788
|
+
req.headers.get('x-backend-override-deleted', False))
|
789
|
+
resp_headers = gen_resp_headers(
|
790
|
+
info, is_deleted=is_deleted and not override_deleted)
|
791
|
+
|
792
|
+
if is_deleted and not override_deleted:
|
793
|
+
return HTTPNotFound(request=req, headers=resp_headers)
|
794
|
+
|
795
|
+
marker = params.get('marker', '')
|
796
|
+
end_marker = params.get('end_marker')
|
797
|
+
reverse = config_true_value(params.get('reverse'))
|
798
|
+
states = params.get('states')
|
799
|
+
includes = params.get('includes')
|
800
|
+
include_deleted = config_true_value(
|
801
|
+
req.headers.get('x-backend-include-deleted', False))
|
802
|
+
|
803
|
+
resp_headers['X-Backend-Record-Type'] = 'shard'
|
804
|
+
override_filter_hdr = req.headers.get(
|
805
|
+
'x-backend-override-shard-name-filter', '').lower()
|
806
|
+
if override_filter_hdr == info.get('db_state') == 'sharded':
|
807
|
+
# respect the request to send back *all* ranges if the db is in
|
808
|
+
# sharded state
|
809
|
+
resp_headers['X-Backend-Override-Shard-Name-Filter'] = 'true'
|
810
|
+
marker = end_marker = includes = None
|
811
|
+
reverse = False
|
812
|
+
fill_gaps = include_own = False
|
813
|
+
if states:
|
814
|
+
states = list_from_csv(states)
|
815
|
+
fill_gaps = any(('listing' in states, 'updating' in states))
|
816
|
+
# The 'auditing' state alias is used by the sharder during
|
817
|
+
# shard audit; if the shard is shrinking then it needs to get
|
818
|
+
# acceptor shard ranges, which may be the root container
|
819
|
+
# itself, so use include_own.
|
820
|
+
include_own = 'auditing' in states
|
821
|
+
try:
|
822
|
+
states = broker.resolve_shard_range_states(states)
|
823
|
+
except ValueError:
|
824
|
+
return HTTPBadRequest(request=req, body='Bad state')
|
825
|
+
|
826
|
+
# For record type of 'shard', user can specify an additional header
|
827
|
+
# to ask for list of Namespaces instead of full ShardRanges.
|
828
|
+
# This will allow proxy server who is going to retrieve Namespace
|
829
|
+
# to talk to older version of container servers who don't support
|
830
|
+
# Namespace yet during upgrade.
|
831
|
+
shard_format = req.headers.get(
|
832
|
+
'x-backend-record-shard-format', 'full').lower()
|
833
|
+
if shard_format == 'namespace':
|
834
|
+
resp_headers['X-Backend-Record-Shard-Format'] = 'namespace'
|
835
|
+
# Namespace GET does not support all the options of Shard Range
|
836
|
+
# GET: 'x-backend-include-deleted' cannot be supported because
|
837
|
+
# there is no way for a Namespace to indicate the deleted state;
|
838
|
+
# the 'auditing' state query parameter is not supported because it
|
839
|
+
# is specific to the sharder which only requests full shard ranges.
|
840
|
+
if include_deleted:
|
841
|
+
return HTTPBadRequest(
|
842
|
+
request=req, body='No include_deleted for namespace GET')
|
843
|
+
if include_own:
|
844
|
+
return HTTPBadRequest(
|
845
|
+
request=req, body='No auditing state for namespace GET')
|
846
|
+
shard_format_full = False
|
847
|
+
container_list = broker.get_namespaces(
|
848
|
+
marker, end_marker, includes, reverse, states, fill_gaps)
|
849
|
+
else:
|
850
|
+
resp_headers['X-Backend-Record-Shard-Format'] = 'full'
|
851
|
+
shard_format_full = True
|
685
852
|
container_list = broker.get_shard_ranges(
|
686
853
|
marker, end_marker, includes, reverse, states=states,
|
687
|
-
include_deleted=include_deleted, fill_gaps=fill_gaps
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
854
|
+
include_deleted=include_deleted, fill_gaps=fill_gaps,
|
855
|
+
include_own=include_own)
|
856
|
+
listing = [self.update_shard_record(record, shard_format_full)
|
857
|
+
for record in container_list]
|
858
|
+
return self._create_GET_response(req, out_content_type, info,
|
859
|
+
resp_headers, broker.metadata,
|
860
|
+
container, listing)
|
861
|
+
|
862
|
+
@timing_stats()
|
863
|
+
def GET_object(self, req, broker, container, params, info,
|
864
|
+
is_deleted, out_content_type):
|
865
|
+
"""
|
866
|
+
Returns a list of objects in response.
|
867
|
+
|
868
|
+
:param req: swob.Request object
|
869
|
+
:param broker: container DB broker object
|
870
|
+
:param container: container name
|
871
|
+
:param params: the request params.
|
872
|
+
:param info: the global info for the container
|
873
|
+
:param is_deleted: the is_deleted status for the container.
|
874
|
+
:param out_content_type: content type as a string.
|
875
|
+
:returns: an instance of :class:`swift.common.swob.Response`
|
876
|
+
"""
|
877
|
+
marker = params.get('marker', '')
|
878
|
+
end_marker = params.get('end_marker')
|
879
|
+
reverse = config_true_value(params.get('reverse'))
|
880
|
+
path = params.get('path')
|
881
|
+
prefix = params.get('prefix')
|
882
|
+
delimiter = params.get('delimiter')
|
883
|
+
limit = params['limit']
|
884
|
+
requested_policy_index = self.get_and_validate_policy_index(req)
|
885
|
+
resp_headers = gen_resp_headers(info, is_deleted=is_deleted)
|
886
|
+
if is_deleted:
|
887
|
+
return HTTPNotFound(request=req, headers=resp_headers)
|
888
|
+
resp_headers['X-Backend-Record-Type'] = 'object'
|
889
|
+
storage_policy_index = (
|
890
|
+
requested_policy_index if requested_policy_index is not None
|
891
|
+
else info['storage_policy_index'])
|
892
|
+
resp_headers['X-Backend-Record-Storage-Policy-Index'] = \
|
893
|
+
storage_policy_index
|
894
|
+
# Use the retired db while container is in process of sharding,
|
895
|
+
# otherwise use current db
|
896
|
+
src_broker = broker.get_brokers()[0]
|
897
|
+
container_list = src_broker.list_objects_iter(
|
898
|
+
limit, marker, end_marker, prefix, delimiter, path,
|
899
|
+
storage_policy_index=storage_policy_index,
|
900
|
+
reverse=reverse, allow_reserved=req.allow_reserved_names)
|
901
|
+
listing = [self.update_object_record(record)
|
902
|
+
for record in container_list]
|
903
|
+
return self._create_GET_response(req, out_content_type, info,
|
904
|
+
resp_headers, broker.metadata,
|
905
|
+
container, listing)
|
906
|
+
|
907
|
+
def _create_GET_response(self, req, out_content_type, info, resp_headers,
|
908
|
+
metadata, container, listing):
|
705
909
|
for key, (value, _timestamp) in metadata.items():
|
706
910
|
if value and (key.lower() in self.save_headers or
|
707
911
|
is_sys_or_user_meta('container', key)):
|
708
912
|
resp_headers[str_to_wsgi(key)] = str_to_wsgi(value)
|
709
|
-
|
710
|
-
for record in container_list]
|
913
|
+
|
711
914
|
if out_content_type.endswith('/xml'):
|
712
915
|
body = listing_formats.container_to_xml(listing, container)
|
713
916
|
elif out_content_type.endswith('/json'):
|
@@ -717,7 +920,7 @@ class ContainerController(BaseStorageServer):
|
|
717
920
|
|
718
921
|
ret = Response(request=req, headers=resp_headers, body=body,
|
719
922
|
content_type=out_content_type, charset='utf-8')
|
720
|
-
ret.last_modified =
|
923
|
+
ret.last_modified = Timestamp(resp_headers['X-PUT-Timestamp']).ceil()
|
721
924
|
if not ret.body:
|
722
925
|
ret.status_int = HTTP_NO_CONTENT
|
723
926
|
return ret
|
@@ -751,7 +954,7 @@ class ContainerController(BaseStorageServer):
|
|
751
954
|
"""
|
752
955
|
Handle HTTP UPDATE request (merge_items RPCs coming from the proxy.)
|
753
956
|
"""
|
754
|
-
drive, part, account, container =
|
957
|
+
drive, part, account, container = get_container_name_and_placement(req)
|
755
958
|
req_timestamp = valid_timestamp(req)
|
756
959
|
try:
|
757
960
|
check_drive(self.root, drive, self.mount_check)
|
@@ -763,7 +966,7 @@ class ContainerController(BaseStorageServer):
|
|
763
966
|
requested_policy_index = self.get_and_validate_policy_index(req)
|
764
967
|
broker = self._get_container_broker(drive, part, account, container)
|
765
968
|
self._maybe_autocreate(broker, req_timestamp, account,
|
766
|
-
requested_policy_index)
|
969
|
+
requested_policy_index, req)
|
767
970
|
try:
|
768
971
|
objs = json.load(req.environ['wsgi.input'])
|
769
972
|
except ValueError as err:
|
@@ -774,8 +977,15 @@ class ContainerController(BaseStorageServer):
|
|
774
977
|
@public
|
775
978
|
@timing_stats()
|
776
979
|
def POST(self, req):
|
777
|
-
"""
|
778
|
-
|
980
|
+
"""
|
981
|
+
Handle HTTP POST request.
|
982
|
+
|
983
|
+
A POST request will update the container's ``put_timestamp``, unless
|
984
|
+
it has an ``X-Backend-No-Timestamp-Update`` header with a truthy value.
|
985
|
+
|
986
|
+
:param req: an instance of :class:`~swift.common.swob.Request`.
|
987
|
+
"""
|
988
|
+
drive, part, account, container = get_container_name_and_placement(req)
|
779
989
|
req_timestamp = valid_timestamp(req)
|
780
990
|
if 'x-container-sync-to' in req.headers:
|
781
991
|
err, sync_to, realm, realm_key = validate_sync_to(
|
@@ -792,7 +1002,9 @@ class ContainerController(BaseStorageServer):
|
|
792
1002
|
broker = self._get_container_broker(drive, part, account, container)
|
793
1003
|
if broker.is_deleted():
|
794
1004
|
return HTTPNotFound(request=req)
|
795
|
-
|
1005
|
+
if not config_true_value(
|
1006
|
+
req.headers.get('x-backend-no-timestamp-update', False)):
|
1007
|
+
broker.update_put_timestamp(req_timestamp.internal)
|
796
1008
|
self._update_metadata(req, broker, req_timestamp, 'POST')
|
797
1009
|
return HTTPNoContent(request=req)
|
798
1010
|
|
@@ -800,7 +1012,7 @@ class ContainerController(BaseStorageServer):
|
|
800
1012
|
start_time = time.time()
|
801
1013
|
req = Request(env)
|
802
1014
|
self.logger.txn_id = req.headers.get('x-trans-id', None)
|
803
|
-
if not check_utf8(wsgi_to_str(req.path_info)):
|
1015
|
+
if not check_utf8(wsgi_to_str(req.path_info), internal=True):
|
804
1016
|
res = HTTPPreconditionFailed(body='Invalid UTF8 or contains NULL')
|
805
1017
|
else:
|
806
1018
|
try:
|
@@ -812,8 +1024,8 @@ class ContainerController(BaseStorageServer):
|
|
812
1024
|
except HTTPException as error_response:
|
813
1025
|
res = error_response
|
814
1026
|
except (Exception, Timeout):
|
815
|
-
self.logger.exception(
|
816
|
-
'ERROR __call__ error with %(method)s %(path)s '
|
1027
|
+
self.logger.exception(
|
1028
|
+
'ERROR __call__ error with %(method)s %(path)s ',
|
817
1029
|
{'method': req.method, 'path': req.path})
|
818
1030
|
res = HTTPInternalServerError(body=traceback.format_exc())
|
819
1031
|
if self.log_requests:
|
@@ -834,3 +1046,12 @@ def app_factory(global_conf, **local_conf):
|
|
834
1046
|
conf = global_conf.copy()
|
835
1047
|
conf.update(local_conf)
|
836
1048
|
return ContainerController(conf)
|
1049
|
+
|
1050
|
+
|
1051
|
+
def main():
|
1052
|
+
conf_file, options = parse_options(test_config=True)
|
1053
|
+
sys.exit(run_wsgi(conf_file, 'container-server', **options))
|
1054
|
+
|
1055
|
+
|
1056
|
+
if __name__ == '__main__':
|
1057
|
+
main()
|