swift 2.33.0__py2.py3-none-any.whl → 2.34.1__py2.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.
Files changed (113) hide show
  1. swift/account/auditor.py +11 -0
  2. swift/account/reaper.py +11 -1
  3. swift/account/replicator.py +22 -0
  4. swift/account/server.py +12 -1
  5. swift-2.33.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
  6. swift-2.33.0.data/scripts/swift-config → swift/cli/config.py +1 -1
  7. swift-2.33.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
  8. swift-2.33.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
  9. swift-2.33.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
  10. swift/cli/info.py +103 -2
  11. swift-2.33.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
  12. swift-2.33.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
  13. swift/cli/recon_cron.py +5 -5
  14. swift-2.33.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  15. swift/cli/relinker.py +1 -1
  16. swift/cli/ringbuilder.py +24 -0
  17. swift/common/db.py +2 -1
  18. swift/common/db_auditor.py +2 -2
  19. swift/common/db_replicator.py +6 -0
  20. swift/common/exceptions.py +12 -0
  21. swift/common/manager.py +102 -0
  22. swift/common/memcached.py +6 -13
  23. swift/common/middleware/account_quotas.py +144 -43
  24. swift/common/middleware/backend_ratelimit.py +166 -24
  25. swift/common/middleware/catch_errors.py +1 -3
  26. swift/common/middleware/cname_lookup.py +3 -5
  27. swift/common/middleware/container_sync.py +6 -10
  28. swift/common/middleware/crypto/crypto_utils.py +4 -5
  29. swift/common/middleware/crypto/decrypter.py +4 -5
  30. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  31. swift/common/middleware/listing_formats.py +26 -38
  32. swift/common/middleware/proxy_logging.py +22 -16
  33. swift/common/middleware/ratelimit.py +6 -7
  34. swift/common/middleware/recon.py +6 -7
  35. swift/common/middleware/s3api/acl_handlers.py +9 -0
  36. swift/common/middleware/s3api/controllers/multi_upload.py +1 -9
  37. swift/common/middleware/s3api/controllers/obj.py +20 -1
  38. swift/common/middleware/s3api/s3api.py +2 -0
  39. swift/common/middleware/s3api/s3request.py +171 -62
  40. swift/common/middleware/s3api/s3response.py +35 -6
  41. swift/common/middleware/s3api/s3token.py +2 -2
  42. swift/common/middleware/s3api/utils.py +1 -0
  43. swift/common/middleware/slo.py +153 -52
  44. swift/common/middleware/tempauth.py +6 -4
  45. swift/common/middleware/tempurl.py +2 -2
  46. swift/common/middleware/x_profile/exceptions.py +1 -4
  47. swift/common/middleware/x_profile/html_viewer.py +10 -11
  48. swift/common/middleware/x_profile/profile_model.py +1 -2
  49. swift/common/middleware/xprofile.py +6 -2
  50. swift/common/request_helpers.py +69 -0
  51. swift/common/statsd_client.py +207 -0
  52. swift/common/utils/__init__.py +97 -1635
  53. swift/common/utils/base.py +138 -0
  54. swift/common/utils/config.py +443 -0
  55. swift/common/utils/logs.py +999 -0
  56. swift/common/wsgi.py +11 -3
  57. swift/container/auditor.py +11 -0
  58. swift/container/backend.py +10 -10
  59. swift/container/reconciler.py +11 -2
  60. swift/container/replicator.py +22 -1
  61. swift/container/server.py +12 -1
  62. swift/container/sharder.py +36 -12
  63. swift/container/sync.py +11 -1
  64. swift/container/updater.py +11 -2
  65. swift/obj/auditor.py +18 -2
  66. swift/obj/diskfile.py +8 -6
  67. swift/obj/expirer.py +155 -36
  68. swift/obj/reconstructor.py +28 -4
  69. swift/obj/replicator.py +61 -22
  70. swift/obj/server.py +64 -36
  71. swift/obj/updater.py +11 -2
  72. swift/proxy/controllers/base.py +38 -22
  73. swift/proxy/controllers/obj.py +23 -26
  74. swift/proxy/server.py +15 -1
  75. {swift-2.33.0.dist-info → swift-2.34.1.dist-info}/AUTHORS +11 -3
  76. {swift-2.33.0.dist-info → swift-2.34.1.dist-info}/METADATA +34 -35
  77. {swift-2.33.0.dist-info → swift-2.34.1.dist-info}/RECORD +82 -108
  78. {swift-2.33.0.dist-info → swift-2.34.1.dist-info}/WHEEL +1 -1
  79. {swift-2.33.0.dist-info → swift-2.34.1.dist-info}/entry_points.txt +38 -1
  80. swift-2.34.1.dist-info/pbr.json +1 -0
  81. swift-2.33.0.data/scripts/swift-account-auditor +0 -23
  82. swift-2.33.0.data/scripts/swift-account-info +0 -52
  83. swift-2.33.0.data/scripts/swift-account-reaper +0 -23
  84. swift-2.33.0.data/scripts/swift-account-replicator +0 -34
  85. swift-2.33.0.data/scripts/swift-account-server +0 -23
  86. swift-2.33.0.data/scripts/swift-container-auditor +0 -23
  87. swift-2.33.0.data/scripts/swift-container-info +0 -59
  88. swift-2.33.0.data/scripts/swift-container-reconciler +0 -21
  89. swift-2.33.0.data/scripts/swift-container-replicator +0 -34
  90. swift-2.33.0.data/scripts/swift-container-server +0 -23
  91. swift-2.33.0.data/scripts/swift-container-sharder +0 -37
  92. swift-2.33.0.data/scripts/swift-container-sync +0 -23
  93. swift-2.33.0.data/scripts/swift-container-updater +0 -23
  94. swift-2.33.0.data/scripts/swift-dispersion-report +0 -24
  95. swift-2.33.0.data/scripts/swift-form-signature +0 -20
  96. swift-2.33.0.data/scripts/swift-init +0 -119
  97. swift-2.33.0.data/scripts/swift-object-auditor +0 -29
  98. swift-2.33.0.data/scripts/swift-object-expirer +0 -33
  99. swift-2.33.0.data/scripts/swift-object-info +0 -60
  100. swift-2.33.0.data/scripts/swift-object-reconstructor +0 -33
  101. swift-2.33.0.data/scripts/swift-object-relinker +0 -23
  102. swift-2.33.0.data/scripts/swift-object-replicator +0 -37
  103. swift-2.33.0.data/scripts/swift-object-server +0 -27
  104. swift-2.33.0.data/scripts/swift-object-updater +0 -23
  105. swift-2.33.0.data/scripts/swift-proxy-server +0 -23
  106. swift-2.33.0.data/scripts/swift-recon +0 -24
  107. swift-2.33.0.data/scripts/swift-recon-cron +0 -24
  108. swift-2.33.0.data/scripts/swift-ring-builder +0 -37
  109. swift-2.33.0.data/scripts/swift-ring-builder-analyzer +0 -22
  110. swift-2.33.0.data/scripts/swift-ring-composer +0 -22
  111. swift-2.33.0.dist-info/pbr.json +0 -1
  112. {swift-2.33.0.dist-info → swift-2.34.1.dist-info}/LICENSE +0 -0
  113. {swift-2.33.0.dist-info → swift-2.34.1.dist-info}/top_level.txt +0 -0
