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/request_helpers.py
CHANGED
@@ -20,69 +20,182 @@ Why not swift.common.utils, you ask? Because this way we can import things
|
|
20
20
|
from swob in here without creating circular imports.
|
21
21
|
"""
|
22
22
|
|
23
|
-
import hashlib
|
24
23
|
import itertools
|
25
|
-
import sys
|
26
24
|
import time
|
27
25
|
|
28
|
-
import six
|
29
26
|
from swift.common.header_key_dict import HeaderKeyDict
|
30
27
|
|
31
|
-
from swift import
|
28
|
+
from swift.common.constraints import AUTO_CREATE_ACCOUNT_PREFIX, \
|
29
|
+
CONTAINER_LISTING_LIMIT
|
32
30
|
from swift.common.storage_policy import POLICIES
|
33
31
|
from swift.common.exceptions import ListingIterError, SegmentError
|
34
|
-
from swift.common.http import is_success
|
32
|
+
from swift.common.http import is_success, is_server_error
|
35
33
|
from swift.common.swob import HTTPBadRequest, \
|
36
34
|
HTTPServiceUnavailable, Range, is_chunked, multi_range_iterator, \
|
37
35
|
HTTPPreconditionFailed, wsgi_to_bytes, wsgi_unquote, wsgi_to_str
|
38
36
|
from swift.common.utils import split_path, validate_device_partition, \
|
39
|
-
close_if_possible,
|
37
|
+
close_if_possible, friendly_close, \
|
38
|
+
maybe_multipart_byteranges_to_document_iters, \
|
40
39
|
multipart_byteranges_to_document_iters, parse_content_type, \
|
41
|
-
parse_content_range, csv_append, list_from_csv, Spliterator, quote
|
42
|
-
|
40
|
+
parse_content_range, csv_append, list_from_csv, Spliterator, quote, \
|
41
|
+
RESERVED, config_true_value, md5, CloseableChain, select_ip_port
|
43
42
|
from swift.common.wsgi import make_subrequest
|
44
43
|
|
45
44
|
|
46
45
|
OBJECT_TRANSIENT_SYSMETA_PREFIX = 'x-object-transient-sysmeta-'
|
47
46
|
OBJECT_SYSMETA_CONTAINER_UPDATE_OVERRIDE_PREFIX = \
|
48
47
|
'x-object-sysmeta-container-update-override-'
|
48
|
+
USE_REPLICATION_NETWORK_HEADER = 'x-backend-use-replication-network'
|
49
|
+
MISPLACED_OBJECTS_ACCOUNT = '.misplaced_objects'
|
49
50
|
|
50
51
|
|
51
52
|
def get_param(req, name, default=None):
|
52
53
|
"""
|
53
|
-
Get
|
54
|
+
Get a parameter from an HTTP request ensuring proper handling UTF-8
|
54
55
|
encoding.
|
55
56
|
|
56
57
|
:param req: request object
|
57
58
|
:param name: parameter name
|
58
59
|
:param default: result to return if the parameter is not found
|
59
|
-
:returns: HTTP request parameter value, as a native string
|
60
|
-
(in py2, as UTF-8 encoded str, not unicode object)
|
60
|
+
:returns: HTTP request parameter value, as a native (not WSGI) string
|
61
61
|
:raises HTTPBadRequest: if param not valid UTF-8 byte sequence
|
62
62
|
"""
|
63
63
|
value = req.params.get(name, default)
|
64
|
-
if
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
# req.params is a dict of WSGI strings, so encoding will succeed
|
75
|
-
value = value.encode('latin1')
|
76
|
-
try:
|
77
|
-
# Ensure UTF8ness since we're at it
|
78
|
-
value = value.decode('utf8')
|
79
|
-
except UnicodeDecodeError:
|
80
|
-
raise HTTPBadRequest(
|
81
|
-
request=req, content_type='text/plain',
|
82
|
-
body='"%s" parameter not valid UTF-8' % name)
|
64
|
+
if value:
|
65
|
+
# req.params is a dict of WSGI strings, so encoding will succeed
|
66
|
+
value = value.encode('latin1')
|
67
|
+
try:
|
68
|
+
# Ensure UTF8ness since we're at it
|
69
|
+
value = value.decode('utf8')
|
70
|
+
except UnicodeDecodeError:
|
71
|
+
raise HTTPBadRequest(
|
72
|
+
request=req, content_type='text/plain',
|
73
|
+
body='"%s" parameter not valid UTF-8' % name)
|
83
74
|
return value
|
84
75
|
|
85
76
|
|
77
|
+
def get_valid_part_num(req):
|
78
|
+
"""
|
79
|
+
Any non-range GET or HEAD request for a SLO object may include a
|
80
|
+
part-number parameter in query string. If the passed in request
|
81
|
+
includes a part-number parameter it will be parsed into a valid integer
|
82
|
+
and returned. If the passed in request does not include a part-number
|
83
|
+
param we will return None. If the part-number parameter is invalid for
|
84
|
+
the given request we will raise the appropriate HTTP exception
|
85
|
+
|
86
|
+
:param req: the request object
|
87
|
+
|
88
|
+
:returns: validated part-number value or None
|
89
|
+
:raises HTTPBadRequest: if request or part-number param is not valid
|
90
|
+
"""
|
91
|
+
part_number_param = get_param(req, 'part-number')
|
92
|
+
if part_number_param is None:
|
93
|
+
return None
|
94
|
+
try:
|
95
|
+
part_number = int(part_number_param)
|
96
|
+
if part_number <= 0:
|
97
|
+
raise ValueError
|
98
|
+
except ValueError:
|
99
|
+
raise HTTPBadRequest('Part number must be an integer greater '
|
100
|
+
'than 0')
|
101
|
+
|
102
|
+
if req.range:
|
103
|
+
raise HTTPBadRequest(req=req,
|
104
|
+
body='Range requests are not supported '
|
105
|
+
'with part number queries')
|
106
|
+
|
107
|
+
return part_number
|
108
|
+
|
109
|
+
|
110
|
+
def validate_params(req, names):
|
111
|
+
"""
|
112
|
+
Get list of parameters from an HTTP request, validating the encoding of
|
113
|
+
each parameter.
|
114
|
+
|
115
|
+
:param req: request object
|
116
|
+
:param names: parameter names
|
117
|
+
:returns: a dict mapping parameter names to values for each name that
|
118
|
+
appears in the request parameters
|
119
|
+
:raises HTTPBadRequest: if any parameter value is not a valid UTF-8 byte
|
120
|
+
sequence
|
121
|
+
"""
|
122
|
+
params = {}
|
123
|
+
for name in names:
|
124
|
+
value = get_param(req, name)
|
125
|
+
if value is None:
|
126
|
+
continue
|
127
|
+
params[name] = value
|
128
|
+
return params
|
129
|
+
|
130
|
+
|
131
|
+
def constrain_req_limit(req, constrained_limit):
|
132
|
+
given_limit = get_param(req, 'limit')
|
133
|
+
limit = constrained_limit
|
134
|
+
if given_limit and given_limit.isdigit():
|
135
|
+
limit = int(given_limit)
|
136
|
+
if limit > constrained_limit:
|
137
|
+
raise HTTPPreconditionFailed(
|
138
|
+
request=req, body='Maximum limit is %d' % constrained_limit)
|
139
|
+
return limit
|
140
|
+
|
141
|
+
|
142
|
+
def validate_container_params(req):
|
143
|
+
params = validate_params(req, ('marker', 'end_marker', 'prefix',
|
144
|
+
'delimiter', 'path', 'format', 'reverse',
|
145
|
+
'states', 'includes'))
|
146
|
+
params['limit'] = constrain_req_limit(req, CONTAINER_LISTING_LIMIT)
|
147
|
+
return params
|
148
|
+
|
149
|
+
|
150
|
+
def _validate_internal_name(name, type_='name'):
|
151
|
+
if RESERVED in name and not name.startswith(RESERVED):
|
152
|
+
raise HTTPBadRequest(body='Invalid reserved-namespace %s' % (type_))
|
153
|
+
|
154
|
+
|
155
|
+
def validate_internal_account(account):
|
156
|
+
"""
|
157
|
+
Validate internal account name.
|
158
|
+
|
159
|
+
:raises: HTTPBadRequest
|
160
|
+
"""
|
161
|
+
_validate_internal_name(account, 'account')
|
162
|
+
|
163
|
+
|
164
|
+
def validate_internal_container(account, container):
|
165
|
+
"""
|
166
|
+
Validate internal account and container names.
|
167
|
+
|
168
|
+
:raises: HTTPBadRequest
|
169
|
+
"""
|
170
|
+
if not account:
|
171
|
+
raise ValueError('Account is required')
|
172
|
+
validate_internal_account(account)
|
173
|
+
if container:
|
174
|
+
_validate_internal_name(container, 'container')
|
175
|
+
|
176
|
+
|
177
|
+
def validate_internal_obj(account, container, obj):
|
178
|
+
"""
|
179
|
+
Validate internal account, container and object names.
|
180
|
+
|
181
|
+
:raises: HTTPBadRequest
|
182
|
+
"""
|
183
|
+
if not account:
|
184
|
+
raise ValueError('Account is required')
|
185
|
+
if not container:
|
186
|
+
raise ValueError('Container is required')
|
187
|
+
validate_internal_container(account, container)
|
188
|
+
if obj and not (account.startswith(AUTO_CREATE_ACCOUNT_PREFIX) or
|
189
|
+
account == MISPLACED_OBJECTS_ACCOUNT):
|
190
|
+
_validate_internal_name(obj, 'object')
|
191
|
+
if container.startswith(RESERVED) and not obj.startswith(RESERVED):
|
192
|
+
raise HTTPBadRequest(body='Invalid user-namespace object '
|
193
|
+
'in reserved-namespace container')
|
194
|
+
elif obj.startswith(RESERVED) and not container.startswith(RESERVED):
|
195
|
+
raise HTTPBadRequest(body='Invalid reserved-namespace object '
|
196
|
+
'in user-namespace container')
|
197
|
+
|
198
|
+
|
86
199
|
def get_name_and_placement(request, minsegs=1, maxsegs=None,
|
87
200
|
rest_with_last=False):
|
88
201
|
"""
|
@@ -101,7 +214,7 @@ def get_name_and_placement(request, minsegs=1, maxsegs=None,
|
|
101
214
|
policy = POLICIES.get_by_index(policy_index)
|
102
215
|
if not policy:
|
103
216
|
raise HTTPServiceUnavailable(
|
104
|
-
body=
|
217
|
+
body="No policy with index %s" % policy_index,
|
105
218
|
request=request, content_type='text/plain')
|
106
219
|
results = split_and_validate_path(request, minsegs=minsegs,
|
107
220
|
maxsegs=maxsegs,
|
@@ -273,6 +386,28 @@ def get_container_update_override_key(key):
|
|
273
386
|
return header.title()
|
274
387
|
|
275
388
|
|
389
|
+
def get_reserved_name(*parts):
|
390
|
+
"""
|
391
|
+
Generate a valid reserved name that joins the component parts.
|
392
|
+
|
393
|
+
:returns: a string
|
394
|
+
"""
|
395
|
+
if any(RESERVED in p for p in parts):
|
396
|
+
raise ValueError('Invalid reserved part in components')
|
397
|
+
return RESERVED + RESERVED.join(parts)
|
398
|
+
|
399
|
+
|
400
|
+
def split_reserved_name(name):
|
401
|
+
"""
|
402
|
+
Separate a valid reserved name into the component parts.
|
403
|
+
|
404
|
+
:returns: a list of strings
|
405
|
+
"""
|
406
|
+
if not name.startswith(RESERVED):
|
407
|
+
raise ValueError('Invalid reserved name')
|
408
|
+
return name.split(RESERVED)[1:]
|
409
|
+
|
410
|
+
|
276
411
|
def remove_items(headers, condition):
|
277
412
|
"""
|
278
413
|
Removes items from a dict whose keys satisfy
|
@@ -338,15 +473,17 @@ class SegmentedIterable(object):
|
|
338
473
|
:param app: WSGI application from which segments will come
|
339
474
|
|
340
475
|
:param listing_iter: iterable yielding the object segments to fetch,
|
341
|
-
along with the byte
|
342
|
-
|
476
|
+
along with the byte sub-ranges to fetch. Each yielded item should be a
|
477
|
+
dict with the following keys: ``path`` or ``raw_data``,
|
478
|
+
``first-byte``, ``last-byte``, ``hash`` (optional), ``bytes``
|
479
|
+
(optional).
|
343
480
|
|
344
|
-
If
|
481
|
+
If ``hash`` is None, no MD5 verification will be done.
|
345
482
|
|
346
|
-
If
|
483
|
+
If ``bytes`` is None, no length verification will be done.
|
347
484
|
|
348
|
-
If first-byte and last-byte are None, then the entire object
|
349
|
-
fetched.
|
485
|
+
If ``first-byte`` and ``last-byte`` are None, then the entire object
|
486
|
+
will be fetched.
|
350
487
|
|
351
488
|
:param max_get_time: maximum permitted duration of a GET request (seconds)
|
352
489
|
:param logger: logger object
|
@@ -405,8 +542,8 @@ class SegmentedIterable(object):
|
|
405
542
|
path = quote(seg_path) + '?multipart-manifest=get'
|
406
543
|
seg_req = make_subrequest(
|
407
544
|
self.req.environ, path=path, method='GET',
|
408
|
-
headers={
|
409
|
-
|
545
|
+
headers={h: self.req.headers.get(h)
|
546
|
+
for h in ('x-auth-token', 'x-open-expired')},
|
410
547
|
agent=('%(orig)s ' + self.ua_suffix),
|
411
548
|
swift_source=self.swift_source)
|
412
549
|
|
@@ -448,11 +585,10 @@ class SegmentedIterable(object):
|
|
448
585
|
pending_etag = seg_etag
|
449
586
|
pending_size = seg_size
|
450
587
|
|
451
|
-
except ListingIterError:
|
452
|
-
e_type, e_value, e_traceback = sys.exc_info()
|
588
|
+
except ListingIterError as e:
|
453
589
|
if pending_req:
|
454
590
|
yield pending_req, pending_etag, pending_size
|
455
|
-
|
591
|
+
raise e
|
456
592
|
|
457
593
|
if pending_req:
|
458
594
|
yield pending_req, pending_etag, pending_size
|
@@ -470,12 +606,18 @@ class SegmentedIterable(object):
|
|
470
606
|
seg_req = data_or_req
|
471
607
|
seg_resp = seg_req.get_response(self.app)
|
472
608
|
if not is_success(seg_resp.status_int):
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
'
|
477
|
-
|
478
|
-
|
609
|
+
# Error body should be short
|
610
|
+
body = seg_resp.body.decode('utf8')
|
611
|
+
msg = 'While processing manifest %s, got %d (%s) ' \
|
612
|
+
'while retrieving %s' % (
|
613
|
+
self.name, seg_resp.status_int,
|
614
|
+
body if len(body) <= 60 else body[:57] + '...',
|
615
|
+
seg_req.path)
|
616
|
+
if is_server_error(seg_resp.status_int):
|
617
|
+
self.logger.error(msg)
|
618
|
+
raise HTTPServiceUnavailable(
|
619
|
+
request=seg_req, content_type='text/plain')
|
620
|
+
raise SegmentError(msg)
|
479
621
|
elif ((seg_etag and (seg_resp.etag != seg_etag)) or
|
480
622
|
(seg_size and (seg_resp.content_length != seg_size) and
|
481
623
|
not seg_req.range)):
|
@@ -498,10 +640,11 @@ class SegmentedIterable(object):
|
|
498
640
|
else:
|
499
641
|
self.current_resp = seg_resp
|
500
642
|
|
643
|
+
resp_len = 0
|
501
644
|
seg_hash = None
|
502
645
|
if seg_resp.etag and not seg_req.headers.get('Range'):
|
503
646
|
# Only calculate the MD5 if it we can use it to validate
|
504
|
-
seg_hash =
|
647
|
+
seg_hash = md5(usedforsecurity=False)
|
505
648
|
|
506
649
|
document_iters = maybe_multipart_byteranges_to_document_iters(
|
507
650
|
seg_resp.app_iter,
|
@@ -510,15 +653,26 @@ class SegmentedIterable(object):
|
|
510
653
|
for chunk in itertools.chain.from_iterable(document_iters):
|
511
654
|
if seg_hash:
|
512
655
|
seg_hash.update(chunk)
|
656
|
+
resp_len += len(chunk)
|
513
657
|
yield (seg_req.path, chunk)
|
514
658
|
close_if_possible(seg_resp.app_iter)
|
515
659
|
|
516
|
-
if seg_hash
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
660
|
+
if seg_hash:
|
661
|
+
if resp_len != seg_resp.content_length:
|
662
|
+
raise SegmentError(
|
663
|
+
"Bad response length for %(seg)s as part of %(name)s: "
|
664
|
+
"headers had %(from_headers)s, but response length "
|
665
|
+
"was actually %(actual)s" %
|
666
|
+
{'seg': seg_req.path,
|
667
|
+
'from_headers': seg_resp.content_length,
|
668
|
+
'name': self.name, 'actual': resp_len})
|
669
|
+
if seg_hash.hexdigest() != seg_resp.etag:
|
670
|
+
raise SegmentError(
|
671
|
+
"Bad MD5 checksum for %(seg)s as part of %(name)s: "
|
672
|
+
"headers had %(etag)s, but object MD5 was actually "
|
673
|
+
"%(actual)s" %
|
674
|
+
{'seg': seg_req.path, 'etag': seg_resp.etag,
|
675
|
+
'name': self.name, 'actual': seg_hash.hexdigest()})
|
522
676
|
|
523
677
|
def _byte_counting_iter(self):
|
524
678
|
# Checks that we give the client the right number of bytes. Raises
|
@@ -598,7 +752,10 @@ class SegmentedIterable(object):
|
|
598
752
|
for x in mri:
|
599
753
|
yield x
|
600
754
|
finally:
|
601
|
-
|
755
|
+
# Spliterator and multi_range_iterator can't possibly know we've
|
756
|
+
# consumed the whole of the app_iter, but we want to read/close the
|
757
|
+
# final segment response
|
758
|
+
friendly_close(self.app_iter)
|
602
759
|
|
603
760
|
def validate_first_segment(self):
|
604
761
|
"""
|
@@ -623,7 +780,7 @@ class SegmentedIterable(object):
|
|
623
780
|
if self.peeked_chunk is not None:
|
624
781
|
pc = self.peeked_chunk
|
625
782
|
self.peeked_chunk = None
|
626
|
-
return
|
783
|
+
return CloseableChain([pc], self.app_iter)
|
627
784
|
else:
|
628
785
|
return self.app_iter
|
629
786
|
|
@@ -738,3 +895,113 @@ def resolve_etag_is_at_header(req, metadata):
|
|
738
895
|
alternate_etag = metadata[name]
|
739
896
|
break
|
740
897
|
return alternate_etag
|
898
|
+
|
899
|
+
|
900
|
+
def update_ignore_range_header(req, name):
|
901
|
+
"""
|
902
|
+
Helper function to update an X-Backend-Ignore-Range-If-Metadata-Present
|
903
|
+
header whose value is a list of header names which, if any are present
|
904
|
+
on an object, mean the object server should respond with a 200 instead
|
905
|
+
of a 206 or 416.
|
906
|
+
|
907
|
+
:param req: a swob Request
|
908
|
+
:param name: name of a header which, if found, indicates the proxy will
|
909
|
+
want the whole object
|
910
|
+
"""
|
911
|
+
if ',' in name:
|
912
|
+
# HTTP header names should not have commas but we'll check anyway
|
913
|
+
raise ValueError('Header name must not contain commas')
|
914
|
+
hdr = 'X-Backend-Ignore-Range-If-Metadata-Present'
|
915
|
+
req.headers[hdr] = csv_append(req.headers.get(hdr), name)
|
916
|
+
|
917
|
+
|
918
|
+
def resolve_ignore_range_header(req, metadata):
|
919
|
+
"""
|
920
|
+
Helper function to remove Range header from request if metadata matching
|
921
|
+
the X-Backend-Ignore-Range-If-Metadata-Present header is found.
|
922
|
+
|
923
|
+
:param req: a swob Request
|
924
|
+
:param metadata: dictionary of object metadata
|
925
|
+
"""
|
926
|
+
ignore_range_headers = set(
|
927
|
+
h.strip().lower()
|
928
|
+
for h in req.headers.get(
|
929
|
+
'X-Backend-Ignore-Range-If-Metadata-Present',
|
930
|
+
'').split(','))
|
931
|
+
if ignore_range_headers.intersection(
|
932
|
+
h.lower() for h in metadata):
|
933
|
+
req.headers.pop('Range', None)
|
934
|
+
|
935
|
+
|
936
|
+
def is_use_replication_network(headers=None):
|
937
|
+
"""
|
938
|
+
Determine if replication network should be used.
|
939
|
+
|
940
|
+
:param headers: a dict of headers
|
941
|
+
:return: the value of the ``x-backend-use-replication-network`` item from
|
942
|
+
``headers``. If no ``headers`` are given or the item is not found then
|
943
|
+
False is returned.
|
944
|
+
"""
|
945
|
+
if headers:
|
946
|
+
for h, v in headers.items():
|
947
|
+
if h.lower() == USE_REPLICATION_NETWORK_HEADER:
|
948
|
+
return config_true_value(v)
|
949
|
+
return False
|
950
|
+
|
951
|
+
|
952
|
+
def get_ip_port(node, headers):
|
953
|
+
"""
|
954
|
+
Get the ip address and port that should be used for the given ``node``.
|
955
|
+
The normal ip address and port are returned unless the ``node`` or
|
956
|
+
``headers`` indicate that the replication ip address and port should be
|
957
|
+
used.
|
958
|
+
|
959
|
+
If the ``headers`` dict has an item with key
|
960
|
+
``x-backend-use-replication-network`` and a truthy value then the
|
961
|
+
replication ip address and port are returned. Otherwise if the ``node``
|
962
|
+
dict has an item with key ``use_replication`` and truthy value then the
|
963
|
+
replication ip address and port are returned. Otherwise the normal ip
|
964
|
+
address and port are returned.
|
965
|
+
|
966
|
+
:param node: a dict describing a node
|
967
|
+
:param headers: a dict of headers
|
968
|
+
:return: a tuple of (ip address, port)
|
969
|
+
"""
|
970
|
+
return select_ip_port(
|
971
|
+
node, use_replication=is_use_replication_network(headers))
|
972
|
+
|
973
|
+
|
974
|
+
def is_open_expired(app, req):
|
975
|
+
"""
|
976
|
+
Helper function to check if a request with the header 'x-open-expired'
|
977
|
+
can access an object that has not yet been reaped by the object-expirer
|
978
|
+
based on the allow_open_expired global config.
|
979
|
+
|
980
|
+
:param app: the application instance
|
981
|
+
:param req: request object
|
982
|
+
"""
|
983
|
+
return (config_true_value(app.allow_open_expired) and
|
984
|
+
config_true_value(req.headers.get('x-open-expired')))
|
985
|
+
|
986
|
+
|
987
|
+
def is_backend_open_expired(request):
|
988
|
+
"""
|
989
|
+
Helper function to check if a request has either the headers
|
990
|
+
'x-backend-open-expired' or 'x-backend-replication' for the backend
|
991
|
+
to access expired objects.
|
992
|
+
|
993
|
+
:param request: request object
|
994
|
+
"""
|
995
|
+
x_backend_open_expired = config_true_value(request.headers.get(
|
996
|
+
'x-backend-open-expired', 'false'))
|
997
|
+
x_backend_replication = config_true_value(request.headers.get(
|
998
|
+
'x-backend-replication', 'false'))
|
999
|
+
return x_backend_open_expired or x_backend_replication
|
1000
|
+
|
1001
|
+
|
1002
|
+
def append_log_info(environ, log_info):
|
1003
|
+
environ.setdefault('swift.log_info', []).append(log_info)
|
1004
|
+
|
1005
|
+
|
1006
|
+
def get_log_info(environ):
|
1007
|
+
return ','.join(environ.get('swift.log_info', []))
|
swift/common/ring/builder.py
CHANGED
@@ -22,18 +22,16 @@ import math
|
|
22
22
|
import random
|
23
23
|
import uuid
|
24
24
|
|
25
|
-
import
|
25
|
+
import pickle # nosec: B403
|
26
26
|
from copy import deepcopy
|
27
27
|
from contextlib import contextmanager
|
28
28
|
|
29
29
|
from array import array
|
30
30
|
from collections import defaultdict
|
31
|
-
import six
|
32
|
-
from six.moves import range
|
33
31
|
from time import time
|
34
32
|
|
35
33
|
from swift.common import exceptions
|
36
|
-
from swift.common.ring import RingData
|
34
|
+
from swift.common.ring.ring import RingData
|
37
35
|
from swift.common.ring.utils import tiers_for_dev, build_tier_tree, \
|
38
36
|
validate_and_normalize_address, validate_replicas_by_tier, pretty_dev
|
39
37
|
|
@@ -87,6 +85,9 @@ class RingBuilder(object):
|
|
87
85
|
if part_power > 32:
|
88
86
|
raise ValueError("part_power must be at most 32 (was %d)"
|
89
87
|
% (part_power,))
|
88
|
+
if part_power < 0:
|
89
|
+
raise ValueError("part_power must be at least 0 (was %d)"
|
90
|
+
% (part_power,))
|
90
91
|
if replicas < 1:
|
91
92
|
raise ValueError("replicas must be at least 1 (was %.6f)"
|
92
93
|
% (replicas,))
|
@@ -178,16 +179,16 @@ class RingBuilder(object):
|
|
178
179
|
@contextmanager
|
179
180
|
def debug(self):
|
180
181
|
"""
|
181
|
-
Temporarily enables debug logging, useful in tests, e.g
|
182
|
+
Temporarily enables debug logging, useful in tests, e.g.::
|
182
183
|
|
183
184
|
with rb.debug():
|
184
185
|
rb.rebalance()
|
185
186
|
"""
|
186
|
-
self.logger.disabled = False
|
187
|
+
old_val, self.logger.disabled = self.logger.disabled, False
|
187
188
|
try:
|
188
189
|
yield
|
189
190
|
finally:
|
190
|
-
self.logger.disabled =
|
191
|
+
self.logger.disabled = old_val
|
191
192
|
|
192
193
|
@property
|
193
194
|
def min_part_seconds_left(self):
|
@@ -364,13 +365,15 @@ class RingBuilder(object):
|
|
364
365
|
# shift an unsigned int >I right to obtain the partition for the
|
365
366
|
# int).
|
366
367
|
if not self._replica2part2dev:
|
367
|
-
self._ring = RingData([], devs, self.part_shift
|
368
|
+
self._ring = RingData([], devs, self.part_shift,
|
369
|
+
version=self.version)
|
368
370
|
else:
|
369
371
|
self._ring = \
|
370
372
|
RingData([array('H', p2d) for p2d in
|
371
373
|
self._replica2part2dev],
|
372
374
|
devs, self.part_shift,
|
373
|
-
self.next_part_power
|
375
|
+
self.next_part_power,
|
376
|
+
self.version)
|
374
377
|
return self._ring
|
375
378
|
|
376
379
|
def add_dev(self, dev):
|
@@ -417,11 +420,11 @@ class RingBuilder(object):
|
|
417
420
|
# Add holes to self.devs to ensure self.devs[dev['id']] will be the dev
|
418
421
|
while dev['id'] >= len(self.devs):
|
419
422
|
self.devs.append(None)
|
420
|
-
required_keys = ('ip', 'port', 'weight')
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
423
|
+
required_keys = ('region', 'zone', 'ip', 'port', 'device', 'weight')
|
424
|
+
missing = tuple(key for key in required_keys if key not in dev)
|
425
|
+
if missing:
|
426
|
+
raise ValueError('%r is missing required key(s): %s' % (
|
427
|
+
dev, ', '.join(missing)))
|
425
428
|
dev['weight'] = float(dev['weight'])
|
426
429
|
dev['parts'] = 0
|
427
430
|
dev.setdefault('meta', '')
|
@@ -450,6 +453,46 @@ class RingBuilder(object):
|
|
450
453
|
self.devs_changed = True
|
451
454
|
self.version += 1
|
452
455
|
|
456
|
+
def set_dev_region(self, dev_id, region):
|
457
|
+
"""
|
458
|
+
Set the region of a device. This should be called rather than just
|
459
|
+
altering the region key in the device dict directly, as the builder
|
460
|
+
will need to rebuild some internal state to reflect the change.
|
461
|
+
|
462
|
+
.. note::
|
463
|
+
This will not rebalance the ring immediately as you may want to
|
464
|
+
make multiple changes for a single rebalance.
|
465
|
+
|
466
|
+
:param dev_id: device id
|
467
|
+
:param region: new region for device
|
468
|
+
"""
|
469
|
+
if any(dev_id == d['id'] for d in self._remove_devs):
|
470
|
+
raise ValueError("Can not set region of dev_id %s because it "
|
471
|
+
"is marked for removal" % (dev_id,))
|
472
|
+
self.devs[dev_id]['region'] = region
|
473
|
+
self.devs_changed = True
|
474
|
+
self.version += 1
|
475
|
+
|
476
|
+
def set_dev_zone(self, dev_id, zone):
|
477
|
+
"""
|
478
|
+
Set the zone of a device. This should be called rather than just
|
479
|
+
altering the zone key in the device dict directly, as the builder
|
480
|
+
will need to rebuild some internal state to reflect the change.
|
481
|
+
|
482
|
+
.. note::
|
483
|
+
This will not rebalance the ring immediately as you may want to
|
484
|
+
make multiple changes for a single rebalance.
|
485
|
+
|
486
|
+
:param dev_id: device id
|
487
|
+
:param zone: new zone for device
|
488
|
+
"""
|
489
|
+
if any(dev_id == d['id'] for d in self._remove_devs):
|
490
|
+
raise ValueError("Can not set zone of dev_id %s because it "
|
491
|
+
"is marked for removal" % (dev_id,))
|
492
|
+
self.devs[dev_id]['zone'] = zone
|
493
|
+
self.devs_changed = True
|
494
|
+
self.version += 1
|
495
|
+
|
453
496
|
def remove_dev(self, dev_id):
|
454
497
|
"""
|
455
498
|
Remove a device from the ring.
|
@@ -601,8 +644,7 @@ class RingBuilder(object):
|
|
601
644
|
|
602
645
|
dispersion_graph = {}
|
603
646
|
# go over all the devices holding each replica part by part
|
604
|
-
for part_id, dev_ids in enumerate(
|
605
|
-
six.moves.zip(*self._replica2part2dev)):
|
647
|
+
for part_id, dev_ids in enumerate(zip(*self._replica2part2dev)):
|
606
648
|
# count the number of replicas of this part for each tier of each
|
607
649
|
# device, some devices may have overlapping tiers!
|
608
650
|
replicas_at_tier = defaultdict(int)
|
@@ -1395,17 +1437,17 @@ class RingBuilder(object):
|
|
1395
1437
|
{(): 3.0,
|
1396
1438
|
(1,): 3.0,
|
1397
1439
|
(1, 1): 1.0,
|
1398
|
-
(1, 1, '127.0.0.1:
|
1399
|
-
(1, 1, '127.0.0.1:
|
1440
|
+
(1, 1, '127.0.0.1:6210'): 1.0,
|
1441
|
+
(1, 1, '127.0.0.1:6210', 0): 1.0,
|
1400
1442
|
(1, 2): 1.0,
|
1401
|
-
(1, 2, '127.0.0.1:
|
1402
|
-
(1, 2, '127.0.0.1:
|
1443
|
+
(1, 2, '127.0.0.1:6220'): 1.0,
|
1444
|
+
(1, 2, '127.0.0.1:6220', 1): 1.0,
|
1403
1445
|
(1, 3): 1.0,
|
1404
|
-
(1, 3, '127.0.0.1:
|
1405
|
-
(1, 3, '127.0.0.1:
|
1446
|
+
(1, 3, '127.0.0.1:6230'): 1.0,
|
1447
|
+
(1, 3, '127.0.0.1:6230', 2): 1.0,
|
1406
1448
|
(1, 4): 1.0,
|
1407
|
-
(1, 4, '127.0.0.1:
|
1408
|
-
(1, 4, '127.0.0.1:
|
1449
|
+
(1, 4, '127.0.0.1:6240'): 1.0,
|
1450
|
+
(1, 4, '127.0.0.1:6240', 3): 1.0}
|
1409
1451
|
|
1410
1452
|
"""
|
1411
1453
|
# Used by walk_tree to know what entries to create for each recursive
|
@@ -1696,7 +1738,7 @@ class RingBuilder(object):
|
|
1696
1738
|
else:
|
1697
1739
|
with fp:
|
1698
1740
|
try:
|
1699
|
-
builder = pickle.load(fp)
|
1741
|
+
builder = pickle.load(fp) # nosec: B301
|
1700
1742
|
except Exception:
|
1701
1743
|
# raise error during unpickling as UnPicklingError
|
1702
1744
|
raise exceptions.UnPicklingError(
|