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
@@ -60,32 +60,32 @@ Static Large Object when the multipart upload is completed.
|
|
60
60
|
"""
|
61
61
|
|
62
62
|
import binascii
|
63
|
-
|
63
|
+
import copy
|
64
64
|
import os
|
65
65
|
import re
|
66
66
|
import time
|
67
67
|
|
68
|
-
import
|
68
|
+
from swift.common import constraints
|
69
|
+
from swift.common.swob import Range, bytes_to_wsgi, normalize_etag, \
|
70
|
+
wsgi_to_str
|
71
|
+
from swift.common.utils import json, public, reiterate, md5, Timestamp
|
72
|
+
from swift.common.request_helpers import get_container_update_override_key, \
|
73
|
+
get_param
|
69
74
|
|
70
|
-
from
|
71
|
-
from swift.common.utils import json, public, reiterate
|
72
|
-
from swift.common.db import utf8encode
|
73
|
-
from swift.common.request_helpers import get_container_update_override_key
|
74
|
-
|
75
|
-
from six.moves.urllib.parse import quote, urlparse
|
75
|
+
from urllib.parse import quote, urlparse
|
76
76
|
|
77
77
|
from swift.common.middleware.s3api.controllers.base import Controller, \
|
78
78
|
bucket_operation, object_operation, check_container_existence
|
79
79
|
from swift.common.middleware.s3api.s3response import InvalidArgument, \
|
80
|
-
ErrorResponse, MalformedXML, BadDigest, \
|
80
|
+
ErrorResponse, MalformedXML, BadDigest, KeyTooLongError, \
|
81
81
|
InvalidPart, BucketAlreadyExists, EntityTooSmall, InvalidPartOrder, \
|
82
82
|
InvalidRequest, HTTPOk, HTTPNoContent, NoSuchKey, NoSuchUpload, \
|
83
|
-
NoSuchBucket, BucketAlreadyOwnedByYou
|
84
|
-
from swift.common.middleware.s3api.exception import BadSwiftRequest
|
83
|
+
NoSuchBucket, BucketAlreadyOwnedByYou, ServiceUnavailable
|
85
84
|
from swift.common.middleware.s3api.utils import unique_id, \
|
86
85
|
MULTIUPLOAD_SUFFIX, S3Timestamp, sysmeta_header
|
87
86
|
from swift.common.middleware.s3api.etree import Element, SubElement, \
|
88
87
|
fromstring, tostring, XMLSyntaxError, DocumentInvalid
|
88
|
+
from swift.common.storage_policy import POLICIES
|
89
89
|
|
90
90
|
DEFAULT_MAX_PARTS_LISTING = 1000
|
91
91
|
DEFAULT_MAX_UPLOADS = 1000
|
@@ -94,19 +94,82 @@ MAX_COMPLETE_UPLOAD_BODY_SIZE = 2048 * 1024
|
|
94
94
|
|
95
95
|
|
96
96
|
def _get_upload_info(req, app, upload_id):
|
97
|
+
"""
|
98
|
+
Make a HEAD request for existing upload object metadata. Tries the upload
|
99
|
+
marker first, and then falls back to the manifest object.
|
100
|
+
|
101
|
+
:param req: an S3Request object.
|
102
|
+
:param app: the wsgi app.
|
103
|
+
:param upload_id: the upload id.
|
104
|
+
:returns: a tuple of (S3Response, boolean) where the boolean is True if the
|
105
|
+
response is from the upload marker and False otherwise.
|
106
|
+
:raises: NoSuchUpload if neither the marker nor the manifest were found.
|
107
|
+
"""
|
97
108
|
|
98
109
|
container = req.container_name + MULTIUPLOAD_SUFFIX
|
99
110
|
obj = '%s/%s' % (req.object_name, upload_id)
|
100
111
|
|
112
|
+
# XXX: if we leave the copy-source header, somewhere later we might
|
113
|
+
# drop in a ?version-id=... query string that's utterly inappropriate
|
114
|
+
# for the upload marker. Until we get around to fixing that, just pop
|
115
|
+
# it off for now...
|
116
|
+
copy_source = req.headers.pop('X-Amz-Copy-Source', None)
|
101
117
|
try:
|
102
|
-
|
118
|
+
resp = req.get_response(app, 'HEAD', container=container, obj=obj)
|
119
|
+
return resp, True
|
103
120
|
except NoSuchKey:
|
121
|
+
# ensure consistent path and policy are logged despite manifest HEAD
|
122
|
+
upload_marker_path = req.environ.get('s3api.backend_path')
|
123
|
+
policy_index = req.policy_index
|
124
|
+
try:
|
125
|
+
resp = req.get_response(app, 'HEAD')
|
126
|
+
if resp.sysmeta_headers.get(sysmeta_header(
|
127
|
+
'object', 'upload-id')) == upload_id:
|
128
|
+
return resp, False
|
129
|
+
except NoSuchKey:
|
130
|
+
pass
|
131
|
+
finally:
|
132
|
+
# Ops often find it more useful for us to log the upload marker
|
133
|
+
# path, so put it back
|
134
|
+
if upload_marker_path is not None:
|
135
|
+
req.environ['s3api.backend_path'] = upload_marker_path
|
136
|
+
if policy_index is not None:
|
137
|
+
req.policy_index = policy_index
|
104
138
|
raise NoSuchUpload(upload_id=upload_id)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
139
|
+
finally:
|
140
|
+
# ...making sure to restore any copy-source before returning
|
141
|
+
if copy_source is not None:
|
142
|
+
req.headers['X-Amz-Copy-Source'] = copy_source
|
143
|
+
|
144
|
+
|
145
|
+
def _make_complete_body(req, s3_etag, yielded_anything):
|
146
|
+
result_elem = Element('CompleteMultipartUploadResult')
|
147
|
+
|
148
|
+
# NOTE: boto with sig v4 appends port to HTTP_HOST value at
|
149
|
+
# the request header when the port is non default value and it
|
150
|
+
# makes req.host_url like as http://localhost:8080:8080/path
|
151
|
+
# that obviously invalid. Probably it should be resolved at
|
152
|
+
# swift.common.swob though, tentatively we are parsing and
|
153
|
+
# reconstructing the correct host_url info here.
|
154
|
+
# in detail, https://github.com/boto/boto/pull/3513
|
155
|
+
parsed_url = urlparse(req.host_url)
|
156
|
+
host_url = '%s://%s' % (parsed_url.scheme, parsed_url.hostname)
|
157
|
+
# Why are we doing our own port parsing? Because py3 decided
|
158
|
+
# to start raising ValueErrors on access after parsing such
|
159
|
+
# an invalid port
|
160
|
+
netloc = parsed_url.netloc.split('@')[-1].split(']')[-1]
|
161
|
+
if ':' in netloc:
|
162
|
+
port = netloc.split(':', 2)[1]
|
163
|
+
host_url += ':%s' % port
|
164
|
+
|
165
|
+
SubElement(result_elem, 'Location').text = host_url + req.path
|
166
|
+
SubElement(result_elem, 'Bucket').text = req.container_name
|
167
|
+
SubElement(result_elem, 'Key').text = wsgi_to_str(req.object_name)
|
168
|
+
SubElement(result_elem, 'ETag').text = '"%s"' % s3_etag
|
169
|
+
body = tostring(result_elem, xml_declaration=not yielded_anything)
|
170
|
+
if yielded_anything:
|
171
|
+
return b'\n' + body
|
172
|
+
return body
|
110
173
|
|
111
174
|
|
112
175
|
class PartController(Controller):
|
@@ -130,18 +193,10 @@ class PartController(Controller):
|
|
130
193
|
raise InvalidArgument('ResourceType', 'partNumber',
|
131
194
|
'Unexpected query string parameter')
|
132
195
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
except Exception:
|
138
|
-
err_msg = 'Part number must be an integer between 1 and %d,' \
|
139
|
-
' inclusive' % self.conf.max_upload_part_num
|
140
|
-
raise InvalidArgument('partNumber', req.params['partNumber'],
|
141
|
-
err_msg)
|
142
|
-
|
143
|
-
upload_id = req.params['uploadId']
|
144
|
-
_check_upload_info(req, self.app, upload_id)
|
196
|
+
part_number = req.validate_part_number()
|
197
|
+
|
198
|
+
upload_id = get_param(req, 'uploadId')
|
199
|
+
_get_upload_info(req, self.app, upload_id)
|
145
200
|
|
146
201
|
req.container_name += MULTIUPLOAD_SUFFIX
|
147
202
|
req.object_name = '%s/%s/%d' % (req.object_name, upload_id,
|
@@ -235,8 +290,6 @@ class UploadsController(Controller):
|
|
235
290
|
|
236
291
|
:return (non_delimited_uploads, common_prefixes)
|
237
292
|
"""
|
238
|
-
if six.PY2:
|
239
|
-
(prefix, delimiter) = utf8encode(prefix, delimiter)
|
240
293
|
non_delimited_uploads = []
|
241
294
|
common_prefixes = set()
|
242
295
|
for upload in uploads:
|
@@ -249,19 +302,19 @@ class UploadsController(Controller):
|
|
249
302
|
non_delimited_uploads.append(upload)
|
250
303
|
return non_delimited_uploads, sorted(common_prefixes)
|
251
304
|
|
252
|
-
encoding_type = req
|
305
|
+
encoding_type = get_param(req, 'encoding-type')
|
253
306
|
if encoding_type is not None and encoding_type != 'url':
|
254
307
|
err_msg = 'Invalid Encoding Method specified in Request'
|
255
308
|
raise InvalidArgument('encoding-type', encoding_type, err_msg)
|
256
309
|
|
257
|
-
keymarker = req
|
258
|
-
uploadid = req
|
310
|
+
keymarker = get_param(req, 'key-marker', '')
|
311
|
+
uploadid = get_param(req, 'upload-id-marker', '')
|
259
312
|
maxuploads = req.get_validated_param(
|
260
313
|
'max-uploads', DEFAULT_MAX_UPLOADS, DEFAULT_MAX_UPLOADS)
|
261
314
|
|
262
315
|
query = {
|
263
316
|
'format': 'json',
|
264
|
-
'
|
317
|
+
'marker': '',
|
265
318
|
}
|
266
319
|
|
267
320
|
if uploadid and keymarker:
|
@@ -269,15 +322,11 @@ class UploadsController(Controller):
|
|
269
322
|
elif keymarker:
|
270
323
|
query.update({'marker': '%s/~' % (keymarker)})
|
271
324
|
if 'prefix' in req.params:
|
272
|
-
query.update({'prefix': req
|
325
|
+
query.update({'prefix': get_param(req, 'prefix')})
|
273
326
|
|
274
327
|
container = req.container_name + MULTIUPLOAD_SUFFIX
|
275
|
-
|
276
|
-
|
277
|
-
objects = json.loads(resp.body)
|
278
|
-
except NoSuchBucket:
|
279
|
-
# Assume NoSuchBucket as no uploads
|
280
|
-
objects = []
|
328
|
+
uploads = []
|
329
|
+
prefixes = []
|
281
330
|
|
282
331
|
def object_to_upload(object_info):
|
283
332
|
obj, upid = object_info['name'].rsplit('/', 1)
|
@@ -286,24 +335,34 @@ class UploadsController(Controller):
|
|
286
335
|
'last_modified': object_info['last_modified']}
|
287
336
|
return obj_dict
|
288
337
|
|
289
|
-
|
290
|
-
# Note that pattern matcher will drop whole segments objects like as
|
291
|
-
# object_name/upload_id/1.
|
292
|
-
pattern = re.compile('/[0-9]+$')
|
293
|
-
uploads = [object_to_upload(obj) for obj in objects if
|
294
|
-
pattern.search(obj.get('name', '')) is None]
|
295
|
-
|
296
|
-
prefixes = []
|
297
|
-
if 'delimiter' in req.params:
|
298
|
-
prefix = req.params.get('prefix', '')
|
299
|
-
delimiter = req.params['delimiter']
|
300
|
-
uploads, prefixes = separate_uploads(uploads, prefix, delimiter)
|
338
|
+
is_segment = re.compile('.*/[0-9]+$')
|
301
339
|
|
340
|
+
while len(uploads) < maxuploads:
|
341
|
+
try:
|
342
|
+
resp = req.get_response(self.app, container=container,
|
343
|
+
query=query)
|
344
|
+
objects = json.loads(resp.body)
|
345
|
+
except NoSuchBucket:
|
346
|
+
# Assume NoSuchBucket as no uploads
|
347
|
+
objects = []
|
348
|
+
if not objects:
|
349
|
+
break
|
350
|
+
|
351
|
+
new_uploads = [object_to_upload(obj) for obj in objects
|
352
|
+
if not is_segment.match(obj.get('name', ''))]
|
353
|
+
new_prefixes = []
|
354
|
+
if 'delimiter' in req.params:
|
355
|
+
prefix = get_param(req, 'prefix', '')
|
356
|
+
delimiter = get_param(req, 'delimiter')
|
357
|
+
new_uploads, new_prefixes = separate_uploads(
|
358
|
+
new_uploads, prefix, delimiter)
|
359
|
+
uploads.extend(new_uploads)
|
360
|
+
prefixes.extend(new_prefixes)
|
361
|
+
query['marker'] = objects[-1]['name']
|
362
|
+
|
363
|
+
truncated = len(uploads) >= maxuploads
|
302
364
|
if len(uploads) > maxuploads:
|
303
365
|
uploads = uploads[:maxuploads]
|
304
|
-
truncated = True
|
305
|
-
else:
|
306
|
-
truncated = False
|
307
366
|
|
308
367
|
nextkeymarker = ''
|
309
368
|
nextuploadmarker = ''
|
@@ -318,9 +377,10 @@ class UploadsController(Controller):
|
|
318
377
|
SubElement(result_elem, 'NextKeyMarker').text = nextkeymarker
|
319
378
|
SubElement(result_elem, 'NextUploadIdMarker').text = nextuploadmarker
|
320
379
|
if 'delimiter' in req.params:
|
321
|
-
SubElement(result_elem, 'Delimiter').text =
|
380
|
+
SubElement(result_elem, 'Delimiter').text = \
|
381
|
+
get_param(req, 'delimiter')
|
322
382
|
if 'prefix' in req.params:
|
323
|
-
SubElement(result_elem, 'Prefix').text = req
|
383
|
+
SubElement(result_elem, 'Prefix').text = get_param(req, 'prefix')
|
324
384
|
SubElement(result_elem, 'MaxUploads').text = str(maxuploads)
|
325
385
|
if encoding_type is not None:
|
326
386
|
SubElement(result_elem, 'EncodingType').text = encoding_type
|
@@ -344,7 +404,7 @@ class UploadsController(Controller):
|
|
344
404
|
SubElement(owner_elem, 'DisplayName').text = req.user_id
|
345
405
|
SubElement(upload_elem, 'StorageClass').text = 'STANDARD'
|
346
406
|
SubElement(upload_elem, 'Initiated').text = \
|
347
|
-
u['last_modified']
|
407
|
+
S3Timestamp.from_isoformat(u['last_modified']).s3xmlformat
|
348
408
|
|
349
409
|
for p in prefixes:
|
350
410
|
elem = SubElement(result_elem, 'CommonPrefixes')
|
@@ -361,11 +421,15 @@ class UploadsController(Controller):
|
|
361
421
|
"""
|
362
422
|
Handles Initiate Multipart Upload.
|
363
423
|
"""
|
424
|
+
if len(req.object_name) > constraints.MAX_OBJECT_NAME_LENGTH:
|
425
|
+
# Note that we can still run into trouble where the MPU is just
|
426
|
+
# within the limit, which means the segment names will go over
|
427
|
+
raise KeyTooLongError()
|
428
|
+
|
364
429
|
# Create a unique S3 upload id from UUID to avoid duplicates.
|
365
430
|
upload_id = unique_id()
|
366
431
|
|
367
|
-
|
368
|
-
seg_container = orig_container + MULTIUPLOAD_SUFFIX
|
432
|
+
seg_container = req.container_name + MULTIUPLOAD_SUFFIX
|
369
433
|
content_type = req.headers.get('Content-Type')
|
370
434
|
if content_type:
|
371
435
|
req.headers[sysmeta_header('object', 'has-content-type')] = 'yes'
|
@@ -376,15 +440,25 @@ class UploadsController(Controller):
|
|
376
440
|
req.headers['Content-Type'] = 'application/directory'
|
377
441
|
|
378
442
|
try:
|
379
|
-
|
380
|
-
|
443
|
+
seg_req = copy.copy(req)
|
444
|
+
seg_req.environ = copy.copy(req.environ)
|
445
|
+
seg_req.container_name = seg_container
|
446
|
+
seg_req.get_container_info(self.app)
|
381
447
|
except NoSuchBucket:
|
382
448
|
try:
|
383
|
-
|
449
|
+
# multi-upload bucket doesn't exist, create one with
|
450
|
+
# same storage policy and acls as the primary bucket
|
451
|
+
info = req.get_container_info(self.app)
|
452
|
+
policy_name = POLICIES[info['storage_policy']].name
|
453
|
+
hdrs = {'X-Storage-Policy': policy_name}
|
454
|
+
if info.get('read_acl'):
|
455
|
+
hdrs['X-Container-Read'] = info['read_acl']
|
456
|
+
if info.get('write_acl'):
|
457
|
+
hdrs['X-Container-Write'] = info['write_acl']
|
458
|
+
seg_req.get_response(self.app, 'PUT', seg_container, '',
|
459
|
+
headers=hdrs)
|
384
460
|
except (BucketAlreadyExists, BucketAlreadyOwnedByYou):
|
385
461
|
pass
|
386
|
-
finally:
|
387
|
-
req.container_name = orig_container
|
388
462
|
|
389
463
|
obj = '%s/%s' % (req.object_name, upload_id)
|
390
464
|
|
@@ -395,7 +469,7 @@ class UploadsController(Controller):
|
|
395
469
|
|
396
470
|
result_elem = Element('InitiateMultipartUploadResult')
|
397
471
|
SubElement(result_elem, 'Bucket').text = req.container_name
|
398
|
-
SubElement(result_elem, 'Key').text = req.object_name
|
472
|
+
SubElement(result_elem, 'Key').text = wsgi_to_str(req.object_name)
|
399
473
|
SubElement(result_elem, 'UploadId').text = upload_id
|
400
474
|
|
401
475
|
body = tostring(result_elem)
|
@@ -427,13 +501,13 @@ class UploadController(Controller):
|
|
427
501
|
except ValueError:
|
428
502
|
return False
|
429
503
|
|
430
|
-
encoding_type = req
|
504
|
+
encoding_type = get_param(req, 'encoding-type')
|
431
505
|
if encoding_type is not None and encoding_type != 'url':
|
432
506
|
err_msg = 'Invalid Encoding Method specified in Request'
|
433
507
|
raise InvalidArgument('encoding-type', encoding_type, err_msg)
|
434
508
|
|
435
|
-
upload_id = req
|
436
|
-
|
509
|
+
upload_id = get_param(req, 'uploadId')
|
510
|
+
_get_upload_info(req, self.app, upload_id)
|
437
511
|
|
438
512
|
maxparts = req.get_validated_param(
|
439
513
|
'max-parts', DEFAULT_MAX_PARTS_LISTING,
|
@@ -441,17 +515,26 @@ class UploadController(Controller):
|
|
441
515
|
part_num_marker = req.get_validated_param(
|
442
516
|
'part-number-marker', 0)
|
443
517
|
|
518
|
+
object_name = wsgi_to_str(req.object_name)
|
444
519
|
query = {
|
445
520
|
'format': 'json',
|
446
|
-
'
|
447
|
-
'
|
448
|
-
'
|
521
|
+
'prefix': '%s/%s/' % (object_name, upload_id),
|
522
|
+
'delimiter': '/',
|
523
|
+
'marker': '',
|
449
524
|
}
|
450
525
|
|
451
526
|
container = req.container_name + MULTIUPLOAD_SUFFIX
|
452
|
-
|
453
|
-
|
454
|
-
objects =
|
527
|
+
# Because the parts are out of order in Swift, we list up to the
|
528
|
+
# maximum number of parts and then apply the marker and limit options.
|
529
|
+
objects = []
|
530
|
+
while True:
|
531
|
+
resp = req.get_response(self.app, container=container, obj='',
|
532
|
+
query=query)
|
533
|
+
new_objects = json.loads(resp.body)
|
534
|
+
if not new_objects:
|
535
|
+
break
|
536
|
+
objects.extend(new_objects)
|
537
|
+
query['marker'] = new_objects[-1]['name']
|
455
538
|
|
456
539
|
last_part = 0
|
457
540
|
|
@@ -477,10 +560,9 @@ class UploadController(Controller):
|
|
477
560
|
|
478
561
|
result_elem = Element('ListPartsResult')
|
479
562
|
SubElement(result_elem, 'Bucket').text = req.container_name
|
480
|
-
name = req.object_name
|
481
563
|
if encoding_type == 'url':
|
482
|
-
|
483
|
-
SubElement(result_elem, 'Key').text =
|
564
|
+
object_name = quote(object_name)
|
565
|
+
SubElement(result_elem, 'Key').text = object_name
|
484
566
|
SubElement(result_elem, 'UploadId').text = upload_id
|
485
567
|
|
486
568
|
initiator_elem = SubElement(result_elem, 'Initiator')
|
@@ -496,7 +578,7 @@ class UploadController(Controller):
|
|
496
578
|
SubElement(result_elem, 'MaxParts').text = str(maxparts)
|
497
579
|
if 'encoding-type' in req.params:
|
498
580
|
SubElement(result_elem, 'EncodingType').text = \
|
499
|
-
req
|
581
|
+
get_param(req, 'encoding-type')
|
500
582
|
SubElement(result_elem, 'IsTruncated').text = \
|
501
583
|
'true' if truncated else 'false'
|
502
584
|
|
@@ -504,7 +586,7 @@ class UploadController(Controller):
|
|
504
586
|
part_elem = SubElement(result_elem, 'Part')
|
505
587
|
SubElement(part_elem, 'PartNumber').text = i['name'].split('/')[-1]
|
506
588
|
SubElement(part_elem, 'LastModified').text = \
|
507
|
-
i['last_modified']
|
589
|
+
S3Timestamp.from_isoformat(i['last_modified']).s3xmlformat
|
508
590
|
SubElement(part_elem, 'ETag').text = '"%s"' % i['hash']
|
509
591
|
SubElement(part_elem, 'Size').text = str(i['bytes'])
|
510
592
|
|
@@ -519,8 +601,8 @@ class UploadController(Controller):
|
|
519
601
|
"""
|
520
602
|
Handles Abort Multipart Upload.
|
521
603
|
"""
|
522
|
-
upload_id = req
|
523
|
-
|
604
|
+
upload_id = get_param(req, 'uploadId')
|
605
|
+
_get_upload_info(req, self.app, upload_id)
|
524
606
|
|
525
607
|
# First check to see if this multi-part upload was already
|
526
608
|
# completed. Look in the primary container, if the object exists,
|
@@ -533,9 +615,10 @@ class UploadController(Controller):
|
|
533
615
|
# must be a multipart upload abort.
|
534
616
|
# We must delete any uploaded segments for this UploadID and then
|
535
617
|
# delete the object in the main container as well
|
618
|
+
object_name = wsgi_to_str(req.object_name)
|
536
619
|
query = {
|
537
620
|
'format': 'json',
|
538
|
-
'prefix': '%s/%s/' % (
|
621
|
+
'prefix': '%s/%s/' % (object_name, upload_id),
|
539
622
|
'delimiter': '/',
|
540
623
|
}
|
541
624
|
|
@@ -543,10 +626,15 @@ class UploadController(Controller):
|
|
543
626
|
|
544
627
|
# Iterate over the segment objects and delete them individually
|
545
628
|
objects = json.loads(resp.body)
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
629
|
+
while objects:
|
630
|
+
for o in objects:
|
631
|
+
container = req.container_name + MULTIUPLOAD_SUFFIX
|
632
|
+
obj = bytes_to_wsgi(o['name'].encode('utf-8'))
|
633
|
+
req.get_response(self.app, container=container, obj=obj)
|
634
|
+
query['marker'] = objects[-1]['name']
|
635
|
+
resp = req.get_response(self.app, 'GET', container, '',
|
636
|
+
query=query)
|
637
|
+
objects = json.loads(resp.body)
|
550
638
|
|
551
639
|
return HTTPNoContent()
|
552
640
|
|
@@ -557,13 +645,24 @@ class UploadController(Controller):
|
|
557
645
|
"""
|
558
646
|
Handles Complete Multipart Upload.
|
559
647
|
"""
|
560
|
-
upload_id = req
|
561
|
-
resp = _get_upload_info(req, self.app, upload_id)
|
562
|
-
|
648
|
+
upload_id = get_param(req, 'uploadId')
|
649
|
+
resp, is_marker = _get_upload_info(req, self.app, upload_id)
|
650
|
+
if (is_marker and
|
651
|
+
resp.sw_headers.get('X-Backend-Timestamp') >= Timestamp.now()):
|
652
|
+
# Somehow the marker was created in the future w.r.t. this thread's
|
653
|
+
# clock. The manifest PUT may succeed but the subsequent marker
|
654
|
+
# DELETE will fail, so don't attempt either.
|
655
|
+
raise ServiceUnavailable
|
656
|
+
|
657
|
+
headers = {'Accept': 'application/json',
|
658
|
+
sysmeta_header('object', 'upload-id'): upload_id}
|
563
659
|
for key, val in resp.headers.items():
|
564
660
|
_key = key.lower()
|
565
661
|
if _key.startswith('x-amz-meta-'):
|
566
662
|
headers['x-object-meta-' + _key[11:]] = val
|
663
|
+
elif _key in ('content-encoding', 'content-language',
|
664
|
+
'content-disposition', 'expires', 'cache-control'):
|
665
|
+
headers[key] = val
|
567
666
|
|
568
667
|
hct_header = sysmeta_header('object', 'has-content-type')
|
569
668
|
if resp.sysmeta_headers.get(hct_header) == 'yes':
|
@@ -582,7 +681,7 @@ class UploadController(Controller):
|
|
582
681
|
headers['Content-Type'] = content_type
|
583
682
|
|
584
683
|
container = req.container_name + MULTIUPLOAD_SUFFIX
|
585
|
-
s3_etag_hasher = md5()
|
684
|
+
s3_etag_hasher = md5(usedforsecurity=False)
|
586
685
|
manifest = []
|
587
686
|
previous_number = 0
|
588
687
|
try:
|
@@ -592,7 +691,8 @@ class UploadController(Controller):
|
|
592
691
|
if 'content-md5' in req.headers:
|
593
692
|
# If an MD5 was provided, we need to verify it.
|
594
693
|
# Note that S3Request already took care of translating to ETag
|
595
|
-
if req.headers['etag'] != md5(
|
694
|
+
if req.headers['etag'] != md5(
|
695
|
+
xml, usedforsecurity=False).hexdigest():
|
596
696
|
raise BadDigest(content_md5=req.headers['content-md5'])
|
597
697
|
# We're only interested in the body here, in the
|
598
698
|
# multipart-upload controller -- *don't* let it get
|
@@ -608,18 +708,20 @@ class UploadController(Controller):
|
|
608
708
|
raise InvalidPartOrder(upload_id=upload_id)
|
609
709
|
previous_number = part_number
|
610
710
|
|
611
|
-
etag = part_elem.find('./ETag').text
|
612
|
-
if
|
613
|
-
|
614
|
-
|
711
|
+
etag = normalize_etag(part_elem.find('./ETag').text)
|
712
|
+
if etag is None:
|
713
|
+
raise InvalidPart(upload_id=upload_id,
|
714
|
+
part_number=part_number,
|
715
|
+
e_tag=etag)
|
615
716
|
if len(etag) != 32 or any(c not in '0123456789abcdef'
|
616
717
|
for c in etag):
|
617
718
|
raise InvalidPart(upload_id=upload_id,
|
618
|
-
part_number=part_number
|
619
|
-
|
719
|
+
part_number=part_number,
|
720
|
+
e_tag=etag)
|
620
721
|
manifest.append({
|
621
722
|
'path': '/%s/%s/%s/%d' % (
|
622
|
-
container, req.object_name
|
723
|
+
wsgi_to_str(container), wsgi_to_str(req.object_name),
|
724
|
+
upload_id, part_number),
|
623
725
|
'etag': etag})
|
624
726
|
s3_etag_hasher.update(binascii.a2b_hex(etag))
|
625
727
|
except (XMLSyntaxError, DocumentInvalid):
|
@@ -632,7 +734,15 @@ class UploadController(Controller):
|
|
632
734
|
raise
|
633
735
|
|
634
736
|
s3_etag = '%s-%d' % (s3_etag_hasher.hexdigest(), len(manifest))
|
635
|
-
|
737
|
+
s3_etag_header = sysmeta_header('object', 'etag')
|
738
|
+
if resp.sysmeta_headers.get(s3_etag_header) == s3_etag:
|
739
|
+
# This header should only already be present if the upload marker
|
740
|
+
# has been cleaned up and the current target uses the same
|
741
|
+
# upload-id; assuming the segments to use haven't changed, the work
|
742
|
+
# is already done
|
743
|
+
return HTTPOk(body=_make_complete_body(req, s3_etag, False),
|
744
|
+
content_type='application/xml')
|
745
|
+
headers[s3_etag_header] = s3_etag
|
636
746
|
# Leave base header value blank; SLO will populate
|
637
747
|
c_etag = '; s3_etag=%s' % s3_etag
|
638
748
|
headers[get_container_update_override_key('etag')] = c_etag
|
@@ -696,8 +806,8 @@ class UploadController(Controller):
|
|
696
806
|
status=body['Response Status'],
|
697
807
|
msg='\n'.join(': '.join(err)
|
698
808
|
for err in body['Errors']))
|
699
|
-
except
|
700
|
-
msg =
|
809
|
+
except InvalidRequest as err_resp:
|
810
|
+
msg = err_resp._msg
|
701
811
|
if too_small_message in msg:
|
702
812
|
raise EntityTooSmall(msg)
|
703
813
|
elif ', Etag Mismatch' in msg:
|
@@ -712,37 +822,13 @@ class UploadController(Controller):
|
|
712
822
|
try:
|
713
823
|
req.get_response(self.app, 'DELETE', container, obj)
|
714
824
|
except NoSuchKey:
|
715
|
-
#
|
825
|
+
# The important thing is that we wrote out a tombstone to
|
826
|
+
# make sure the marker got cleaned up. If it's already
|
827
|
+
# gone (e.g., because of concurrent completes or a retried
|
828
|
+
# complete), so much the better.
|
716
829
|
pass
|
717
830
|
|
718
|
-
|
719
|
-
|
720
|
-
# NOTE: boto with sig v4 appends port to HTTP_HOST value at
|
721
|
-
# the request header when the port is non default value and it
|
722
|
-
# makes req.host_url like as http://localhost:8080:8080/path
|
723
|
-
# that obviously invalid. Probably it should be resolved at
|
724
|
-
# swift.common.swob though, tentatively we are parsing and
|
725
|
-
# reconstructing the correct host_url info here.
|
726
|
-
# in detail, https://github.com/boto/boto/pull/3513
|
727
|
-
parsed_url = urlparse(req.host_url)
|
728
|
-
host_url = '%s://%s' % (parsed_url.scheme, parsed_url.hostname)
|
729
|
-
# Why are we doing our own port parsing? Because py3 decided
|
730
|
-
# to start raising ValueErrors on access after parsing such
|
731
|
-
# an invalid port
|
732
|
-
netloc = parsed_url.netloc.split('@')[-1].split(']')[-1]
|
733
|
-
if ':' in netloc:
|
734
|
-
port = netloc.split(':', 2)[1]
|
735
|
-
host_url += ':%s' % port
|
736
|
-
|
737
|
-
SubElement(result_elem, 'Location').text = host_url + req.path
|
738
|
-
SubElement(result_elem, 'Bucket').text = req.container_name
|
739
|
-
SubElement(result_elem, 'Key').text = req.object_name
|
740
|
-
SubElement(result_elem, 'ETag').text = '"%s"' % s3_etag
|
741
|
-
resp.headers.pop('ETag', None)
|
742
|
-
if yielded_anything:
|
743
|
-
yield b'\n'
|
744
|
-
yield tostring(result_elem,
|
745
|
-
xml_declaration=not yielded_anything)
|
831
|
+
yield _make_complete_body(req, s3_etag, yielded_anything)
|
746
832
|
except ErrorResponse as err_resp:
|
747
833
|
if yielded_anything:
|
748
834
|
err_resp.xml_declaration = False
|