@@ -56,7 +56,8 @@ from swift.common.middleware.s3api.s3response import AccessDenied, \
56
56
  MissingContentLength, InvalidStorageClass, S3NotImplemented, InvalidURI, \
57
57
  MalformedXML, InvalidRequest, RequestTimeout, InvalidBucketName, \
58
58
  BadDigest, AuthorizationHeaderMalformed, SlowDown, \
59
- AuthorizationQueryParametersError, ServiceUnavailable, BrokenMPU
59
+ AuthorizationQueryParametersError, ServiceUnavailable, BrokenMPU, \
60
+ InvalidPartNumber, InvalidPartArgument, XAmzContentSHA256Mismatch
60
61
  from swift.common.middleware.s3api.exception import NotS3Request
61
62
  from swift.common.middleware.s3api.utils import utf8encode, \
62
63
  S3Timestamp, mktime, MULTIUPLOAD_SUFFIX
@@ -119,6 +120,20 @@ def _header_acl_property(resource):
119
120
  doc='Get and set the %s acl property' % resource)
120
121
 
121
122
 
123
+ class S3InputSHA256Mismatch(BaseException):
124
+ """
125
+ Client provided a X-Amz-Content-SHA256, but it doesn't match the data.
126
+
127
+ Inherit from BaseException (rather than Exception) so it cuts from the
128
+ proxy-server app (which will presumably be the one reading the input)
129
+ through all the layers of the pipeline back to us. It should never escape
130
+ the s3api middleware.
131
+ """
132
+ def __init__(self, expected, computed):
133
+ self.expected = expected
134
+ self.computed = computed
135
+
136
+
122
137
  class HashingInput(object):
