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.
Files changed (206) hide show
  1. swift/__init__.py +29 -50
  2. swift/account/auditor.py +21 -118
  3. swift/account/backend.py +33 -28
  4. swift/account/reaper.py +37 -28
  5. swift/account/replicator.py +22 -0
  6. swift/account/server.py +60 -26
  7. swift/account/utils.py +28 -11
  8. swift-2.23.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +154 -14
  17. swift/cli/manage_shard_ranges.py +705 -37
  18. swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.3.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  23. swift/cli/relinker.py +807 -126
  24. swift/cli/reload.py +135 -0
  25. swift/cli/ringbuilder.py +217 -20
  26. swift/cli/ringcomposer.py +0 -1
  27. swift/cli/shard-info.py +4 -3
  28. swift/common/base_storage_server.py +9 -20
  29. swift/common/bufferedhttp.py +48 -74
  30. swift/common/constraints.py +20 -15
  31. swift/common/container_sync_realms.py +9 -11
  32. swift/common/daemon.py +25 -8
  33. swift/common/db.py +195 -128
  34. swift/common/db_auditor.py +168 -0
  35. swift/common/db_replicator.py +95 -55
  36. swift/common/digest.py +141 -0
  37. swift/common/direct_client.py +144 -33
  38. swift/common/error_limiter.py +93 -0
  39. swift/common/exceptions.py +25 -1
  40. swift/common/header_key_dict.py +2 -9
  41. swift/common/http_protocol.py +373 -0
  42. swift/common/internal_client.py +129 -59
  43. swift/common/linkat.py +3 -4
  44. swift/common/manager.py +284 -67
  45. swift/common/memcached.py +390 -145
  46. swift/common/middleware/__init__.py +4 -0
  47. swift/common/middleware/account_quotas.py +211 -46
  48. swift/common/middleware/acl.py +3 -8
  49. swift/common/middleware/backend_ratelimit.py +230 -0
  50. swift/common/middleware/bulk.py +22 -34
  51. swift/common/middleware/catch_errors.py +1 -3
  52. swift/common/middleware/cname_lookup.py +6 -11
  53. swift/common/middleware/container_quotas.py +1 -1
  54. swift/common/middleware/container_sync.py +39 -17
  55. swift/common/middleware/copy.py +12 -0
  56. swift/common/middleware/crossdomain.py +22 -9
  57. swift/common/middleware/crypto/__init__.py +2 -1
  58. swift/common/middleware/crypto/crypto_utils.py +11 -15
  59. swift/common/middleware/crypto/decrypter.py +28 -11
  60. swift/common/middleware/crypto/encrypter.py +12 -17
  61. swift/common/middleware/crypto/keymaster.py +8 -15
  62. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  63. swift/common/middleware/dlo.py +15 -11
  64. swift/common/middleware/domain_remap.py +5 -4
  65. swift/common/middleware/etag_quoter.py +128 -0
  66. swift/common/middleware/formpost.py +73 -70
  67. swift/common/middleware/gatekeeper.py +8 -1
  68. swift/common/middleware/keystoneauth.py +33 -3
  69. swift/common/middleware/list_endpoints.py +4 -4
  70. swift/common/middleware/listing_formats.py +85 -49
  71. swift/common/middleware/memcache.py +4 -95
  72. swift/common/middleware/name_check.py +3 -2
  73. swift/common/middleware/proxy_logging.py +160 -92
  74. swift/common/middleware/ratelimit.py +17 -10
  75. swift/common/middleware/read_only.py +6 -4
  76. swift/common/middleware/recon.py +59 -22
  77. swift/common/middleware/s3api/acl_handlers.py +25 -3
  78. swift/common/middleware/s3api/acl_utils.py +6 -1
  79. swift/common/middleware/s3api/controllers/__init__.py +6 -0
  80. swift/common/middleware/s3api/controllers/acl.py +3 -2
  81. swift/common/middleware/s3api/controllers/bucket.py +242 -137
  82. swift/common/middleware/s3api/controllers/logging.py +2 -2
  83. swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
  84. swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
  85. swift/common/middleware/s3api/controllers/obj.py +112 -8
  86. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  87. swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
  88. swift/common/middleware/s3api/controllers/tagging.py +57 -0
  89. swift/common/middleware/s3api/controllers/versioning.py +36 -7
  90. swift/common/middleware/s3api/etree.py +22 -9
  91. swift/common/middleware/s3api/exception.py +0 -4
  92. swift/common/middleware/s3api/s3api.py +113 -41
  93. swift/common/middleware/s3api/s3request.py +384 -218
  94. swift/common/middleware/s3api/s3response.py +126 -23
  95. swift/common/middleware/s3api/s3token.py +16 -17
  96. swift/common/middleware/s3api/schema/delete.rng +1 -1
  97. swift/common/middleware/s3api/subresource.py +7 -10
  98. swift/common/middleware/s3api/utils.py +27 -10
  99. swift/common/middleware/slo.py +665 -358
  100. swift/common/middleware/staticweb.py +64 -37
  101. swift/common/middleware/symlink.py +51 -18
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +191 -173
  104. swift/common/middleware/versioned_writes/__init__.py +51 -0
  105. swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
  106. swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
  107. swift/common/middleware/x_profile/exceptions.py +1 -4
  108. swift/common/middleware/x_profile/html_viewer.py +18 -19
  109. swift/common/middleware/x_profile/profile_model.py +1 -2
  110. swift/common/middleware/xprofile.py +10 -10
  111. swift-2.23.3.data/scripts/swift-container-server → swift/common/recon.py +13 -8
  112. swift/common/registry.py +147 -0
  113. swift/common/request_helpers.py +324 -57
  114. swift/common/ring/builder.py +67 -25
  115. swift/common/ring/composite_builder.py +1 -1
  116. swift/common/ring/ring.py +177 -51
  117. swift/common/ring/utils.py +1 -1
  118. swift/common/splice.py +10 -6
  119. swift/common/statsd_client.py +205 -0
  120. swift/common/storage_policy.py +49 -44
  121. swift/common/swob.py +86 -102
  122. swift/common/{utils.py → utils/__init__.py} +2163 -2772
  123. swift/common/utils/base.py +131 -0
  124. swift/common/utils/config.py +433 -0
  125. swift/common/utils/ipaddrs.py +256 -0
  126. swift/common/utils/libc.py +345 -0
  127. swift/common/utils/logs.py +859 -0
  128. swift/common/utils/timestamp.py +412 -0
  129. swift/common/wsgi.py +553 -535
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +490 -231
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +358 -165
  135. swift/container/sharder.py +1540 -684
  136. swift/container/sync.py +94 -88
  137. swift/container/updater.py +53 -32
  138. swift/obj/auditor.py +153 -35
  139. swift/obj/diskfile.py +466 -217
  140. swift/obj/expirer.py +406 -124
  141. swift/obj/mem_diskfile.py +7 -4
  142. swift/obj/mem_server.py +1 -0
  143. swift/obj/reconstructor.py +523 -262
  144. swift/obj/replicator.py +249 -188
  145. swift/obj/server.py +207 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +652 -139
  149. swift/obj/watchers/__init__.py +0 -0
  150. swift/obj/watchers/dark_data.py +213 -0
  151. swift/proxy/controllers/account.py +11 -11
  152. swift/proxy/controllers/base.py +848 -604
  153. swift/proxy/controllers/container.py +433 -92
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1000 -489
  156. swift/proxy/server.py +185 -112
  157. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
  158. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
  159. swift-2.35.0.dist-info/RECORD +201 -0
  160. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  161. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  162. swift-2.35.0.dist-info/pbr.json +1 -0
  163. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  164. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  165. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  166. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  167. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  168. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  169. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  170. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  171. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  172. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  173. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  174. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  175. swift-2.23.3.data/scripts/swift-account-auditor +0 -23
  176. swift-2.23.3.data/scripts/swift-account-info +0 -51
  177. swift-2.23.3.data/scripts/swift-account-reaper +0 -23
  178. swift-2.23.3.data/scripts/swift-account-replicator +0 -34
  179. swift-2.23.3.data/scripts/swift-account-server +0 -23
  180. swift-2.23.3.data/scripts/swift-container-auditor +0 -23
  181. swift-2.23.3.data/scripts/swift-container-info +0 -55
  182. swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
  183. swift-2.23.3.data/scripts/swift-container-replicator +0 -34
  184. swift-2.23.3.data/scripts/swift-container-sharder +0 -37
  185. swift-2.23.3.data/scripts/swift-container-sync +0 -23
  186. swift-2.23.3.data/scripts/swift-container-updater +0 -23
  187. swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
  188. swift-2.23.3.data/scripts/swift-form-signature +0 -20
  189. swift-2.23.3.data/scripts/swift-init +0 -119
  190. swift-2.23.3.data/scripts/swift-object-auditor +0 -29
  191. swift-2.23.3.data/scripts/swift-object-expirer +0 -33
  192. swift-2.23.3.data/scripts/swift-object-info +0 -60
  193. swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
  194. swift-2.23.3.data/scripts/swift-object-relinker +0 -41
  195. swift-2.23.3.data/scripts/swift-object-replicator +0 -37
  196. swift-2.23.3.data/scripts/swift-object-server +0 -27
  197. swift-2.23.3.data/scripts/swift-object-updater +0 -23
  198. swift-2.23.3.data/scripts/swift-proxy-server +0 -23
  199. swift-2.23.3.data/scripts/swift-recon +0 -24
  200. swift-2.23.3.data/scripts/swift-ring-builder +0 -24
  201. swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
  202. swift-2.23.3.data/scripts/swift-ring-composer +0 -22
  203. swift-2.23.3.dist-info/RECORD +0 -220
  204. swift-2.23.3.dist-info/pbr.json +0 -1
  205. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
  206. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
