swift 2.33.0__py2.py3-none-any.whl → 2.34.0__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 (112) 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/proxy_logging.py +22 -16
  32. swift/common/middleware/ratelimit.py +6 -7
  33. swift/common/middleware/recon.py +6 -7
  34. swift/common/middleware/s3api/acl_handlers.py +9 -0
  35. swift/common/middleware/s3api/controllers/multi_upload.py +1 -9
  36. swift/common/middleware/s3api/controllers/obj.py +20 -1
  37. swift/common/middleware/s3api/s3api.py +2 -0
  38. swift/common/middleware/s3api/s3request.py +171 -62
  39. swift/common/middleware/s3api/s3response.py +35 -6
  40. swift/common/middleware/s3api/s3token.py +2 -2
  41. swift/common/middleware/s3api/utils.py +1 -0
  42. swift/common/middleware/slo.py +153 -52
  43. swift/common/middleware/tempauth.py +6 -4
  44. swift/common/middleware/tempurl.py +2 -2
  45. swift/common/middleware/x_profile/exceptions.py +1 -4
  46. swift/common/middleware/x_profile/html_viewer.py +9 -10
  47. swift/common/middleware/x_profile/profile_model.py +1 -2
  48. swift/common/middleware/xprofile.py +1 -2
  49. swift/common/request_helpers.py +69 -0
  50. swift/common/statsd_client.py +207 -0
  51. swift/common/utils/__init__.py +97 -1635
  52. swift/common/utils/base.py +138 -0
  53. swift/common/utils/config.py +443 -0
  54. swift/common/utils/logs.py +999 -0
  55. swift/common/wsgi.py +11 -3
  56. swift/container/auditor.py +11 -0
  57. swift/container/backend.py +10 -10
  58. swift/container/reconciler.py +11 -2
  59. swift/container/replicator.py +22 -1
  60. swift/container/server.py +12 -1
  61. swift/container/sharder.py +36 -12
  62. swift/container/sync.py +11 -1
  63. swift/container/updater.py +11 -2
  64. swift/obj/auditor.py +18 -2
  65. swift/obj/diskfile.py +8 -6
  66. swift/obj/expirer.py +155 -36
  67. swift/obj/reconstructor.py +28 -4
  68. swift/obj/replicator.py +61 -22
  69. swift/obj/server.py +64 -36
  70. swift/obj/updater.py +11 -2
  71. swift/proxy/controllers/base.py +38 -22
  72. swift/proxy/controllers/obj.py +23 -26
  73. swift/proxy/server.py +15 -1
  74. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +11 -3
  75. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/METADATA +6 -5
  76. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/RECORD +81 -107
  77. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +38 -0
  78. swift-2.34.0.dist-info/pbr.json +1 -0
  79. swift-2.33.0.data/scripts/swift-account-auditor +0 -23
  80. swift-2.33.0.data/scripts/swift-account-info +0 -52
  81. swift-2.33.0.data/scripts/swift-account-reaper +0 -23
  82. swift-2.33.0.data/scripts/swift-account-replicator +0 -34
  83. swift-2.33.0.data/scripts/swift-account-server +0 -23
  84. swift-2.33.0.data/scripts/swift-container-auditor +0 -23
  85. swift-2.33.0.data/scripts/swift-container-info +0 -59
  86. swift-2.33.0.data/scripts/swift-container-reconciler +0 -21
  87. swift-2.33.0.data/scripts/swift-container-replicator +0 -34
  88. swift-2.33.0.data/scripts/swift-container-server +0 -23
  89. swift-2.33.0.data/scripts/swift-container-sharder +0 -37
  90. swift-2.33.0.data/scripts/swift-container-sync +0 -23
  91. swift-2.33.0.data/scripts/swift-container-updater +0 -23
  92. swift-2.33.0.data/scripts/swift-dispersion-report +0 -24
  93. swift-2.33.0.data/scripts/swift-form-signature +0 -20
  94. swift-2.33.0.data/scripts/swift-init +0 -119
  95. swift-2.33.0.data/scripts/swift-object-auditor +0 -29
  96. swift-2.33.0.data/scripts/swift-object-expirer +0 -33
  97. swift-2.33.0.data/scripts/swift-object-info +0 -60
  98. swift-2.33.0.data/scripts/swift-object-reconstructor +0 -33
  99. swift-2.33.0.data/scripts/swift-object-relinker +0 -23
  100. swift-2.33.0.data/scripts/swift-object-replicator +0 -37
  101. swift-2.33.0.data/scripts/swift-object-server +0 -27
  102. swift-2.33.0.data/scripts/swift-object-updater +0 -23
  103. swift-2.33.0.data/scripts/swift-proxy-server +0 -23
  104. swift-2.33.0.data/scripts/swift-recon +0 -24
  105. swift-2.33.0.data/scripts/swift-recon-cron +0 -24
  106. swift-2.33.0.data/scripts/swift-ring-builder +0 -37
  107. swift-2.33.0.data/scripts/swift-ring-builder-analyzer +0 -22
  108. swift-2.33.0.data/scripts/swift-ring-composer +0 -22
  109. swift-2.33.0.dist-info/pbr.json +0 -1
  110. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
  111. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
  112. {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/top_level.txt +0 -0
@@ -29,8 +29,6 @@ rewritten and the request is passed further down the WSGI chain.
29
29
 
30
30
  import six
31
31
 
32
- from swift import gettext_ as _
33
-
34
32
  try:
35
33
  import dns.resolver
36
34
  import dns.exception
@@ -167,7 +165,7 @@ class CNAMELookupMiddleware(object):
167
165
  elif self._domain_endswith_in_storage_domain(found_domain):
168
166
  # Found it!
169
167
  self.logger.info(
170
- _('Mapped %(given_domain)s to %(found_domain)s') %
168
+ 'Mapped %(given_domain)s to %(found_domain)s',
171
169
  {'given_domain': given_domain,
172
170
  'found_domain': found_domain})
173
171
  if port:
@@ -180,8 +178,8 @@ class CNAMELookupMiddleware(object):
180
178
  else:
181
179
  # try one more deep in the chain
182
180
  self.logger.debug(
183
- _('Following CNAME chain for '
184
- '%(given_domain)s to %(found_domain)s') %
181
+ 'Following CNAME chain for '
182
+ '%(given_domain)s to %(found_domain)s',
185
183
  {'given_domain': given_domain,
186
184
  'found_domain': found_domain})
187
185
  a_domain = found_domain
@@ -17,6 +17,7 @@ import os
17
17
 
18
18
  from swift.common.constraints import valid_api_version
19
19
  from swift.common.container_sync_realms import ContainerSyncRealms
20
+ from swift.common.request_helpers import append_log_info
20
21
  from swift.common.swob import HTTPBadRequest, HTTPUnauthorized, wsgify
21
22
  from swift.common.utils import (
22
23
  config_true_value, get_logger, streq_const_time)
@@ -109,20 +110,17 @@ class ContainerSync(object):
109
110
  valid = False
110
111
  auth = auth.split()
111
112
  if len(auth) != 3:
112
- req.environ.setdefault('swift.log_info', []).append(
113
- 'cs:not-3-args')
113
+ append_log_info(req.environ, 'cs:not-3-args')
114
114
  else:
115
115
  realm, nonce, sig = auth
116
116
  realm_key = self.realms_conf.key(realm)
117
117
  realm_key2 = self.realms_conf.key2(realm)
118
118
  if not realm_key:
119
- req.environ.setdefault('swift.log_info', []).append(
120
- 'cs:no-local-realm-key')
119
+ append_log_info(req.environ, 'cs:no-local-realm-key')
121
120
  else:
122
121
  user_key = info.get('sync_key')
123
122
  if not user_key:
124
- req.environ.setdefault('swift.log_info', []).append(
125
- 'cs:no-local-user-key')
123
+ append_log_info(req.environ, 'cs:no-local-user-key')
126
124
  else:
127
125
  # x-timestamp headers get shunted by gatekeeper
128
126
  if 'x-backend-inbound-x-timestamp' in req.headers:
@@ -139,11 +137,9 @@ class ContainerSync(object):
139
137
  realm_key2, user_key) if realm_key2 else expected