123
138
  """
124
139
  wsgi.input wrapper to verify the hash of the input as it's read.
@@ -129,6 +144,13 @@ class HashingInput(object):
129
144
  self._to_read = content_length
130
145
  self._hasher = hasher()
131
146
  self._expected = expected_hex_hash
147
+ if content_length == 0 and \
148
+ self._hasher.hexdigest() != self._expected.lower():
149
+ self.close()
150
+ raise XAmzContentSHA256Mismatch(
151
+ client_computed_content_s_h_a256=self._expected,
152
+ s3_computed_content_s_h_a256=self._hasher.hexdigest(),
153
+ )
132
154
 
133
155
  def read(self, size=None):
134
156
  chunk = self._input.read(size)
@@ -137,12 +159,12 @@ class HashingInput(object):
137
159
  short_read = bool(chunk) if size is None else (len(chunk) < size)
138
160
  if self._to_read < 0 or (short_read and self._to_read) or (
139
161
  self._to_read == 0 and
140
- self._hasher.hexdigest() != self._expected):
162
+ self._hasher.hexdigest() != self._expected.lower()):
141
163
  self.close()
142
164
  # Since we don't return the last chunk, the PUT never completes
143
- raise swob.HTTPUnprocessableEntity(
144
- 'The X-Amz-Content-SHA56 you specified did not match '
145
- 'what we received.')
165
+ raise S3InputSHA256Mismatch(
166
+ self._expected,
167
+ self._hasher.hexdigest())
146
168
  return chunk
147
169
 
148
170
  def close(self):
@@ -237,6 +259,28 @@ class SigV4Mixin(object):
237
259
  if int(self.timestamp) + expires < S3Timestamp.now():
238
260
  raise AccessDenied('Request has expired', reason='expired')
239
261
 
262
+ def _validate_sha256(self):
263
+ aws_sha256 = self.headers.get('x-amz-content-sha256')
264
+ looks_like_sha256 = (
265
+ aws_sha256 and len(aws_sha256) == 64 and
266
+ all(c in '0123456789abcdef' for c in aws_sha256.lower()))
267
+ if not aws_sha256:
268
+ if 'X-Amz-Credential' in self.params:
269
+ pass # pre-signed URL; not required
270
+ else:
271
+ msg = 'Missing required header for this request: ' \
272
+ 'x-amz-content-sha256'
273
+ raise InvalidRequest(msg)
274
+ elif aws_sha256 == 'UNSIGNED-PAYLOAD':
275
+ pass
276
+ elif not looks_like_sha256 and 'X-Amz-Credential' not in self.params:
277
+ raise InvalidArgument(
278
+ 'x-amz-content-sha256',
279
+ aws_sha256,
280
+ 'x-amz-content-sha256 must be UNSIGNED-PAYLOAD, or '
281
+ 'a valid sha256 value.')
282
+ return aws_sha256
283
+
240
284
  def _parse_credential(self, credential_string):
241
285
  parts = credential_string.split("/")
242
286
  # credential must be in following format:
@@ -447,30 +491,9 @@ class SigV4Mixin(object):
447
491
  cr.append(b';'.join(swob.wsgi_to_bytes(k) for k, v in headers_to_sign))
448
492
 
449
493
  # 6. Add payload string at the tail
450
- if 'X-Amz-Credential' in self.params:
451
- # V4 with query parameters only
452
- hashed_payload = 'UNSIGNED-PAYLOAD'
453
- elif 'X-Amz-Content-SHA256' not in self.headers:
454
- msg = 'Missing required header for this request: ' \
455
- 'x-amz-content-sha256'
456
- raise InvalidRequest(msg)
457
- else:
458
- hashed_payload = self.headers['X-Amz-Content-SHA256']
459
- if hashed_payload != 'UNSIGNED-PAYLOAD':
460
- if self.content_length == 0:
461
- if hashed_payload.lower() != sha256().hexdigest():
462
- raise BadDigest(
463
- 'The X-Amz-Content-SHA56 you specified did not '
464
- 'match what we received.')
465
- elif self.content_length:
466
- self.environ['wsgi.input'] = HashingInput(
467
- self.environ['wsgi.input'],
468
- self.content_length,
469
- sha256,
470
- hashed_payload.lower())
471
- # else, length not provided -- Swift will kick out a
472
- # 411 Length Required which will get translated back
473
- # to a S3-style response in S3Request._swift_error_codes
494
+ hashed_payload = self.headers.get('X-Amz-Content-SHA256',
495
+ 'UNSIGNED-PAYLOAD')
496
+
474
497
  cr.append(swob.wsgi_to_bytes(hashed_payload))
475
498
  return b'\n'.join(cr)
476
499
 
@@ -558,6 +581,57 @@ class S3Request(swob.Request):
558
581
  # by full URL when absolute path given. See swift.swob for more detail.
559
582
  self.environ['swift.leave_relative_location'] = True
560
583
 
584
+ def validate_part_number(self, parts_count=None, check_max=True):
585
+ """
586
+ Get the partNumber param, if it exists, and check it is valid.
587
+
588
+ To be valid, a partNumber must satisfy two criteria. First, it must be
589
+ an integer between 1 and the maximum allowed parts, inclusive. The
590
+ maximum allowed parts is the maximum of the configured
591
+ ``max_upload_part_num`` and, if given, ``parts_count``. Second, the
592
+ partNumber must be less than or equal to the ``parts_count``, if it is
593
+ given.
594
+
595
+ :param parts_count: if given, this is the number of parts in an
596
+ existing object.
597
+ :raises InvalidPartArgument: if the partNumber param is invalid i.e.
598
+ less than 1 or greater than the maximum allowed parts.
599
+ :raises InvalidPartNumber: if the partNumber param is valid but greater
600
+ than ``num_parts``.
601
+ :return: an integer part number if the partNumber param exists,
602
+ otherwise ``None``.
603
+ """
604
+ part_number = self.params.get('partNumber')
605
+ if part_number is None:
606
+ return None
607
+
608
+ if self.range:
609
+ raise InvalidRequest('Cannot specify both Range header and '
610
+ 'partNumber query parameter')
611
+
612
+ try:
613
+ parts_count = int(parts_count)
614
+ except (TypeError, ValueError):
615
+ # an invalid/empty param is treated like parts_count=max_parts
616
+ parts_count = self.conf.max_upload_part_num
617
+ # max_parts may be raised to the number of existing parts
618
+ max_parts = max(self.conf.max_upload_part_num, parts_count)
619
+
620
+ try:
621
+ part_number = int(part_number)
622
+ if part_number < 1:
623
+ raise ValueError
624
+ except ValueError:
625
+ raise InvalidPartArgument(max_parts, part_number) # 400
626
+
627
+ if check_max:
628
+ if part_number > max_parts:
629
+ raise InvalidPartArgument(max_parts, part_number) # 400
630
+ if part_number > parts_count:
631
+ raise InvalidPartNumber() # 416
632
+
633
+ return part_number
634
+
561
635
  def check_signature(self, secret):
562
636
  secret = utf8encode(secret)
563
637
  user_signature = self.signature
@@ -747,6 +821,9 @@ class S3Request(swob.Request):
747
821
  if delta > self.conf.allowable_clock_skew:
748
822
  raise RequestTimeTooSkewed()
749
823
 
824
+ def _validate_sha256(self):
825
+ return self.headers.get('x-amz-content-sha256')
826
+
750
827
  def _validate_headers(self):
751
828
  if 'CONTENT_LENGTH' in self.environ:
752
829
  try:
@@ -757,21 +834,6 @@ class S3Request(swob.Request):
757
834
  raise InvalidArgument('Content-Length',
758
835
  self.environ['CONTENT_LENGTH'])
759
836
 
760
- value = _header_strip(self.headers.get('Content-MD5'))
761
- if value is not None:
762
- if not re.match('^[A-Za-z0-9+/]+={0,2}$', value):
763
- # Non-base64-alphabet characters in value.
764
- raise InvalidDigest(content_md5=value)
765
- try:
766
- self.headers['ETag'] = binascii.b2a_hex(
767
- binascii.a2b_base64(value))
768
- except binascii.Error:
769
- # incorrect padding, most likely
770
- raise InvalidDigest(content_md5=value)
771
-
772
- if len(self.headers['ETag']) != 32:
773
- raise InvalidDigest(content_md5=value)
774
-
775
837
  if self.method == 'PUT' and any(h in self.headers for h in (
776
838
  'If-Match', 'If-None-Match',
777
839
  'If-Modified-Since', 'If-Unmodified-Since')):
@@ -817,6 +879,38 @@ class S3Request(swob.Request):
817
879
  if 'x-amz-website-redirect-location' in self.headers:
818
880
  raise S3NotImplemented('Website redirection is not supported.')
819
881
 
882
+ aws_sha256 = self._validate_sha256()
883
+ if (aws_sha256
884
+ and aws_sha256 != 'UNSIGNED-PAYLOAD'
885
+ and self.content_length is not None):
886
+ # Even if client-provided SHA doesn't look like a SHA, wrap the
887
+ # input anyway so we'll send the SHA of what the client sent in
888
+ # the eventual error
889
+ self.environ['wsgi.input'] = HashingInput(
890
+ self.environ['wsgi.input'],
891
+ self.content_length,
892
+ sha256,
893
+ aws_sha256)
894
+ # If no content-length, either client's trying to do a HTTP chunked
895
+ # transfer, or a HTTP/1.0-style transfer (in which case swift will
896
+ # reject with length-required and we'll translate back to
897
+ # MissingContentLength)
898
+
899
+ value = _header_strip(self.headers.get('Content-MD5'))
900
+ if value is not None:
901
+ if not re.match('^[A-Za-z0-9+/]+={0,2}$', value):
902
+ # Non-base64-alphabet characters in value.
903
+ raise InvalidDigest(content_md5=value)
904
+ try:
905
+ self.headers['ETag'] = binascii.b2a_hex(
906
+ binascii.a2b_base64(value))
907
+ except binascii.Error:
908
+ # incorrect padding, most likely
909
+ raise InvalidDigest(content_md5=value)
910
+
911
+ if len(self.headers['ETag']) != 32:
912
+ raise InvalidDigest(content_md5=value)
913
+
820
914
  # https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
821
915
  # describes some of what would be required to support this
822
916
  if any(['aws-chunked' in self.headers.get('content-encoding', ''),
@@ -858,13 +952,11 @@ class S3Request(swob.Request):
858
952
  # Limit the read similar to how SLO handles manifests
859
953
  try:
860
954
  body = self.body_file.read(max_length)
861
- except swob.HTTPException as err:
862
- if err.status_int == HTTP_UNPROCESSABLE_ENTITY:
863
- # Special case for HashingInput check
864
- raise BadDigest(
865
- 'The X-Amz-Content-SHA56 you specified did not '
866
- 'match what we received.')
867
- raise
955
+ except S3InputSHA256Mismatch as err:
956
+ raise XAmzContentSHA256Mismatch(
957
+ client_computed_content_s_h_a256=err.expected,
958
+ s3_computed_content_s_h_a256=err.computed,
959
+ )
868
960
  else:
869
961
  # No (or zero) Content-Length provided, and not chunked transfer;
870
962
  # no body. Assume zero-length, and enforce a required body below.
@@ -1044,7 +1136,10 @@ class S3Request(swob.Request):
1044
1136
  if 'logging' in self.params:
1045
1137
  return LoggingStatusController
1046
1138
  if 'partNumber' in self.params:
1047
- return PartController
1139
+ if self.method == 'PUT':
1140
+ return PartController
1141
+ else:
1142
+ return ObjectController
1048
1143
  if 'uploadId' in self.params:
1049
1144
  return UploadController
1050
1145
  if 'uploads' in self.params:
@@ -1087,7 +1182,7 @@ class S3Request(swob.Request):
1087
1182
  Create a Swift request based on this request's environment.
1088
1183
  """