swift/obj/server.py CHANGED
@@ -15,52 +15,53 @@
15
15
 
16
16
  """ Object Server for Swift """
17
17
 
18
- import six
19
- import six.moves.cPickle as pickle
20
- from six.moves.urllib.parse import unquote
18
+ import pickle # nosec: B403
19
+ from urllib.parse import unquote
21
20
  import json
22
21
  import os
23
22
  import multiprocessing
23
+ import sys
24
24
  import time
25
25
  import traceback
26
26
  import socket
27
- import math
28
- from swift import gettext_ as _
29
- from hashlib import md5
30
27
 
31
28
  from eventlet import sleep, wsgi, Timeout, tpool
32
29
  from eventlet.greenthread import spawn
33
30
 
34
31
  from swift.common.utils import public, get_logger, \
35
- config_true_value, timing_stats, replication, \
32
+ config_true_value, config_percent_value, timing_stats, replication, \
36
33
  normalize_delete_at_timestamp, get_log_line, Timestamp, \
37
- get_expirer_container, parse_mime_headers, \
34
+ parse_mime_headers, \
38
35
  iter_multipart_mime_documents, extract_swift_bytes, safe_json_loads, \
39
- config_auto_int_value, split_path, get_redirect_data, normalize_timestamp
36
+ config_auto_int_value, split_path, get_redirect_data, \
37
+ normalize_timestamp, md5, parse_options, CooperativeIterator
40
38
  from swift.common.bufferedhttp import http_connect
41
39
  from swift.common.constraints import check_object_creation, \
42
- valid_timestamp, check_utf8
40
+ valid_timestamp, check_utf8, AUTO_CREATE_ACCOUNT_PREFIX
43
41
  from swift.common.exceptions import ConnectionTimeout, DiskFileQuarantined, \
