swift 2.23.2__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 (208) 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.2.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.2.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.2.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.2.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.2.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +183 -29
  17. swift/cli/manage_shard_ranges.py +708 -37
  18. swift-2.23.2.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.2.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.2.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.2.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 +198 -127
  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 +396 -147
  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 -81
  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 +52 -19
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +192 -174
  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.2.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} +2191 -2762
  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 +555 -536
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +552 -227
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +397 -176
  135. swift/container/sharder.py +1580 -639
  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 +213 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +653 -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 +452 -86
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1009 -490
  156. swift/proxy/server.py +185 -112
  157. swift-2.35.0.dist-info/AUTHORS +501 -0
  158. swift-2.35.0.dist-info/LICENSE +202 -0
  159. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/METADATA +52 -61
  160. swift-2.35.0.dist-info/RECORD +201 -0
  161. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  162. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  163. swift-2.35.0.dist-info/pbr.json +1 -0
  164. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  165. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  166. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  167. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  168. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  169. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  170. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  171. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  172. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  173. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  174. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  175. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  176. swift-2.23.2.data/scripts/swift-account-auditor +0 -23
  177. swift-2.23.2.data/scripts/swift-account-info +0 -51
  178. swift-2.23.2.data/scripts/swift-account-reaper +0 -23
  179. swift-2.23.2.data/scripts/swift-account-replicator +0 -34
  180. swift-2.23.2.data/scripts/swift-account-server +0 -23
  181. swift-2.23.2.data/scripts/swift-container-auditor +0 -23
  182. swift-2.23.2.data/scripts/swift-container-info +0 -51
  183. swift-2.23.2.data/scripts/swift-container-reconciler +0 -21
  184. swift-2.23.2.data/scripts/swift-container-replicator +0 -34
  185. swift-2.23.2.data/scripts/swift-container-sharder +0 -33
  186. swift-2.23.2.data/scripts/swift-container-sync +0 -23
  187. swift-2.23.2.data/scripts/swift-container-updater +0 -23
  188. swift-2.23.2.data/scripts/swift-dispersion-report +0 -24
  189. swift-2.23.2.data/scripts/swift-form-signature +0 -20
  190. swift-2.23.2.data/scripts/swift-init +0 -119
  191. swift-2.23.2.data/scripts/swift-object-auditor +0 -29
  192. swift-2.23.2.data/scripts/swift-object-expirer +0 -33
  193. swift-2.23.2.data/scripts/swift-object-info +0 -60
  194. swift-2.23.2.data/scripts/swift-object-reconstructor +0 -33
  195. swift-2.23.2.data/scripts/swift-object-relinker +0 -41
  196. swift-2.23.2.data/scripts/swift-object-replicator +0 -37
  197. swift-2.23.2.data/scripts/swift-object-server +0 -27
  198. swift-2.23.2.data/scripts/swift-object-updater +0 -23
  199. swift-2.23.2.data/scripts/swift-proxy-server +0 -23
  200. swift-2.23.2.data/scripts/swift-recon +0 -24
  201. swift-2.23.2.data/scripts/swift-ring-builder +0 -24
  202. swift-2.23.2.data/scripts/swift-ring-builder-analyzer +0 -22
  203. swift-2.23.2.data/scripts/swift-ring-composer +0 -22
  204. swift-2.23.2.dist-info/DESCRIPTION.rst +0 -166
  205. swift-2.23.2.dist-info/RECORD +0 -220
  206. swift-2.23.2.dist-info/metadata.json +0 -1
  207. swift-2.23.2.dist-info/pbr.json +0 -1
  208. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
swift/obj/server.py CHANGED
@@ -15,51 +15,53 @@
15
15
 
16
16
  """ Object Server for Swift """
17
17
 
18
- import six
19
- import six.moves.cPickle as pickle
18
+ import pickle # nosec: B403
19
+ from urllib.parse import unquote
20
20
  import json
21
21
  import os
22
22
  import multiprocessing
23
+ import sys
23
24
  import time
24
25
  import traceback
25
26
  import socket
26
- import math
27
- from swift import gettext_ as _
28
- from hashlib import md5
29
27
 