140
138
  if not streq_const_time(sig, expected) and \
141
139
  not streq_const_time(sig, expected2):
142
- req.environ.setdefault(
143
- 'swift.log_info', []).append('cs:invalid-sig')
140
+ append_log_info(req.environ, 'cs:invalid-sig')
144
141
  else:
145
- req.environ.setdefault(
146
- 'swift.log_info', []).append('cs:valid')
142
+ append_log_info(req.environ, 'cs:valid')
147
143
  valid = True
148
144
  if not valid:
149
145
  exc = HTTPUnauthorized(
@@ -22,7 +22,6 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
22
22
  import six
23
23
  from six.moves.urllib import parse as urlparse
24
24
 
25
- from swift import gettext_ as _
26
25
  from swift.common.exceptions import EncryptionException, UnknownSecretIdError
27
26
  from swift.common.swob import HTTPInternalServerError
28
27
  from swift.common.utils import get_logger
@@ -160,7 +159,7 @@ class CryptoWSGIContext(WSGIContext):
160
159
  try:
161
160
  fetch_crypto_keys = env[CRYPTO_KEY_CALLBACK]
162
161
  except KeyError:
163
- self.logger.exception(_('ERROR get_keys() missing callback'))
162
+ self.logger.exception('ERROR get_keys() missing callback')
164
163
  raise HTTPInternalServerError(
165
164
  "Unable to retrieve encryption keys.")
166
165
 
@@ -181,12 +180,12 @@ class CryptoWSGIContext(WSGIContext):
181
180
  self.crypto.check_key(key)
182
181
  continue
183
182
  except KeyError:
184
- self.logger.exception(_("Missing key for %r") % name)
183
+ self.logger.exception("Missing key for %r", name)
185
184
  except TypeError:
186
- self.logger.exception(_("Did not get a keys dict"))
185
+ self.logger.exception("Did not get a keys dict")
187
186
  except ValueError as e:
188
187
  # don't include the key in any messages!
189
- self.logger.exception(_("Bad key for %(name)r: %(err)s") %
188
+ self.logger.exception("Bad key for %(name)r: %(err)s",
190
189
  {'name': name, 'err': e})
191
190
  raise HTTPInternalServerError(
192
191
  "Unable to retrieve encryption keys.")
@@ -16,7 +16,6 @@
16
16
  import base64
17
17
  import json
18
18
 
19
- from swift import gettext_ as _
20
19
  from swift.common.header_key_dict import HeaderKeyDict
21
20
  from swift.common.http import is_success
22
21
  from swift.common.middleware.crypto.crypto_utils import CryptoWSGIContext, \
@@ -77,10 +76,10 @@ class BaseDecrypterContext(CryptoWSGIContext):
77
76
  crypto_meta['body_key'])
78
77
  except KeyError as err:
79
78
  self.logger.error(
80
- _('Error decrypting %(resp_type)s: Missing %(key)s'),
79
+ 'Error decrypting %(resp_type)s: Missing %(key)s',
81
80
  {'resp_type': self.server_type, 'key': err})
82
81
  except ValueError as err:
83
- self.logger.error(_('Error decrypting %(resp_type)s: %(reason)s'),
82
+ self.logger.error('Error decrypting %(resp_type)s: %(reason)s',
84
83
  {'resp_type': self.server_type, 'reason': err})
85
84
  raise HTTPInternalServerError(
86
85
  body='Error decrypting %s' % self.server_type,
@@ -178,7 +177,7 @@ class DecrypterObjContext(BaseDecrypterContext):
178
177
  value, key, required, bytes_to_wsgi)
179
178
  except EncryptionException as err:
180
179
  self.logger.error(
181
- _("Error decrypting header %(header)s: %(error)s"),
180
+ "Error decrypting header %(header)s: %(error)s",
182
181
  {'header': header, 'error': err})
183
182
  raise HTTPInternalServerError(
184
183
  body='Error decrypting header',
@@ -313,7 +312,7 @@ class DecrypterObjContext(BaseDecrypterContext):
313
312
  try:
314
313
  crypto_meta = self.get_crypto_meta(header, check)
315
314
  except EncryptionException as err:
316
- self.logger.error(_('Error decrypting object: %s'), err)
315
+ self.logger.error('Error decrypting object: %s', err)
317
316
  raise HTTPInternalServerError(
318
317
  body='Error decrypting object', content_type='text/plain')
319
318
  return crypto_meta
@@ -34,7 +34,7 @@ class KmsKeyMaster(BaseKeyMaster):
34
34
  'domain_id', 'domain_name', 'project_id',
35
35
  'project_domain_id', 'reauthenticate',
36
36
  'auth_endpoint', 'api_class', 'key_id*',
37
- 'active_root_secret_id')
37
+ 'barbican_endpoint', 'active_root_secret_id')
38
38
  keymaster_conf_section = 'kms_keymaster'
39
39
 
40
40
  def _get_root_secret(self, conf):
@@ -67,6 +67,7 @@ class KmsKeyMaster(BaseKeyMaster):
67
67
  oslo_conf = cfg.ConfigOpts()
68
68
  options.set_defaults(
69
69
  oslo_conf, auth_endpoint=conf.get('auth_endpoint'),
70
+ barbican_endpoint=conf.get('barbican_endpoint'),
70
71
  api_class=conf.get('api_class')
71
72
  )
72
73
  options.enable_logging()
@@ -88,7 +88,9 @@ bandwidth usage will want to only sum up logs with no ``swift.source``.
88
88
  import os
89
89
  import time
90
90
 
91
+ from swift.common.constraints import valid_api_version
91
92
  from swift.common.middleware.catch_errors import enforce_byte_count
93
+ from swift.common.request_helpers import get_log_info
92
94
  from swift.common.swob import Request
93
95
  from swift.common.utils import (get_logger, get_remote_client,
94
96
  config_true_value, reiterate,
@@ -247,6 +249,7 @@ class ProxyLoggingMiddleware(object):
247
249
  :param start_time: timestamp request started
248
250
  :param end_time: timestamp request completed
249
251
  :param resp_headers: dict of the response headers
252
+ :param ttfb: time to first byte
250
253
  :param wire_status_int: the on the wire status int
251
254
  """
252
255
  self.obscure_req(req)
@@ -269,10 +272,8 @@ class ProxyLoggingMiddleware(object):
269
272
  duration_time_str = "%.4f" % (end_time - start_time)
270
273
  policy_index = get_policy_index(req.headers, resp_headers)
271
274
 
272
- acc, cont, obj = None, None, None
273
275
  swift_path = req.environ.get('swift.backend_path', req.path)
274
- if swift_path.startswith('/v1/'):
275
- _, acc, cont, obj = split_path(swift_path, 1, 4, True)
276
+ acc, cont, obj = self.get_aco_from_path(swift_path)
276
277
 
277
278
  replacements = {
278
279
  # Time information
@@ -317,8 +318,7 @@ class ProxyLoggingMiddleware(object):
317
318
  'transaction_id': req.environ.get('swift.trans_id'),
318
319
  'request_time': duration_time_str,
319
320
  'source': req.environ.get('swift.source'),
320
- 'log_info':
321
- ','.join(req.environ.get('swift.log_info', '')),
321
+ 'log_info': get_log_info(req.environ),
322
322
  'policy_index': policy_index,
323
323
  'ttfb': ttfb,
324
324
  'pid': self.pid,
@@ -347,22 +347,28 @@ class ProxyLoggingMiddleware(object):
347
347
  self.access_logger.update_stats(metric_name_policy + '.xfer',
348
348
  bytes_received + bytes_sent)
349
349
 
350
+ def get_aco_from_path(self, swift_path):
351
+ try:
352
+ version, acc, cont, obj = split_path(swift_path, 1, 4, True)
353
+ if not valid_api_version(version):
354
+ raise ValueError
355
+ except ValueError:
356
+ acc, cont, obj = None, None, None
357
+ return acc, cont, obj
358
+
350
359
  def get_metric_name_type(self, req):
351
360
  swift_path = req.environ.get('swift.backend_path', req.path)
352
- if swift_path.startswith('/v1/'):
353
- try:
354
- stat_type = [None, 'account', 'container',
355
- 'object'][swift_path.strip('/').count('/')]
356
- except IndexError:
357
- stat_type = 'object'
358
- else:
359
- stat_type = req.environ.get('swift.source')
360
- return stat_type
361
+ acc, cont, obj = self.get_aco_from_path(swift_path)
362
+ if obj:
363
+ return 'object'
364
+ if cont:
365
+ return 'container'
366
+ if acc:
367
+ return 'account'
368
+ return req.environ.get('swift.source') or 'UNKNOWN'
361
369
 
362
370
  def statsd_metric_name(self, req, status_int, method):
363
371
  stat_type = self.get_metric_name_type(req)
364
- if stat_type is None:
365
- return None
366
372
  stat_method = method if method in self.valid_methods \
367
373
  else 'BAD_METHOD'
368
374
  return '.'.join((stat_type, stat_method, str(status_int)))
@@ -13,7 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  import time
16
- from swift import gettext_ as _
17
16
 
18
17
  import eventlet
19
18
 
@@ -261,7 +260,7 @@ class RateLimitMiddleware(object):
261
260
 
262
261
  if account_name in self.ratelimit_blacklist or \
263
262
  account_global_ratelimit == 'BLACKLIST':
264
- self.logger.error(_('Returning 497 because of blacklisting: %s'),
263
+ self.logger.error('Returning 497 because of blacklisting: %s',
265
264
  account_name)
266
265
  eventlet.sleep(self.BLACK_LIST_SLEEP)
267
266
  return Response(status='497 Blacklisted',
@@ -276,8 +275,8 @@ class RateLimitMiddleware(object):
276
275
  if self.log_sleep_time_seconds and \
277
276
  need_to_sleep > self.log_sleep_time_seconds:
278
277
  self.logger.warning(
279
- _("Ratelimit sleep log: %(sleep)s for "
280
- "%(account)s/%(container)s/%(object)s"),
278
+ "Ratelimit sleep log: %(sleep)s for "
279
+ "%(account)s/%(container)s/%(object)s",
281
280
  {'sleep': need_to_sleep, 'account': account_name,
282
281
  'container': container_name, 'object': obj_name})
283
282
  if need_to_sleep > 0:
@@ -288,8 +287,8 @@ class RateLimitMiddleware(object):
288
287
  else:
289
288
  path = '/'.join((account_name, container_name))
290
289
  self.logger.error(
291
- _('Returning 498 for %(meth)s to %(path)s. '
292
- 'Ratelimit (Max Sleep) %(e)s'),
290
+ 'Returning 498 for %(meth)s to %(path)s. '
291
+ 'Ratelimit (Max Sleep) %(e)s',
293
292
  {'meth': req.method, 'path': path, 'e': str(e)})
294
293
  error_resp = Response(status='498 Rate Limited',
295
294
  body='Slow down', request=req)
@@ -309,7 +308,7 @@ class RateLimitMiddleware(object):
309
308
  self.memcache_client = cache_from_env(env)
310
309
  if not self.memcache_client:
311
310
  self.logger.warning(
312
- _('Warning: Cannot ratelimit without a memcached client'))
311
+ 'Warning: Cannot ratelimit without a memcached client')
313
312
  return self.app(env, start_response)
314
313
  try:
315
314
  version, account, container, obj = req.split_path(1, 4, True)
@@ -20,7 +20,6 @@ import time
20
20
  from resource import getpagesize
21
21
 
22
22
  from swift import __version__ as swiftver
23
- from swift import gettext_ as _
24
23
  from swift.common.constraints import check_mount
25
24
  from swift.common.storage_policy import POLICIES
26
25
  from swift.common.swob import Request, Response
@@ -89,11 +88,11 @@ class ReconMiddleware(object):
89
88
  if err.errno == errno.ENOENT and ignore_missing:
90
89
  pass
91
90
  else:
92
- self.logger.exception(_('Error reading recon cache file'))
91
+ self.logger.exception('Error reading recon cache file')
93
92
  except ValueError:
94
- self.logger.exception(_('Error parsing recon cache file'))
93
+ self.logger.exception('Error parsing recon cache file')
95
94
  except Exception:
96
- self.logger.exception(_('Error retrieving recon data'))
95
+ self.logger.exception('Error retrieving recon data')
97
96
  return dict((key, None) for key in cache_keys)
98
97
 
99
98
  def get_version(self):
@@ -181,7 +180,7 @@ class ReconMiddleware(object):
181
180
  try:
182
181
  return {self.devices: os.listdir(self.devices)}
183
182
  except Exception:
184
- self.logger.exception(_('Error listing devices'))
183
+ self.logger.exception('Error listing devices')
185
184
  return {self.devices: None}
186
185
 
187
186
  def get_updater_info(self, recon_type):
@@ -277,7 +276,7 @@ class ReconMiddleware(object):
277
276
  except IOError as err:
278
277
  sums[ringfile] = None
279
278
  if err.errno != errno.ENOENT:
280
- self.logger.exception(_('Error reading ringfile'))
279
+ self.logger.exception('Error reading ringfile')
281
280
  return sums
282
281
 
283
282
  def get_swift_conf_md5(self):
@@ -287,7 +286,7 @@ class ReconMiddleware(object):
287
286
  hexsum = md5_hash_for_file(SWIFT_CONF_FILE)
288
287
  except IOError as err:
289
288
  if err.errno != errno.ENOENT:
290
- self.logger.exception(_('Error reading swift.conf'))
289
+ self.logger.exception('Error reading swift.conf')
291
290
  return {SWIFT_CONF_FILE: hexsum}
292
291
 
293
292
  def get_quarantine_count(self):
@@ -133,6 +133,15 @@ class BaseAclHandler(object):
133
133
  query = {}
134
134
  else:
135
135
  query = {'version-id': version_id}
136
+ if self.req.method == 'HEAD':
137
+ # This HEAD for ACL is going to also be the definitive response
138
+ # to the client so we need to include client params. We don't
139
+ # do this for other client request methods because they may
140
+ # have invalid combinations of params and headers for a swift
141
+ # HEAD request.
142
+ part_number = self.req.params.get('partNumber')
143
+ if part_number is not None:
144
+ query['part-number'] = part_number
136
145
  resp = self.req.get_acl_response(app, 'HEAD',
137
146
  container, obj,
138
147
  headers, query=query)
@@ -196,15 +196,7 @@ class PartController(Controller):
196
196
  raise InvalidArgument('ResourceType', 'partNumber',
197
197
  'Unexpected query string parameter')
198
198
 
199
- try:
200
- part_number = int(get_param(req, 'partNumber'))
201
- if part_number < 1 or self.conf.max_upload_part_num < part_number:
202
- raise Exception()
203
- except Exception:
204
- err_msg = 'Part number must be an integer between 1 and %d,' \
205
- ' inclusive' % self.conf.max_upload_part_num
206
- raise InvalidArgument('partNumber', get_param(req, 'partNumber'),
207
- err_msg)
199
+ part_number = req.validate_part_number()
208
200
 
209
201
  upload_id = get_param(req, 'uploadId')
210
202
  _get_upload_info(req, self.app, upload_id)
@@ -90,8 +90,14 @@ class ObjectController(Controller):
90
90
  if version_id not in ('null', None) and \
91
91
  'object_versioning' not in get_swift_info():
92
92
  raise S3NotImplemented()
93
+ part_number = req.validate_part_number(check_max=False)
94
+
95
+ query = {}
96
+ if version_id is not None:
97
+ query['version-id'] = version_id
98
+ if part_number is not None:
99
+ query['part-number'] = part_number
93
100
 
94
- query = {} if version_id is None else {'version-id': version_id}
95
101
  if version_id not in ('null', None):
96
102
  container_info = req.get_container_info(self.app)
97
103
  if not container_info.get(
@@ -101,6 +107,19 @@ class ObjectController(Controller):
101
107
 
102
108
  resp = req.get_response(self.app, query=query)
103
109
 
110
+ if not resp.is_slo:
111
+ # SLO ignores part_number for non-slo objects, but s3api only
112
+ # allows the query param for non-MPU if it's exactly 1.
113
+ part_number = req.validate_part_number(parts_count=1)
114
+ if part_number == 1:
115
+ # When the query param *is* exactly 1 the response status code
116
+ # and headers are updated.
117
+ resp.status = HTTP_PARTIAL_CONTENT
118
+ resp.headers['Content-Range'] = \
119
+ 'bytes 0-%d/%s' % (int(resp.headers['Content-Length']) - 1,
120
+ resp.headers['Content-Length'])
121
+ # else: part_number is None
122
+
104
123
  if req.method == 'HEAD':
105
124
  resp.app_iter = None
106
125
 
@@ -149,6 +149,7 @@ from six.moves.urllib.parse import parse_qs
149
149
  from swift.common.constraints import valid_api_version
150
150
  from swift.common.middleware.listing_formats import \
151
151
  MAX_CONTAINER_LISTING_CONTENT_LENGTH
152
+ from swift.common.request_helpers import append_log_info
152
153
  from swift.common.wsgi import PipelineWrapper, loadcontext, WSGIContext
153
154
 
154
155
  from swift.common.middleware import app_property
@@ -353,6 +354,7 @@ class S3ApiMiddleware(object):
353
354
  self.logger.debug(e.cause)
354
355
  except ErrorResponse as err_resp:
355
356
  self.logger.increment(err_resp.metric_name)
357
+ append_log_info(env, 's3:err:%s' % err_resp.summary)
356
358
  if isinstance(err_resp, InternalError):
357
359
  self.logger.exception(err_resp)
358
360
  resp = err_resp