44
42
  DiskFileNotExist, DiskFileCollision, DiskFileNoSpace, DiskFileDeleted, \
45
43
  DiskFileDeviceUnavailable, DiskFileExpired, ChunkReadTimeout, \
46
44
  ChunkReadError, DiskFileXattrNotSupported
47
- from swift.common.request_helpers import \
45
+ from swift.common.request_helpers import resolve_ignore_range_header, \
48
46
  OBJECT_SYSMETA_CONTAINER_UPDATE_OVERRIDE_PREFIX
49
- from swift.obj import ssync_receiver
47
+ from swift.obj import ssync_receiver, expirer
50
48
  from swift.common.http import is_success, HTTP_MOVED_PERMANENTLY
51
49
  from swift.common.base_storage_server import BaseStorageServer
52
50
  from swift.common.header_key_dict import HeaderKeyDict
53
51
  from swift.common.request_helpers import get_name_and_placement, \
54
52
  is_user_meta, is_sys_or_user_meta, is_object_transient_sysmeta, \
55
- resolve_etag_is_at_header, is_sys_meta
53
+ resolve_etag_is_at_header, is_sys_meta, validate_internal_obj, \
54
+ is_backend_open_expired
56
55
  from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPCreated, \
57
56
  HTTPInternalServerError, HTTPNoContent, HTTPNotFound, \
58
57
  HTTPPreconditionFailed, HTTPRequestTimeout, HTTPUnprocessableEntity, \
59
58
  HTTPClientDisconnect, HTTPMethodNotAllowed, Request, Response, \
60
59
  HTTPInsufficientStorage, HTTPForbidden, HTTPException, HTTPConflict, \
61
- HTTPServerError, wsgi_to_bytes, wsgi_to_str
60
+ HTTPServerError, bytes_to_wsgi, wsgi_to_bytes, wsgi_to_str, normalize_etag
61
+ from swift.common.wsgi import run_wsgi
62
62
  from swift.obj.diskfile import RESERVED_DATAFILE_META, DiskFileRouter
63
- from swift.obj.expirer import build_task_obj
63
+ from swift.obj.expirer import build_task_obj, embed_expirer_bytes_in_ctype, \
64
+ X_DELETE_TYPE
64
65
 
65
66
 
66
67
  def iter_mime_headers_and_bodies(wsgi_input, mime_boundary, read_chunk_size):
@@ -90,6 +91,20 @@ def drain(file_like, read_size, timeout):
90
91
  break
91
92
 
92
93
 
94
+ def get_obj_name_and_placement(request):
95
+ """
96
+ Split and validate path for an object.
97
+
98
+ :param request: a swob request
99
+
100
+ :returns: a tuple of path parts and storage policy
101
+ """
102
+ device, partition, account, container, obj, policy = \
103
+ get_name_and_placement(request, 5, 5, True)
104
+ validate_internal_obj(account, container, obj)
105
+ return device, partition, account, container, obj, policy
106
+
107
+
93
108
  def _make_backend_fragments_header(fragments):
94
109
  if fragments:
95
110
  result = {}
@@ -130,7 +145,7 @@ class ObjectController(BaseStorageServer):
130
145
  self.container_update_timeout = float(
131
146
  conf.get('container_update_timeout', 1))
132
147
  self.conn_timeout = float(conf.get('conn_timeout', 0.5))
133
- self.client_timeout = int(conf.get('client_timeout', 60))
148
+ self.client_timeout = float(conf.get('client_timeout', 60))
134
149
  self.disk_chunk_size = int(conf.get('disk_chunk_size', 65536))
135
150
  self.network_chunk_size = int(conf.get('network_chunk_size', 65536))
136
151
  self.log_requests = config_true_value(conf.get('log_requests', 'true'))
@@ -138,6 +153,11 @@ class ObjectController(BaseStorageServer):
138
153
  self.slow = int(conf.get('slow', 0))
139
154
  self.keep_cache_private = \
140
155
  config_true_value(conf.get('keep_cache_private', 'false'))
156
+ self.keep_cache_slo_manifest = \
157
+ config_true_value(conf.get('keep_cache_slo_manifest', 'false'))
158
+ self.cooperative_period = int(conf.get("cooperative_period", 0))
159
+ self.etag_validate_frac = config_percent_value(
160
+ conf.get("etag_validate_pct", 100))
141
161
 