30
28
  from eventlet import sleep, wsgi, Timeout, tpool
31
29
  from eventlet.greenthread import spawn
32
30
 
33
31
  from swift.common.utils import public, get_logger, \
34
- config_true_value, timing_stats, replication, \
32
+ config_true_value, config_percent_value, timing_stats, replication, \
35
33
  normalize_delete_at_timestamp, get_log_line, Timestamp, \
36
- get_expirer_container, parse_mime_headers, \
34
+ parse_mime_headers, \
37
35
  iter_multipart_mime_documents, extract_swift_bytes, safe_json_loads, \
38
- 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
39
38
  from swift.common.bufferedhttp import http_connect
40
39
  from swift.common.constraints import check_object_creation, \
41
- valid_timestamp, check_utf8
40
+ valid_timestamp, check_utf8, AUTO_CREATE_ACCOUNT_PREFIX
42
41
  from swift.common.exceptions import ConnectionTimeout, DiskFileQuarantined, \
43
42
  DiskFileNotExist, DiskFileCollision, DiskFileNoSpace, DiskFileDeleted, \
44
43
  DiskFileDeviceUnavailable, DiskFileExpired, ChunkReadTimeout, \
45
44
  ChunkReadError, DiskFileXattrNotSupported
46
- from swift.common.request_helpers import \
45
+ from swift.common.request_helpers import resolve_ignore_range_header, \
47
46
  OBJECT_SYSMETA_CONTAINER_UPDATE_OVERRIDE_PREFIX
48
- from swift.obj import ssync_receiver
47
+ from swift.obj import ssync_receiver, expirer
49
48
  from swift.common.http import is_success, HTTP_MOVED_PERMANENTLY
50
49
  from swift.common.base_storage_server import BaseStorageServer
51
50
  from swift.common.header_key_dict import HeaderKeyDict
52
51
  from swift.common.request_helpers import get_name_and_placement, \
53
52
  is_user_meta, is_sys_or_user_meta, is_object_transient_sysmeta, \
54
- 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
55
55
  from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPCreated, \
56
56
  HTTPInternalServerError, HTTPNoContent, HTTPNotFound, \
57
57
  HTTPPreconditionFailed, HTTPRequestTimeout, HTTPUnprocessableEntity, \
58
58
  HTTPClientDisconnect, HTTPMethodNotAllowed, Request, Response, \
59
59
  HTTPInsufficientStorage, HTTPForbidden, HTTPException, HTTPConflict, \
60
- 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
61
62
  from swift.obj.diskfile import RESERVED_DATAFILE_META, DiskFileRouter
62
- 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
63
65
 
64
66
 
65
67
  def iter_mime_headers_and_bodies(wsgi_input, mime_boundary, read_chunk_size):
@@ -89,6 +91,20 @@ def drain(file_like, read_size, timeout):
89
91
  break
90
92
 
91
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
+
92
108
  def _make_backend_fragments_header(fragments):
93
109
  if fragments:
94
110
  result = {}
@@ -129,7 +145,7 @@ class ObjectController(BaseStorageServer):
129
145
  self.container_update_timeout = float(
130
146
  conf.get('container_update_timeout', 1))
131
147
  self.conn_timeout = float(conf.get('conn_timeout', 0.5))
132
- self.client_timeout = int(conf.get('client_timeout', 60))
148
+ self.client_timeout = float(conf.get('client_timeout', 60))
133
149
  self.disk_chunk_size = int(conf.get('disk_chunk_size', 65536))
134
150
  self.network_chunk_size = int(conf.get('network_chunk_size', 65536))
135
151
  self.log_requests = config_true_value(conf.get('log_requests', 'true'))
@@ -137,6 +153,11 @@ class ObjectController(BaseStorageServer):
137
153
  self.slow = int(conf.get('slow', 0))
138
154
  self.keep_cache_private = \
139
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))
140
161
 