1089
1184
  if self.account is None:
1090
- account = self.access_key
1185
+ account = swob.str_to_wsgi(self.access_key)
1091
1186
  else:
1092
1187
  account = self.account
1093
1188
 
@@ -1307,6 +1402,16 @@ class S3Request(swob.Request):
1307
1402
  return NoSuchKey(obj)
1308
1403
  return NoSuchBucket(container)
1309
1404
 
1405
+ # Since BadDigest ought to plumb in some client-provided values,
1406
+ # defer evaluation until we know they're provided
1407
+ def bad_digest_handler():
1408
+ etag = binascii.hexlify(base64.b64decode(
1409
+ env['HTTP_CONTENT_MD5']))
1410
+ return BadDigest(
1411
+ expected_digest=etag, # yes, really hex
1412
+ # TODO: plumb in calculated_digest, as b64
1413
+ )
1414
+
1310
1415
  code_map = {
1311
1416
  'HEAD': {
1312
1417
  HTTP_NOT_FOUND: not_found_handler,
@@ -1315,11 +1420,10 @@ class S3Request(swob.Request):
1315
1420
  'GET': {
1316
1421
  HTTP_NOT_FOUND: not_found_handler,
1317
1422
  HTTP_PRECONDITION_FAILED: PreconditionFailed,
1318
- HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: InvalidRange,
1319
1423
  },
1320
1424
  'PUT': {
1321
1425
  HTTP_NOT_FOUND: (NoSuchBucket, container),
1322
- HTTP_UNPROCESSABLE_ENTITY: BadDigest,
1426
+ HTTP_UNPROCESSABLE_ENTITY: bad_digest_handler,
1323
1427
  HTTP_REQUEST_ENTITY_TOO_LARGE: EntityTooLarge,
1324
1428
  HTTP_LENGTH_REQUIRED: MissingContentLength,
1325
1429
  HTTP_REQUEST_TIMEOUT: RequestTimeout,
@@ -1356,13 +1460,14 @@ class S3Request(swob.Request):
1356
1460
 
1357
1461
  try:
1358
1462
  sw_resp = sw_req.get_response(app)
1359
- except swob.HTTPException as err:
1360
- # Maybe a 422 from HashingInput? Put something in
1361
- # s3api.backend_path - hopefully by now any modifications to the
1362
- # path (e.g. tenant to account translation) will have been made by
1363
- # auth middleware
1463
+ except S3InputSHA256Mismatch as err:
1464
+ # hopefully by now any modifications to the path (e.g. tenant to
1465
+ # account translation) will have been made by auth middleware
1364
1466
  self.environ['s3api.backend_path'] = sw_req.environ['PATH_INFO']
1365
- sw_resp = err
1467
+ raise XAmzContentSHA256Mismatch(
1468
+ client_computed_content_s_h_a256=err.expected,
1469
+ s3_computed_content_s_h_a256=err.computed,
1470
+ )
1366
1471
  else:
1367
1472
  # reuse account
1368
1473
  _, self.account, _ = split_path(sw_resp.environ['PATH_INFO'],
@@ -1414,7 +1519,7 @@ class S3Request(swob.Request):
1414
1519
  raise InvalidArgument('X-Delete-At',
1415
1520
  self.headers['X-Delete-At'],
1416
1521
  err_str)
1417
- if 'X-Delete-After' in err_msg.decode('utf8'):
1522
+ if 'X-Delete-After' in err_str:
1418
1523
  raise InvalidArgument('X-Delete-After',
1419
1524
  self.headers['X-Delete-After'],
1420
1525
  err_str)
@@ -1425,6 +1530,10 @@ class S3Request(swob.Request):
1425
1530
  **self.signature_does_not_match_kwargs())
1426
1531
  if status == HTTP_FORBIDDEN:
1427
1532
  raise AccessDenied(reason='forbidden')
1533
+ if status == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
1534
+ self.validate_part_number(
1535
+ parts_count=resp.headers.get('x-amz-mp-parts-count'))
1536
+ raise InvalidRange()
1428
1537
  if status == HTTP_SERVICE_UNAVAILABLE:
1429
1538
  raise ServiceUnavailable()
1430
1539
  if status in (HTTP_RATE_LIMITED, HTTP_TOO_MANY_REQUESTS):
@@ -72,6 +72,8 @@ def translate_swift_to_s3(key, val):
72
72
  return key, val
73
73
  elif _key == 'x-object-version-id':
74
74
  return 'x-amz-version-id', val
75
+ elif _key == 'x-parts-count':
76
+ return 'x-amz-mp-parts-count', val
75
77
  elif _key == 'x-copied-from-version-id':
76
78
  return 'x-amz-copy-source-version-id', val
77
79
  elif _key == 'x-backend-content-type' and \
@@ -250,12 +252,17 @@ class ErrorResponse(S3ResponseBase, swob.HTTPException):
250
252
  self.headers = HeaderKeyDict(self.headers)
251
253
 
252
254
  @property
253
- def metric_name(self):
254
- parts = [str(self.status_int), self._code]
255
+ def summary(self):
256
+ """Provide a summary of the error code and reason."""
255
257
  if self.reason:
256
- parts.append(self.reason)
257
- metric = '.'.join(parts)
258
- return metric.replace(' ', '_')
258
+ summary = '.'.join([self._code, self.reason])
259
+ else:
260
+ summary = self._code
261
+ return summary.replace(' ', '_')
262
+
263
+ @property
264
+ def metric_name(self):
265
+ return '.'.join([str(self.status_int), self.summary])
259
266
 
260
267
  def _body_iter(self):
261
268
  error_elem = Element('Error')
@@ -320,6 +327,12 @@ class BadDigest(ErrorResponse):
320
327
  _msg = 'The Content-MD5 you specified did not match what we received.'
321
328
 
322
329
 
330
+ class XAmzContentSHA256Mismatch(ErrorResponse):
331
+ _status = '400 Bad Request'
332
+ _msg = "The provided 'x-amz-content-sha256' header does not match what " \
333
+ "was computed."
334
+
335
+
323
336
  class BucketAlreadyExists(ErrorResponse):
324
337
  _status = '409 Conflict'
325
338
  _msg = 'The requested bucket name is not available. The bucket ' \
@@ -436,7 +449,7 @@ class InvalidBucketState(ErrorResponse):
436
449
 
437
450
  class InvalidDigest(ErrorResponse):
438
451
  _status = '400 Bad Request'
439
- _msg = 'The Content-MD5 you specified was an invalid.'
452
+ _msg = 'The Content-MD5 you specified was invalid.'
440
453
 
441
454
 
442
455
  class InvalidLocationConstraint(ErrorResponse):
@@ -449,6 +462,17 @@ class InvalidObjectState(ErrorResponse):
449
462
  _msg = 'The operation is not valid for the current state of the object.'
450
463
 
451
464
 
465
+ class InvalidPartArgument(InvalidArgument):
466
+ _code = 'InvalidArgument'
467
+
468
+ def __init__(self, max_parts, value):
469
+ err_msg = ('Part number must be an integer between '
470
+ '1 and %s, inclusive' % max_parts)
471
+ super(InvalidArgument, self).__init__(err_msg,
472
+ argument_name='partNumber',
473
+ argument_value=value)
474
+
475
+
452
476
  class InvalidPart(ErrorResponse):
453
477
  _status = '400 Bad Request'
454
478
  _msg = 'One or more of the specified parts could not be found. The part ' \
@@ -478,6 +502,11 @@ class InvalidRange(ErrorResponse):
478
502
  _msg = 'The requested range cannot be satisfied.'
479
503
 
480
504
 
505
+ class InvalidPartNumber(ErrorResponse):
506
+ _status = '416 Requested Range Not Satisfiable'
507
+ _msg = 'The requested partnumber is not satisfiable'
508
+
509
+
481
510
  class InvalidRequest(ErrorResponse):
482
511
  _status = '400 Bad Request'
483
512
  _msg = 'Invalid Request.'
@@ -65,7 +65,7 @@ import six
65
65
  from six.moves import urllib
66
66
 
67
67
  from swift.common.swob import Request, HTTPBadRequest, HTTPUnauthorized, \
68
- HTTPException
68
+ HTTPException, str_to_wsgi
69
69
  from swift.common.utils import config_true_value, split_path, get_logger, \
70
70
  cache_from_env, append_underscore
71
71
  from swift.common.wsgi import ConfigFileError
@@ -404,7 +404,7 @@ class S3Token(object):
404
404
  self._logger.debug('Connecting with tenant: %s', tenant_to_connect)
405
405
  new_tenant_name = '%s%s' % (self._reseller_prefix, tenant_to_connect)
406
406
  environ['PATH_INFO'] = environ['PATH_INFO'].replace(
407
- account, new_tenant_name, 1)
407
+ str_to_wsgi(account), str_to_wsgi(new_tenant_name), 1)
408
408
  return self._app(environ, start_response)
409
409
 
410
410
 
@@ -172,6 +172,7 @@ class Config(dict):
172
172
  'allow_no_owner': False,
173
173
  'allowable_clock_skew': 900,
174
174
  'ratelimit_as_client_error': False,
175
+ 'max_upload_part_num': 1000,
175
176
  }
176
177
 
177
178
  def __init__(self, base=None):