142
162
  default_allowed_headers = '''
143
163
  content-disposition,
@@ -159,12 +179,9 @@ class ObjectController(BaseStorageServer):
159
179
  for header in extra_allowed_headers:
160
180
  if header not in RESERVED_DATAFILE_META:
161
181
  self.allowed_headers.add(header)
162
- self.auto_create_account_prefix = \
163
- conf.get('auto_create_account_prefix') or '.'
164
- self.expiring_objects_account = self.auto_create_account_prefix + \
165
- (conf.get('expiring_objects_account_name') or 'expiring_objects')
166
- self.expiring_objects_container_divisor = \
167
- int(conf.get('expiring_objects_container_divisor') or 86400)
182
+
183
+ self.auto_create_account_prefix = AUTO_CREATE_ACCOUNT_PREFIX
184
+ self.expirer_config = expirer.ExpirerConfig(conf, logger=self.logger)
168
185
  # Initialization was successful, so now apply the network chunk size
169
186
  # parameter as the default read / write buffer size for the network
170
187
  # sockets.
@@ -179,8 +196,8 @@ class ObjectController(BaseStorageServer):
179
196
  # disk_chunk_size parameter. However, it affects all created sockets
180
197
  # using this class so we have chosen to tie it to the
181
198
  # network_chunk_size parameter value instead.
182
- if six.PY2:
183
- socket._fileobject.default_bufsize = self.network_chunk_size
199
+ # if six.PY2:
200
+ # socket._fileobject.default_bufsize = self.network_chunk_size
184
201
  # TODO: find a way to enable similar functionality in py3
185
202
 
186
203
  # Provide further setup specific to an object server implementation.
@@ -253,7 +270,8 @@ class ObjectController(BaseStorageServer):
253
270
 
254
271
  def async_update(self, op, account, container, obj, host, partition,
255
272
  contdevice, headers_out, objdevice, policy,
256
- logger_thread_locals=None, container_path=None):
273
+ logger_thread_locals=None, container_path=None,
274
+ db_state=None):
257
275
  """
258
276
  Sends or saves an async update.
259
277
 
@@ -275,6 +293,8 @@ class ObjectController(BaseStorageServer):
275
293
  to which the update should be sent. If given this path will be used
276
294
  instead of constructing a path from the ``account`` and
277
295
  ``container`` params.
296
+ :param db_state: The current database state of the container as
297
+ supplied to us by the proxy.
278
298
  """
279
299
  if logger_thread_locals:
280
300
  self.logger.thread_locals = logger_thread_locals
@@ -306,19 +326,19 @@ class ObjectController(BaseStorageServer):
306
326
  'Container update failed for %r; problem with '
307
327
  'redirect location: %s' % (obj, err))
308
328
  else:
309
- self.logger.error(_(
329
+ self.logger.error(
310
330
  'ERROR Container update failed '
311
331
  '(saving for async update later): %(status)d '
312
- 'response from %(ip)s:%(port)s/%(dev)s'),
332
+ 'response from %(ip)s:%(port)s/%(dev)s',
313
333
  {'status': response.status, 'ip': ip, 'port': port,
314
334
  'dev': contdevice})
315
335
  except (Exception, Timeout):
316
- self.logger.exception(_(
336
+ self.logger.exception(
317
337
  'ERROR container update failed with '
318
- '%(ip)s:%(port)s/%(dev)s (saving for async update later)'),
338
+ '%(ip)s:%(port)s/%(dev)s (saving for async update later)',
319
339
  {'ip': ip, 'port': port, 'dev': contdevice})
320
340
  data = {'op': op, 'account': account, 'container': container,
321
- 'obj': obj, 'headers': headers_out}
341
+ 'obj': obj, 'headers': headers_out, 'db_state': db_state}
322
342
  if redirect_data:
323
343
  self.logger.debug(
324
344
  'Update to %(path)s redirected to %(redirect)s',
@@ -352,14 +372,15 @@ class ObjectController(BaseStorageServer):
352
372
  contdevices = [d.strip() for d in
353
373
  headers_in.get('X-Container-Device', '').split(',')]
354
374
  contpartition = headers_in.get('X-Container-Partition', '')
375
+ contdbstate = headers_in.get('X-Container-Root-Db-State')
355
376
 
356
377
  if len(conthosts) != len(contdevices):
357
378
  # This shouldn't happen unless there's a bug in the proxy,
358
379
  # but if there is, we want to know about it.
359
- self.logger.error(_(
380
+ self.logger.error(
360
381
  'ERROR Container update failed: different '
361
382
  'numbers of hosts and devices in request: '
362
- '"%(hosts)s" vs "%(devices)s"') % {
383
+ '"%(hosts)s" vs "%(devices)s"', {
363
384
  'hosts': headers_in.get('X-Container-Host', ''),
364
385
  'devices': headers_in.get('X-Container-Device', '')})
365
386
  return
@@ -402,7 +423,7 @@ class ObjectController(BaseStorageServer):
402
423
  conthost, contpartition, contdevice, headers_out,
403
424
  objdevice, policy,
404
425
  logger_thread_locals=self.logger.thread_locals,
405
- container_path=contpath)
426
+ container_path=contpath, db_state=contdbstate)
406
427
  update_greenthreads.append(gt)
407
428
  # Wait a little bit to see if the container updates are successful.
408
429
  # If we immediately return after firing off the greenthread above, then
@@ -421,7 +442,7 @@ class ObjectController(BaseStorageServer):
421
442
  self.container_update_timeout, updates)
422
443
 
423
444
  def delete_at_update(self, op, delete_at, account, container, obj,
424
- request, objdevice, policy):
445
+ request, objdevice, policy, extra_headers=None):
425
446
  """
426
447
  Update the expiring objects container when objects are updated.
427
448
 
@@ -433,15 +454,14 @@ class ObjectController(BaseStorageServer):
433
454
  :param request: the original request driving the update
434
455
  :param objdevice: device name that the object is in
435
456
  :param policy: the BaseStoragePolicy instance (used for tmp dir)
457
+ :param extra_headers: dict of additional headers for the update
436
458
  """
437
459
  if config_true_value(
438
460
  request.headers.get('x-backend-replication', 'f')):
439
461
  return
462
+
440
463
  delete_at = normalize_delete_at_timestamp(delete_at)
441
- updates = [(None, None)]
442
464
 
443
- partition = None
444
- hosts = contdevices = [None]
445
465
  headers_in = request.headers
446
466
  headers_out = HeaderKeyDict({
447
467
  # system accounts are always Policy-0
@@ -449,26 +469,42 @@ class ObjectController(BaseStorageServer):
449
469
  'x-timestamp': request.timestamp.internal,
450
470
  'x-trans-id': headers_in.get('x-trans-id', '-'),
451
471
  'referer': request.as_referer()})
472
+
473
+ expiring_objects_account_name, delete_at_container = \
474
+ self.expirer_config.get_expirer_account_and_container(
475
+ delete_at, account, container, obj)
452
476
  if op != 'DELETE':
453
477
  hosts = headers_in.get('X-Delete-At-Host', None)
454
478
  if hosts is None:
455
479
  # If header is missing, no update needed as sufficient other
456
480
  # object servers should perform the required update.
457
481
  return
458
- delete_at_container = headers_in.get('X-Delete-At-Container', None)
459
- if not delete_at_container:
460
- # older proxy servers did not send X-Delete-At-Container so for
461
- # backwards compatibility calculate the value here, but also
462
- # log a warning because this is prone to inconsistent
463
- # expiring_objects_container_divisor configurations.
464
- # See https://bugs.launchpad.net/swift/+bug/1187200
465
- self.logger.warning(
466
- 'X-Delete-At-Container header must be specified for '
467
- 'expiring objects background %s to work properly. Making '
468
- 'best guess as to the container name for now.' % op)
469
- delete_at_container = get_expirer_container(
470
- delete_at, self.expiring_objects_container_divisor,
471
- account, container, obj)
482
+
483
+ proxy_delete_at_container = headers_in.get(
484
+ 'X-Delete-At-Container', None)
485
+ if delete_at_container != proxy_delete_at_container:
486
+ if not proxy_delete_at_container:
487
+ # We carry this warning around for pre-2013 proxies
488
+ self.logger.warning(
489
+ 'X-Delete-At-Container header must be specified for '
490
+ 'expiring objects background %s to work properly. '
491
+ 'Making best guess as to the container name '
492
+ 'for now.', op)
493
+ proxy_delete_at_container = delete_at_container
494
+ else:
495
+ # Inconsistent configuration may lead to orphaned expirer
496
+ # task queue objects when X-Delete-At is updated, which can
497
+ # stick around for a whole reclaim age.
498
+ self.logger.debug(
499
+ 'Proxy X-Delete-At-Container %r does not match '
500
+ 'expected %r for current expirer_config.',
501
+ proxy_delete_at_container, delete_at_container)
502
+ # it's not possible to say which is "more correct", this will
503
+ # at least match the host/part/device
504
+ delete_at_container = normalize_delete_at_timestamp(
505
+ proxy_delete_at_container)
506
+
507
+ # new updates need to enqueue new x-delete-at
472
508
  partition = headers_in.get('X-Delete-At-Partition', None)
473
509
  contdevices = headers_in.get('X-Delete-At-Device', '')
474
510
  updates = [upd for upd in
@@ -478,30 +514,22 @@ class ObjectController(BaseStorageServer):
478
514
  if not updates:
479
515
  updates = [(None, None)]
480
516
  headers_out['x-size'] = '0'
481
- headers_out['x-content-type'] = 'text/plain'
517
+ headers_out['x-content-type'] = X_DELETE_TYPE
482
518
  headers_out['x-etag'] = 'd41d8cd98f00b204e9800998ecf8427e'
519
+ if extra_headers:
520
+ headers_out.update(extra_headers)
483
521
  else:
484
522
  if not config_true_value(
485
523
  request.headers.get(
486
524
  'X-Backend-Clean-Expiring-Object-Queue', 't')):
487
525
  return
488
-
489
- # DELETEs of old expiration data have no way of knowing what the
490
- # old X-Delete-At-Container was at the time of the initial setting
491
- # of the data, so a best guess is made here.
492
- # Worst case is a DELETE is issued now for something that doesn't
493
- # exist there and the original data is left where it is, where
494
- # it will be ignored when the expirer eventually tries to issue the
495
- # object DELETE later since the X-Delete-At value won't match up.
496
- delete_at_container = get_expirer_container(
497
- delete_at, self.expiring_objects_container_divisor,
498
- account, container, obj)
499
- delete_at_container = normalize_delete_at_timestamp(
500
- delete_at_container)
526
+ # DELETE op always go directly to async_pending
527
+ partition = None
528
+ updates = [(None, None)]
501
529
 
502
530
  for host, contdevice in updates:
503
531
  self.async_update(
504
- op, self.expiring_objects_account, delete_at_container,
532
+ op, expiring_objects_account_name, delete_at_container,
505
533
  build_task_obj(delete_at, account, container, obj),
506
534
  host, partition, contdevice, headers_out, objdevice,
507
535
  policy)
@@ -558,7 +586,7 @@ class ObjectController(BaseStorageServer):
558
586
  footer_md5 = footer_hdrs.get('Content-MD5')
559
587
  if not footer_md5:
560
588
  raise HTTPBadRequest(body="no Content-MD5 in footer")
561
- if footer_md5 != md5(footer_body).hexdigest():
589
+ if footer_md5 != md5(footer_body, usedforsecurity=False).hexdigest():
562
590
  raise HTTPUnprocessableEntity(body="footer MD5 mismatch")
563
591
 
564
592
  try:
@@ -604,12 +632,31 @@ class ObjectController(BaseStorageServer):
604
632
  override = key.lower().replace(override_prefix, 'x-')
605
633
  update_headers[override] = val
606
634
 
635
+ def _conditional_delete_at_update(self, request, device, account,
636
+ container, obj, policy, metadata,
637
+ orig_delete_at, new_delete_at):
638
+ if new_delete_at:
639
+ extra_headers = {
640
+ 'x-content-type': embed_expirer_bytes_in_ctype(
641
+ X_DELETE_TYPE, metadata),
642
+ 'x-content-type-timestamp':
643
+ metadata.get('X-Timestamp'),
644
+ }
645
+ self.delete_at_update(
646
+ 'PUT', new_delete_at, account, container, obj, request,
647
+ device, policy, extra_headers)
648
+ if orig_delete_at and orig_delete_at != new_delete_at:
649
+ self.delete_at_update(
650
+ 'DELETE', orig_delete_at, account, container, obj,
651
+ request, device, policy)
652
+
607
653
  @public
608
654
  @timing_stats()
609
655
  def POST(self, request):
610
656
  """Handle HTTP POST requests for the Swift Object Server."""
611
657
  device, partition, account, container, obj, policy = \
612
- get_name_and_placement(request, 5, 5, True)
658
+ get_obj_name_and_placement(request)
659
+
613
660
  req_timestamp = valid_timestamp(request)
614
661
  new_delete_at = int(request.headers.get('X-Delete-At') or 0)
615
662
  if new_delete_at and new_delete_at < req_timestamp:
@@ -619,8 +666,7 @@ class ObjectController(BaseStorageServer):
619
666
  try:
620
667
  disk_file = self.get_diskfile(
621
668
  device, partition, account, container, obj,
622
- policy=policy, open_expired=config_true_value(
623
- request.headers.get('x-backend-replication', 'false')),
669
+ policy=policy, open_expired=is_backend_open_expired(request),
624
670
  next_part_power=next_part_power)
625
671
  except DiskFileDeviceUnavailable:
626
672
  return HTTPInsufficientStorage(drive=device, request=request)
@@ -655,18 +701,15 @@ class ObjectController(BaseStorageServer):
655
701
  list(self.allowed_headers))
656
702
  for header_key in headers_to_copy:
657
703
  if header_key in request.headers:
658
- header_caps = header_key.title()
704
+ header_caps = bytes_to_wsgi(
705
+ wsgi_to_bytes(header_key).title())
659
706
  metadata[header_caps] = request.headers[header_key]
660
707
  orig_delete_at = int(orig_metadata.get('X-Delete-At') or 0)
661
- if orig_delete_at != new_delete_at:
662
- if new_delete_at:
663
- self.delete_at_update(
664
- 'PUT', new_delete_at, account, container, obj, request,
665
- device, policy)
666
- if orig_delete_at:
667
- self.delete_at_update('DELETE', orig_delete_at, account,
668
- container, obj, request, device,
669
- policy)
708
+ disk_file_metadata = disk_file.get_datafile_metadata()
709
+ self._conditional_delete_at_update(
710
+ request, device, account, container, obj, policy,
711
+ disk_file_metadata, orig_delete_at, new_delete_at
712
+ )
670
713
  else:
671
714
  # preserve existing metadata, only content-type may be updated
672
715
  metadata = dict(disk_file.get_metafile_metadata())
@@ -738,8 +781,9 @@ class ObjectController(BaseStorageServer):
738
781
  'PUT', account, container, obj, request, update_headers,
739
782
  device, policy)
740
783
 
741
- # Add sysmeta to response
742
- resp_headers = {}
784
+ # Add current content-type and sysmeta to response
785
+ resp_headers = {
786
+ 'X-Backend-Content-Type': content_type_headers['Content-Type']}
743
787
  for key, value in orig_metadata.items():
744
788
  if is_sys_meta('object', key):
745
789
  resp_headers[key] = value
@@ -866,13 +910,20 @@ class ObjectController(BaseStorageServer):
866
910
  elapsed_time = 0
867
911
  upload_expiration = time.time() + self.max_upload_time
868
912
  timeout_reader = self._make_timeout_reader(obj_input)
869
- for chunk in iter(timeout_reader, b''):
913
+
914
+ # Wrap the chunks in CooperativeIterator with specified period
915
+ cooperative_reader = CooperativeIterator(
916
+ iter(timeout_reader, b''), period=self.cooperative_period
917
+ )
918
+
919
+ for chunk in cooperative_reader:
870
920
  start_time = time.time()
871
921
  if start_time > upload_expiration:
872
922
  self.logger.increment('PUT.timeouts')
873
923
  raise HTTPRequestTimeout(request=request)
874
924
  writer.write(chunk)
875
925
  elapsed_time += time.time() - start_time
926
+
876
927
  upload_size, etag = writer.chunks_finished()
877
928
  if fsize is not None and fsize != upload_size:
878
929
  raise HTTPClientDisconnect(request=request)
@@ -903,7 +954,8 @@ class ObjectController(BaseStorageServer):
903
954
  list(self.allowed_headers))
904
955
  for header_key in headers_to_copy:
905
956
  if header_key in request.headers:
906
- header_caps = header_key.title()
957
+ header_caps = bytes_to_wsgi(
958
+ wsgi_to_bytes(header_key).title())
907
959
  metadata[header_caps] = request.headers[header_key]
908
960
  return metadata
909
961
 
@@ -932,8 +984,8 @@ class ObjectController(BaseStorageServer):
932
984
  if (is_sys_or_user_meta('object', val[0]) or
933
985
  is_object_transient_sysmeta(val[0])))
934
986
  # N.B. footers_metadata is a HeaderKeyDict
935
- received_etag = footers_metadata.get('etag', request.headers.get(
936
- 'etag', '')).strip('"')
987
+ received_etag = normalize_etag(footers_metadata.get(
988
+ 'etag', request.headers.get('etag', '')))
937
989
  if received_etag and received_etag != metadata['ETag']:
938
990
  raise HTTPUnprocessableEntity(request=request)
939
991
 
@@ -974,15 +1026,10 @@ class ObjectController(BaseStorageServer):
974
1026
  orig_metadata, footers_metadata, metadata):
975
1027
  orig_delete_at = int(orig_metadata.get('X-Delete-At') or 0)
976
1028
  new_delete_at = int(request.headers.get('X-Delete-At') or 0)
977
- if orig_delete_at != new_delete_at:
978
- if new_delete_at:
979
- self.delete_at_update(
980
- 'PUT', new_delete_at, account, container, obj, request,
981
- device, policy)
982
- if orig_delete_at:
983
- self.delete_at_update(
984
- 'DELETE', orig_delete_at, account, container, obj,
985
- request, device, policy)
1029
+
1030
+ self._conditional_delete_at_update(request, device, account, container,
1031
+ obj, policy, metadata,
1032
+ orig_delete_at, new_delete_at)
986
1033
 
987
1034
  update_headers = HeaderKeyDict({
988
1035
  'x-size': metadata['Content-Length'],
@@ -1001,7 +1048,7 @@ class ObjectController(BaseStorageServer):
1001
1048
  def PUT(self, request):
1002
1049
  """Handle HTTP PUT requests for the Swift Object Server."""
1003
1050
  device, partition, account, container, obj, policy = \
1004
- get_name_and_placement(request, 5, 5, True)
1051
+ get_obj_name_and_placement(request)
1005
1052
  disk_file, fsize, orig_metadata = self._pre_create_checks(
1006
1053
  request, device, partition, account, container, obj, policy)
1007
1054
  writer = disk_file.writer(size=fsize)
@@ -1022,7 +1069,9 @@ class ObjectController(BaseStorageServer):
1022
1069
  if multi_stage_mime_state:
1023
1070
  self._send_multi_stage_continue_headers(
1024
1071
  request, **multi_stage_mime_state)
1025
- writer.commit(request.timestamp)
1072
+ if not config_true_value(
1073
+ request.headers.get('X-Backend-No-Commit', False)):
1074
+ writer.commit(request.timestamp)
1026
1075
  if multi_stage_mime_state:
1027
1076
  self._drain_mime_request(**multi_stage_mime_state)
1028
1077
  except (DiskFileXattrNotSupported, DiskFileNoSpace):
@@ -1043,7 +1092,7 @@ class ObjectController(BaseStorageServer):
1043
1092
  def GET(self, request):
1044
1093
  """Handle HTTP GET requests for the Swift Object Server."""
1045
1094
  device, partition, account, container, obj, policy = \
1046
- get_name_and_placement(request, 5, 5, True)
1095
+ get_obj_name_and_placement(request)
1047
1096
  request.headers.setdefault('X-Timestamp',
1048
1097
  normalize_timestamp(time.time()))
1049
1098
  req_timestamp = valid_timestamp(request)
@@ -1053,23 +1102,39 @@ class ObjectController(BaseStorageServer):
1053
1102
  disk_file = self.get_diskfile(
1054
1103
  device, partition, account, container, obj,
1055
1104
  policy=policy, frag_prefs=frag_prefs,
1056
- open_expired=config_true_value(
1057
- request.headers.get('x-backend-replication', 'false')))
1105
+ open_expired=is_backend_open_expired(request))
1058
1106
  except DiskFileDeviceUnavailable:
1059
1107
  return HTTPInsufficientStorage(drive=device, request=request)
1060
1108
  try:
1061
1109
  with disk_file.open(current_time=req_timestamp):
1062
1110
  metadata = disk_file.get_metadata()
1111
+ resolve_ignore_range_header(request, metadata)
1063
1112
  obj_size = int(metadata['Content-Length'])
1064
1113
  file_x_ts = Timestamp(metadata['X-Timestamp'])
1065
- keep_cache = (self.keep_cache_private or
1066
- ('X-Auth-Token' not in request.headers and
1067
- 'X-Storage-Token' not in request.headers))
1114
+ keep_cache = (
1115
+ self.keep_cache_private
1116
+ or (
1117
+ "X-Auth-Token" not in request.headers
1118
+ and "X-Storage-Token" not in request.headers
1119
+ )
1120
+ or (
1121
+ self.keep_cache_slo_manifest
1122
+ and config_true_value(
1123
+ metadata.get("X-Static-Large-Object")
1124
+ )
1125
+ )
1126
+ )
1068
1127
  conditional_etag = resolve_etag_is_at_header(request, metadata)
1128
+ app_iter = disk_file.reader(
1129
+ keep_cache=keep_cache,
1130
+ cooperative_period=self.cooperative_period,
1131
+ etag_validate_frac=self.etag_validate_frac,
1132
+ )
1069
1133
  response = Response(
1070
- app_iter=disk_file.reader(keep_cache=keep_cache),
1071
- request=request, conditional_response=True,
1072
- conditional_etag=conditional_etag)
1134
+ app_iter=app_iter, request=request,
1135
+ conditional_response=True,
1136
+ conditional_etag=conditional_etag,
1137
+ )
1073
1138
  response.headers['Content-Type'] = metadata.get(
1074
1139
  'Content-Type', 'application/octet-stream')
1075
1140
  for key, value in metadata.items():
@@ -1078,7 +1143,7 @@ class ObjectController(BaseStorageServer):
1078
1143
  key.lower() in self.allowed_headers):
1079
1144
  response.headers[key] = value
1080
1145
  response.etag = metadata['ETag']
1081
- response.last_modified = math.ceil(float(file_x_ts))
1146
+ response.last_modified = file_x_ts.ceil()
1082
1147
  response.content_length = obj_size
1083
1148
  try:
1084
1149
  response.content_encoding = metadata[
@@ -1110,7 +1175,7 @@ class ObjectController(BaseStorageServer):
1110
1175
  def HEAD(self, request):
1111
1176
  """Handle HTTP HEAD requests for the Swift Object Server."""
1112
1177
  device, partition, account, container, obj, policy = \
1113
- get_name_and_placement(request, 5, 5, True)
1178
+ get_obj_name_and_placement(request)
1114
1179
  request.headers.setdefault('X-Timestamp',
1115
1180
  normalize_timestamp(time.time()))
1116
1181
  req_timestamp = valid_timestamp(request)
@@ -1120,8 +1185,7 @@ class ObjectController(BaseStorageServer):
1120
1185
  disk_file = self.get_diskfile(
1121
1186
  device, partition, account, container, obj,
1122
1187
  policy=policy, frag_prefs=frag_prefs,
1123
- open_expired=config_true_value(
1124
- request.headers.get('x-backend-replication', 'false')))
1188
+ open_expired=is_backend_open_expired(request))
1125
1189
  except DiskFileDeviceUnavailable:
1126
1190
  return HTTPInsufficientStorage(drive=device, request=request)
1127
1191
  try:
@@ -1146,7 +1210,7 @@ class ObjectController(BaseStorageServer):
1146
1210
  response.headers[key] = value
1147
1211
  response.etag = metadata['ETag']
1148
1212
  ts = Timestamp(metadata['X-Timestamp'])
1149
- response.last_modified = math.ceil(float(ts))
1213
+ response.last_modified = ts.ceil()
1150
1214
  # Needed for container sync feature
1151
1215
  response.headers['X-Timestamp'] = ts.normal
1152
1216
  response.headers['X-Backend-Timestamp'] = ts.internal
@@ -1169,7 +1233,7 @@ class ObjectController(BaseStorageServer):
1169
1233
  def DELETE(self, request):
1170
1234
  """Handle HTTP DELETE requests for the Swift Object Server."""
1171
1235
  device, partition, account, container, obj, policy = \
1172
- get_name_and_placement(request, 5, 5, True)
1236
+ get_obj_name_and_placement(request)
1173
1237
  req_timestamp = valid_timestamp(request)
1174
1238
  next_part_power = request.headers.get('X-Backend-Next-Part-Power')
1175
1239
  try:
@@ -1227,10 +1291,10 @@ class ObjectController(BaseStorageServer):
1227
1291
  else:
1228
1292
  # differentiate success from no object at all
1229
1293
  response_class = HTTPNoContent
1230
- if orig_delete_at:
1231
- self.delete_at_update('DELETE', orig_delete_at, account,
1232
- container, obj, request, device,
1233
- policy)
1294
+ self._conditional_delete_at_update(
1295
+ request, device, account, container, obj, policy, {},
1296
+ orig_delete_at, 0
1297
+ )
1234
1298
  if orig_timestamp < req_timestamp:
1235
1299
  try:
1236
1300
  disk_file.delete(req_timestamp)
@@ -1242,7 +1306,9 @@ class ObjectController(BaseStorageServer):
1242
1306
  device, policy)
1243
1307
  return response_class(
1244
1308
  request=request,
1245
- headers={'X-Backend-Timestamp': response_timestamp.internal})
1309
+ headers={'X-Backend-Timestamp': response_timestamp.internal,
1310
+ 'X-Backend-Content-Type': orig_metadata.get(
1311
+ 'Content-Type', '')})
1246
1312
 
1247
1313
  @public
1248
1314
  @replication
@@ -1261,7 +1327,8 @@ class ObjectController(BaseStorageServer):
1261
1327
  suffixes = suffix_parts.split('-') if suffix_parts else []
1262
1328
  try:
1263
1329
  hashes = self._diskfile_router[policy].get_hashes(
1264
- device, partition, suffixes, policy)
1330
+ device, partition, suffixes, policy,
1331
+ skip_rehash=bool(suffixes))
1265
1332
  except DiskFileDeviceUnavailable:
1266
1333
  resp = HTTPInsufficientStorage(drive=device, request=request)
1267
1334
  else:
@@ -1273,7 +1340,14 @@ class ObjectController(BaseStorageServer):
1273
1340
  @replication
1274
1341
  @timing_stats(sample_rate=0.1)
1275
1342
  def SSYNC(self, request):
1276
- return Response(app_iter=ssync_receiver.Receiver(self, request)())
1343
+ # the ssync sender may want to send PUT subrequests for non-durable
1344
+ # data that should not be committed; legacy behaviour has been to
1345
+ # commit all PUTs (subject to EC footer metadata), so we need to
1346
+ # indicate to the sender that this object server has been upgraded to
1347
+ # understand the X-Backend-No-Commit header.
1348
+ headers = {'X-Backend-Accept-No-Commit': True}
1349
+ return Response(app_iter=ssync_receiver.Receiver(self, request)(),
1350
+ headers=headers)
1277
1351
 
1278
1352
  def __call__(self, env, start_response):
1279
1353
  """WSGI Application entry point for the Swift Object Server."""
@@ -1281,7 +1355,7 @@ class ObjectController(BaseStorageServer):
1281
1355
  req = Request(env)
1282
1356
  self.logger.txn_id = req.headers.get('x-trans-id', None)
1283
1357
 
1284
- if not check_utf8(wsgi_to_str(req.path_info)):
1358
+ if not check_utf8(wsgi_to_str(req.path_info), internal=True):
1285
1359
  res = HTTPPreconditionFailed(body='Invalid UTF8 or contains NULL')
1286
1360
  else:
1287
1361
  try:
@@ -1295,9 +1369,9 @@ class ObjectController(BaseStorageServer):
1295
1369
  except HTTPException as error_response:
1296
1370
  res = error_response
1297
1371
  except (Exception, Timeout):
1298
- self.logger.exception(_(
1372
+ self.logger.exception(
1299
1373
  'ERROR __call__ error with %(method)s'
1300
- ' %(path)s '), {'method': req.method, 'path': req.path})
1374
+ ' %(path)s ', {'method': req.method, 'path': req.path})
1301
1375
  res = HTTPInternalServerError(body=traceback.format_exc())
1302
1376
  trans_time = time.time() - start_time
1303
1377
  res.fix_conditional_response()
@@ -1396,3 +1470,14 @@ def app_factory(global_conf, **local_conf):
1396
1470
  conf = global_conf.copy()
1397
1471
  conf.update(local_conf)
1398
1472
  return ObjectController(conf)
1473
+
1474
+
1475
+ def main():
1476
+ conf_file, options = parse_options(test_config=True)
1477
+ sys.exit(run_wsgi(conf_file, 'object-server',
1478
+ global_conf_callback=global_conf_callback,
1479
+ **options))
1480
+
1481
+
1482
+ if __name__ == '__main__':
1483
+ main()