141
162
  default_allowed_headers = '''
142
163
  content-disposition,
@@ -158,12 +179,9 @@ class ObjectController(BaseStorageServer):
158
179
  for header in extra_allowed_headers:
159
180
  if header not in RESERVED_DATAFILE_META:
160
181
  self.allowed_headers.add(header)
161
- self.auto_create_account_prefix = \
162
- conf.get('auto_create_account_prefix') or '.'
163
- self.expiring_objects_account = self.auto_create_account_prefix + \
164
- (conf.get('expiring_objects_account_name') or 'expiring_objects')
165
- self.expiring_objects_container_divisor = \
166
- 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)
167
185
  # Initialization was successful, so now apply the network chunk size
168
186
  # parameter as the default read / write buffer size for the network
169
187
  # sockets.
@@ -178,8 +196,8 @@ class ObjectController(BaseStorageServer):
178
196
  # disk_chunk_size parameter. However, it affects all created sockets
179
197
  # using this class so we have chosen to tie it to the
180
198
  # network_chunk_size parameter value instead.
181
- if six.PY2:
182
- socket._fileobject.default_bufsize = self.network_chunk_size
199
+ # if six.PY2:
200
+ # socket._fileobject.default_bufsize = self.network_chunk_size
183
201
  # TODO: find a way to enable similar functionality in py3
184
202
 
185
203
  # Provide further setup specific to an object server implementation.
@@ -252,7 +270,8 @@ class ObjectController(BaseStorageServer):
252
270
 
253
271
  def async_update(self, op, account, container, obj, host, partition,
254
272
  contdevice, headers_out, objdevice, policy,
255
- logger_thread_locals=None, container_path=None):
273
+ logger_thread_locals=None, container_path=None,
274
+ db_state=None):
256
275
  """
257
276
  Sends or saves an async update.
258
277
 
@@ -274,6 +293,8 @@ class ObjectController(BaseStorageServer):
274
293
  to which the update should be sent. If given this path will be used
275
294
  instead of constructing a path from the ``account`` and
276
295
  ``container`` params.
296
+ :param db_state: The current database state of the container as
297
+ supplied to us by the proxy.
277
298
  """
278
299
  if logger_thread_locals:
279
300
  self.logger.thread_locals = logger_thread_locals
@@ -305,19 +326,19 @@ class ObjectController(BaseStorageServer):
305
326
  'Container update failed for %r; problem with '
306
327
  'redirect location: %s' % (obj, err))
307
328
  else:
308
- self.logger.error(_(
329
+ self.logger.error(
309
330
  'ERROR Container update failed '
310
331
  '(saving for async update later): %(status)d '
311
- 'response from %(ip)s:%(port)s/%(dev)s'),
332
+ 'response from %(ip)s:%(port)s/%(dev)s',
312
333
  {'status': response.status, 'ip': ip, 'port': port,
313
334
  'dev': contdevice})
314
335
  except (Exception, Timeout):
315
- self.logger.exception(_(
336
+ self.logger.exception(
316
337
  'ERROR container update failed with '
317
- '%(ip)s:%(port)s/%(dev)s (saving for async update later)'),
338
+ '%(ip)s:%(port)s/%(dev)s (saving for async update later)',
318
339
  {'ip': ip, 'port': port, 'dev': contdevice})
319
340
  data = {'op': op, 'account': account, 'container': container,
320
- 'obj': obj, 'headers': headers_out}
341
+ 'obj': obj, 'headers': headers_out, 'db_state': db_state}
321
342
  if redirect_data:
322
343
  self.logger.debug(
323
344
  'Update to %(path)s redirected to %(redirect)s',
@@ -351,19 +372,25 @@ class ObjectController(BaseStorageServer):
351
372
  contdevices = [d.strip() for d in
352
373
  headers_in.get('X-Container-Device', '').split(',')]
353
374
  contpartition = headers_in.get('X-Container-Partition', '')
354
- contpath = headers_in.get('X-Backend-Container-Path')
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
366
387
 
388
+ contpath = headers_in.get('X-Backend-Quoted-Container-Path')
389
+ if contpath:
390
+ contpath = unquote(contpath)
391
+ else:
392
+ contpath = headers_in.get('X-Backend-Container-Path')
393
+
367
394
  if contpath:
368
395
  try:
369
396
  # TODO: this is very late in request handling to be validating
@@ -396,7 +423,7 @@ class ObjectController(BaseStorageServer):
396
423
  conthost, contpartition, contdevice, headers_out,
397
424
  objdevice, policy,
398
425
  logger_thread_locals=self.logger.thread_locals,
399
- container_path=contpath)
426
+ container_path=contpath, db_state=contdbstate)
400
427
  update_greenthreads.append(gt)
401
428
  # Wait a little bit to see if the container updates are successful.
402
429
  # If we immediately return after firing off the greenthread above, then
@@ -415,7 +442,7 @@ class ObjectController(BaseStorageServer):
415
442
  self.container_update_timeout, updates)
416
443
 
417
444
  def delete_at_update(self, op, delete_at, account, container, obj,
418
- request, objdevice, policy):
445
+ request, objdevice, policy, extra_headers=None):
419
446
  """
420
447
  Update the expiring objects container when objects are updated.
421
448
 
@@ -427,15 +454,14 @@ class ObjectController(BaseStorageServer):
427
454
  :param request: the original request driving the update
428
455
  :param objdevice: device name that the object is in
429
456
  :param policy: the BaseStoragePolicy instance (used for tmp dir)
457
+ :param extra_headers: dict of additional headers for the update
430
458
  """
431
459
  if config_true_value(
432
460
  request.headers.get('x-backend-replication', 'f')):
433
461
  return
462
+
434
463
  delete_at = normalize_delete_at_timestamp(delete_at)
435
- updates = [(None, None)]
436
464
 
437
- partition = None
438
- hosts = contdevices = [None]
439
465
  headers_in = request.headers
440
466
  headers_out = HeaderKeyDict({
441
467
  # system accounts are always Policy-0
@@ -443,26 +469,42 @@ class ObjectController(BaseStorageServer):
443
469
  'x-timestamp': request.timestamp.internal,
444
470
  'x-trans-id': headers_in.get('x-trans-id', '-'),
445
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)
446
476
  if op != 'DELETE':
447
477
  hosts = headers_in.get('X-Delete-At-Host', None)
448
478
  if hosts is None:
449
479
  # If header is missing, no update needed as sufficient other
450
480
  # object servers should perform the required update.
451
481
  return
452
- delete_at_container = headers_in.get('X-Delete-At-Container', None)
453
- if not delete_at_container:
454
- # older proxy servers did not send X-Delete-At-Container so for
455
- # backwards compatibility calculate the value here, but also
456
- # log a warning because this is prone to inconsistent
457
- # expiring_objects_container_divisor configurations.
458
- # See https://bugs.launchpad.net/swift/+bug/1187200
459
- self.logger.warning(
460
- 'X-Delete-At-Container header must be specified for '
461
- 'expiring objects background %s to work properly. Making '
462
- 'best guess as to the container name for now.' % op)
463
- delete_at_container = get_expirer_container(
464
- delete_at, self.expiring_objects_container_divisor,
465
- 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
466
508
  partition = headers_in.get('X-Delete-At-Partition', None)
467
509
  contdevices = headers_in.get('X-Delete-At-Device', '')
468
510
  updates = [upd for upd in
@@ -472,30 +514,22 @@ class ObjectController(BaseStorageServer):
472
514
  if not updates:
473
515
  updates = [(None, None)]
474
516
  headers_out['x-size'] = '0'
475
- headers_out['x-content-type'] = 'text/plain'
517
+ headers_out['x-content-type'] = X_DELETE_TYPE
476
518
  headers_out['x-etag'] = 'd41d8cd98f00b204e9800998ecf8427e'
519
+ if extra_headers:
520
+ headers_out.update(extra_headers)
477
521
  else:
478
522
  if not config_true_value(
479
523
  request.headers.get(
480
524
  'X-Backend-Clean-Expiring-Object-Queue', 't')):
481
525
  return
482
-
483
- # DELETEs of old expiration data have no way of knowing what the
484
- # old X-Delete-At-Container was at the time of the initial setting
485
- # of the data, so a best guess is made here.
486
- # Worst case is a DELETE is issued now for something that doesn't
487
- # exist there and the original data is left where it is, where
488
- # it will be ignored when the expirer eventually tries to issue the
489
- # object DELETE later since the X-Delete-At value won't match up.
490
- delete_at_container = get_expirer_container(
491
- delete_at, self.expiring_objects_container_divisor,
492
- account, container, obj)
493
- delete_at_container = normalize_delete_at_timestamp(
494
- delete_at_container)
526
+ # DELETE op always go directly to async_pending
527
+ partition = None
528
+ updates = [(None, None)]
495
529
 
496
530
  for host, contdevice in updates:
497
531
  self.async_update(
498
- op, self.expiring_objects_account, delete_at_container,
532
+ op, expiring_objects_account_name, delete_at_container,
499
533
  build_task_obj(delete_at, account, container, obj),
500
534
  host, partition, contdevice, headers_out, objdevice,
501
535
  policy)
@@ -552,7 +586,7 @@ class ObjectController(BaseStorageServer):
552
586
  footer_md5 = footer_hdrs.get('Content-MD5')
553
587
  if not footer_md5:
554
588
  raise HTTPBadRequest(body="no Content-MD5 in footer")
555
- if footer_md5 != md5(footer_body).hexdigest():
589
+ if footer_md5 != md5(footer_body, usedforsecurity=False).hexdigest():
556
590
  raise HTTPUnprocessableEntity(body="footer MD5 mismatch")
557
591
 
558
592
  try:
@@ -598,12 +632,31 @@ class ObjectController(BaseStorageServer):
598
632
  override = key.lower().replace(override_prefix, 'x-')
599
633
  update_headers[override] = val
600
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
+
601
653
  @public
602
654
  @timing_stats()
603
655
  def POST(self, request):
604
656
  """Handle HTTP POST requests for the Swift Object Server."""
605
657
  device, partition, account, container, obj, policy = \
606
- get_name_and_placement(request, 5, 5, True)
658
+ get_obj_name_and_placement(request)
659
+
607
660
  req_timestamp = valid_timestamp(request)
608
661
  new_delete_at = int(request.headers.get('X-Delete-At') or 0)
609
662
  if new_delete_at and new_delete_at < req_timestamp:
@@ -613,8 +666,7 @@ class ObjectController(BaseStorageServer):
613
666
  try:
614
667
  disk_file = self.get_diskfile(
615
668
  device, partition, account, container, obj,
616
- policy=policy, open_expired=config_true_value(
617
- request.headers.get('x-backend-replication', 'false')),
669
+ policy=policy, open_expired=is_backend_open_expired(request),
618
670
  next_part_power=next_part_power)
619
671
  except DiskFileDeviceUnavailable:
620
672
  return HTTPInsufficientStorage(drive=device, request=request)
@@ -649,18 +701,15 @@ class ObjectController(BaseStorageServer):
649
701
  list(self.allowed_headers))
650
702
  for header_key in headers_to_copy:
651
703
  if header_key in request.headers:
652
- header_caps = header_key.title()
704
+ header_caps = bytes_to_wsgi(
705
+ wsgi_to_bytes(header_key).title())
653
706
  metadata[header_caps] = request.headers[header_key]
654
707
  orig_delete_at = int(orig_metadata.get('X-Delete-At') or 0)
655
- if orig_delete_at != new_delete_at:
656
- if new_delete_at:
657
- self.delete_at_update(
658
- 'PUT', new_delete_at, account, container, obj, request,
659
- device, policy)
660
- if orig_delete_at:
661
- self.delete_at_update('DELETE', orig_delete_at, account,
662
- container, obj, request, device,
663
- 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
+ )
664
713
  else:
665
714
  # preserve existing metadata, only content-type may be updated
666
715
  metadata = dict(disk_file.get_metafile_metadata())
@@ -732,8 +781,9 @@ class ObjectController(BaseStorageServer):
732
781
  'PUT', account, container, obj, request, update_headers,
733
782
  device, policy)
734
783
 
735
- # Add sysmeta to response
736
- resp_headers = {}
784
+ # Add current content-type and sysmeta to response
785
+ resp_headers = {
786
+ 'X-Backend-Content-Type': content_type_headers['Content-Type']}
737
787
  for key, value in orig_metadata.items():
738
788
  if is_sys_meta('object', key):
739
789
  resp_headers[key] = value
@@ -860,13 +910,20 @@ class ObjectController(BaseStorageServer):
860
910
  elapsed_time = 0
861
911
  upload_expiration = time.time() + self.max_upload_time
862
912
  timeout_reader = self._make_timeout_reader(obj_input)
863
- 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:
864
920
  start_time = time.time()
865
921
  if start_time > upload_expiration:
866
922
  self.logger.increment('PUT.timeouts')
867
923
  raise HTTPRequestTimeout(request=request)
868
924
  writer.write(chunk)
869
925
  elapsed_time += time.time() - start_time
926
+
870
927
  upload_size, etag = writer.chunks_finished()
871
928
  if fsize is not None and fsize != upload_size:
872
929
  raise HTTPClientDisconnect(request=request)
@@ -897,7 +954,8 @@ class ObjectController(BaseStorageServer):
897
954
  list(self.allowed_headers))
898
955
  for header_key in headers_to_copy:
899
956
  if header_key in request.headers:
900
- header_caps = header_key.title()
957
+ header_caps = bytes_to_wsgi(
958
+ wsgi_to_bytes(header_key).title())
901
959
  metadata[header_caps] = request.headers[header_key]
902
960
  return metadata
903
961
 
@@ -926,8 +984,8 @@ class ObjectController(BaseStorageServer):
926
984
  if (is_sys_or_user_meta('object', val[0]) or
927
985
  is_object_transient_sysmeta(val[0])))
928
986
  # N.B. footers_metadata is a HeaderKeyDict
929
- received_etag = footers_metadata.get('etag', request.headers.get(
930
- 'etag', '')).strip('"')
987
+ received_etag = normalize_etag(footers_metadata.get(
988
+ 'etag', request.headers.get('etag', '')))
931
989
  if received_etag and received_etag != metadata['ETag']:
932
990
  raise HTTPUnprocessableEntity(request=request)
933
991
 
@@ -968,15 +1026,10 @@ class ObjectController(BaseStorageServer):
968
1026
  orig_metadata, footers_metadata, metadata):
969
1027
  orig_delete_at = int(orig_metadata.get('X-Delete-At') or 0)
970
1028
  new_delete_at = int(request.headers.get('X-Delete-At') or 0)
971
- if orig_delete_at != new_delete_at:
972
- if new_delete_at:
973
- self.delete_at_update(
974
- 'PUT', new_delete_at, account, container, obj, request,
975
- device, policy)
976
- if orig_delete_at:
977
- self.delete_at_update(
978
- 'DELETE', orig_delete_at, account, container, obj,
979
- 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)
980
1033
 
981
1034
  update_headers = HeaderKeyDict({
982
1035
  'x-size': metadata['Content-Length'],
@@ -995,7 +1048,7 @@ class ObjectController(BaseStorageServer):
995
1048
  def PUT(self, request):
996
1049
  """Handle HTTP PUT requests for the Swift Object Server."""
997
1050
  device, partition, account, container, obj, policy = \
998
- get_name_and_placement(request, 5, 5, True)
1051
+ get_obj_name_and_placement(request)
999
1052
  disk_file, fsize, orig_metadata = self._pre_create_checks(
1000
1053
  request, device, partition, account, container, obj, policy)
1001
1054
  writer = disk_file.writer(size=fsize)
@@ -1016,7 +1069,9 @@ class ObjectController(BaseStorageServer):
1016
1069
  if multi_stage_mime_state:
1017
1070
  self._send_multi_stage_continue_headers(
1018
1071
  request, **multi_stage_mime_state)
1019
- writer.commit(request.timestamp)
1072
+ if not config_true_value(
1073
+ request.headers.get('X-Backend-No-Commit', False)):
1074
+ writer.commit(request.timestamp)
1020
1075
  if multi_stage_mime_state:
1021
1076
  self._drain_mime_request(**multi_stage_mime_state)
1022
1077
  except (DiskFileXattrNotSupported, DiskFileNoSpace):
@@ -1037,7 +1092,7 @@ class ObjectController(BaseStorageServer):
1037
1092
  def GET(self, request):
1038
1093
  """Handle HTTP GET requests for the Swift Object Server."""
1039
1094
  device, partition, account, container, obj, policy = \
1040
- get_name_and_placement(request, 5, 5, True)
1095
+ get_obj_name_and_placement(request)
1041
1096
  request.headers.setdefault('X-Timestamp',
1042
1097
  normalize_timestamp(time.time()))
1043
1098
  req_timestamp = valid_timestamp(request)
@@ -1047,23 +1102,39 @@ class ObjectController(BaseStorageServer):
1047
1102
  disk_file = self.get_diskfile(
1048
1103
  device, partition, account, container, obj,
1049
1104
  policy=policy, frag_prefs=frag_prefs,
1050
- open_expired=config_true_value(
1051
- request.headers.get('x-backend-replication', 'false')))
1105
+ open_expired=is_backend_open_expired(request))
1052
1106
  except DiskFileDeviceUnavailable:
1053
1107
  return HTTPInsufficientStorage(drive=device, request=request)
1054
1108
  try:
1055
1109
  with disk_file.open(current_time=req_timestamp):
1056
1110
  metadata = disk_file.get_metadata()
1111
+ resolve_ignore_range_header(request, metadata)
1057
1112
  obj_size = int(metadata['Content-Length'])
1058
1113
  file_x_ts = Timestamp(metadata['X-Timestamp'])
1059
- keep_cache = (self.keep_cache_private or
1060
- ('X-Auth-Token' not in request.headers and
1061
- '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
+ )
1062
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
+ )
1063
1133
  response = Response(
1064
- app_iter=disk_file.reader(keep_cache=keep_cache),
1065
- request=request, conditional_response=True,
1066
- conditional_etag=conditional_etag)
1134
+ app_iter=app_iter, request=request,
1135
+ conditional_response=True,
1136
+ conditional_etag=conditional_etag,
1137
+ )
1067
1138
  response.headers['Content-Type'] = metadata.get(
1068
1139
  'Content-Type', 'application/octet-stream')
1069
1140
  for key, value in metadata.items():
@@ -1072,7 +1143,7 @@ class ObjectController(BaseStorageServer):
1072
1143
  key.lower() in self.allowed_headers):
1073
1144
  response.headers[key] = value
1074
1145
  response.etag = metadata['ETag']
1075
- response.last_modified = math.ceil(float(file_x_ts))
1146
+ response.last_modified = file_x_ts.ceil()
1076
1147
  response.content_length = obj_size
1077
1148
  try:
1078
1149
  response.content_encoding = metadata[
@@ -1104,7 +1175,7 @@ class ObjectController(BaseStorageServer):
1104
1175
  def HEAD(self, request):
1105
1176
  """Handle HTTP HEAD requests for the Swift Object Server."""
1106
1177
  device, partition, account, container, obj, policy = \
1107
- get_name_and_placement(request, 5, 5, True)
1178
+ get_obj_name_and_placement(request)
1108
1179
  request.headers.setdefault('X-Timestamp',
1109
1180
  normalize_timestamp(time.time()))
1110
1181
  req_timestamp = valid_timestamp(request)
@@ -1114,8 +1185,7 @@ class ObjectController(BaseStorageServer):
1114
1185
  disk_file = self.get_diskfile(
1115
1186
  device, partition, account, container, obj,
1116
1187
  policy=policy, frag_prefs=frag_prefs,
1117
- open_expired=config_true_value(
1118
- request.headers.get('x-backend-replication', 'false')))
1188
+ open_expired=is_backend_open_expired(request))
1119
1189
  except DiskFileDeviceUnavailable:
1120
1190
  return HTTPInsufficientStorage(drive=device, request=request)
1121
1191
  try:
@@ -1140,7 +1210,7 @@ class ObjectController(BaseStorageServer):
1140
1210
  response.headers[key] = value
1141
1211
  response.etag = metadata['ETag']
1142
1212
  ts = Timestamp(metadata['X-Timestamp'])
1143
- response.last_modified = math.ceil(float(ts))
1213
+ response.last_modified = ts.ceil()
1144
1214
  # Needed for container sync feature
1145
1215
  response.headers['X-Timestamp'] = ts.normal
1146
1216
  response.headers['X-Backend-Timestamp'] = ts.internal
@@ -1163,7 +1233,7 @@ class ObjectController(BaseStorageServer):
1163
1233
  def DELETE(self, request):
1164
1234
  """Handle HTTP DELETE requests for the Swift Object Server."""
1165
1235
  device, partition, account, container, obj, policy = \
1166
- get_name_and_placement(request, 5, 5, True)
1236
+ get_obj_name_and_placement(request)
1167
1237
  req_timestamp = valid_timestamp(request)
1168
1238
  next_part_power = request.headers.get('X-Backend-Next-Part-Power')
1169
1239
  try:
@@ -1221,10 +1291,10 @@ class ObjectController(BaseStorageServer):
1221
1291
  else:
1222
1292
  # differentiate success from no object at all
1223
1293
  response_class = HTTPNoContent
1224
- if orig_delete_at:
1225
- self.delete_at_update('DELETE', orig_delete_at, account,
1226
- container, obj, request, device,
1227
- policy)
1294
+ self._conditional_delete_at_update(
1295
+ request, device, account, container, obj, policy, {},
1296
+ orig_delete_at, 0
1297
+ )
1228
1298
  if orig_timestamp < req_timestamp:
1229
1299
  try:
1230
1300
  disk_file.delete(req_timestamp)
@@ -1236,7 +1306,9 @@ class ObjectController(BaseStorageServer):
1236
1306
  device, policy)
1237
1307
  return response_class(
1238
1308
  request=request,
1239
- 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', '')})
1240
1312
 
1241
1313
  @public
1242
1314
  @replication
@@ -1255,7 +1327,8 @@ class ObjectController(BaseStorageServer):
1255
1327
  suffixes = suffix_parts.split('-') if suffix_parts else []
1256
1328
  try:
1257
1329
  hashes = self._diskfile_router[policy].get_hashes(
1258
- device, partition, suffixes, policy)
1330
+ device, partition, suffixes, policy,
1331
+ skip_rehash=bool(suffixes))
1259
1332
  except DiskFileDeviceUnavailable:
1260
1333
  resp = HTTPInsufficientStorage(drive=device, request=request)
1261
1334
  else:
@@ -1267,7 +1340,14 @@ class ObjectController(BaseStorageServer):
1267
1340
  @replication
1268
1341
  @timing_stats(sample_rate=0.1)
1269
1342
  def SSYNC(self, request):
1270
- 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)
1271
1351
 
1272
1352
  def __call__(self, env, start_response):
1273
1353
  """WSGI Application entry point for the Swift Object Server."""
@@ -1275,7 +1355,7 @@ class ObjectController(BaseStorageServer):
1275
1355
  req = Request(env)
1276
1356
  self.logger.txn_id = req.headers.get('x-trans-id', None)
1277
1357
 
1278
- if not check_utf8(wsgi_to_str(req.path_info)):
1358
+ if not check_utf8(wsgi_to_str(req.path_info), internal=True):
1279
1359
  res = HTTPPreconditionFailed(body='Invalid UTF8 or contains NULL')
1280
1360
  else:
1281
1361
  try:
@@ -1289,9 +1369,9 @@ class ObjectController(BaseStorageServer):
1289
1369
  except HTTPException as error_response:
1290
1370
  res = error_response
1291
1371
  except (Exception, Timeout):
1292
- self.logger.exception(_(
1372
+ self.logger.exception(
1293
1373
  'ERROR __call__ error with %(method)s'
1294
- ' %(path)s '), {'method': req.method, 'path': req.path})
1374
+ ' %(path)s ', {'method': req.method, 'path': req.path})
1295
1375
  res = HTTPInternalServerError(body=traceback.format_exc())
1296
1376
  trans_time = time.time() - start_time
1297
1377
  res.fix_conditional_response()
@@ -1390,3 +1470,14 @@ def app_factory(global_conf, **local_conf):
1390
1470
  conf = global_conf.copy()
1391
1471
  conf.update(local_conf)
1392
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()