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/container/server.py CHANGED
@@ -15,14 +15,13 @@
15
15
 
16
16
  import json
17
17
  import os
18
+ import sys
18
19
  import time
19
20
  import traceback
20
- import math
21
- from swift import gettext_ as _
22
21
 
23
22
  from eventlet import Timeout
24
23
 
25
- import six
24
+ from urllib.parse import quote
26
25
 
27
26
  import swift.common.db
28
27
  from swift.container.sync_store import ContainerSyncStore
@@ -31,16 +30,17 @@ from swift.container.backend import ContainerBroker, DATADIR, \
31
30
  from swift.container.replicator import ContainerReplicatorRpc
32
31
  from swift.common.db import DatabaseAlreadyExists
33
32
  from swift.common.container_sync_realms import ContainerSyncRealms
34
- from swift.common.request_helpers import get_param, \
35
- split_and_validate_path, is_sys_or_user_meta
33
+ from swift.common.request_helpers import split_and_validate_path, \
34
+ is_sys_or_user_meta, validate_internal_container, validate_internal_obj, \
35
+ validate_container_params
36
36
  from swift.common.utils import get_logger, hash_path, public, \
37
37
  Timestamp, storage_directory, validate_sync_to, \
38
38
  config_true_value, timing_stats, replication, \
39
39
  override_bytes_from_content_type, get_log_line, \
40
40
  config_fallocate_value, fs_has_free_space, list_from_csv, \
41
- ShardRange
42
- from swift.common.constraints import valid_timestamp, check_utf8, check_drive
43
- from swift.common import constraints
41
+ ShardRange, parse_options
42
+ from swift.common.constraints import valid_timestamp, check_utf8, \
43
+ check_drive, AUTO_CREATE_ACCOUNT_PREFIX
44
44
  from swift.common.bufferedhttp import http_connect
45
45
  from swift.common.exceptions import ConnectionTimeout
46
46
  from swift.common.http import HTTP_NO_CONTENT, HTTP_NOT_FOUND, is_success
@@ -53,6 +53,7 @@ from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPConflict, \
53
53
  HTTPPreconditionFailed, HTTPMethodNotAllowed, Request, Response, \
54
54
  HTTPInsufficientStorage, HTTPException, HTTPMovedPermanently, \
55
55
  wsgi_to_str, str_to_wsgi
56
+ from swift.common.wsgi import run_wsgi
56
57
 
57
58
 
58
59
  def gen_resp_headers(info, is_deleted=False):
@@ -83,6 +84,33 @@ def gen_resp_headers(info, is_deleted=False):
83
84
  return headers
84
85
 
85
86
 
87
+ def get_container_name_and_placement(req):
88
+ """
89
+ Split and validate path for a container.
90
+
91
+ :param req: a swob request
92
+
93
+ :returns: a tuple of path parts as strings
94
+ """
95
+ drive, part, account, container = split_and_validate_path(req, 4)
96
+ validate_internal_container(account, container)
97
+ return drive, part, account, container
98
+
99
+
100
+ def get_obj_name_and_placement(req):
101
+ """
102
+ Split and validate path for an object.
103
+
104
+ :param req: a swob request
105
+
106
+ :returns: a tuple of path parts as strings
107
+ """
108
+ drive, part, account, container, obj = split_and_validate_path(
109
+ req, 4, 5, True)
110
+ validate_internal_obj(account, container, obj)
111
+ return drive, part, account, container, obj
112
+
113
+
86
114
  class ContainerController(BaseStorageServer):
87
115
  """WSGI Controller for the container server."""
88
116
 
@@ -114,8 +142,9 @@ class ContainerController(BaseStorageServer):
114
142
  self.replicator_rpc = ContainerReplicatorRpc(
115
143
  self.root, DATADIR, ContainerBroker, self.mount_check,
116
144
  logger=self.logger)
117
- self.auto_create_account_prefix = \
118
- conf.get('auto_create_account_prefix') or '.'
145
+ self.auto_create_account_prefix = AUTO_CREATE_ACCOUNT_PREFIX
146
+ self.shards_account_prefix = (
147
+ self.auto_create_account_prefix + 'shards_')
119
148
  if config_true_value(conf.get('allow_versions', 'f')):
120
149
  self.save_headers.append('x-versions-location')
121
150
  if 'allow_versions' in conf:
@@ -125,6 +154,8 @@ class ContainerController(BaseStorageServer):
125
154
  'be ignored in a future release.')
126
155
  swift.common.db.DB_PREALLOCATION = \
127
156
  config_true_value(conf.get('db_preallocation', 'f'))
157
+ swift.common.db.QUERY_LOGGING = \
158
+ config_true_value(conf.get('db_query_logging', 'f'))
128
159
  self.sync_store = ContainerSyncStore(self.root,
129
160
  self.logger,
130
161
  self.mount_check)
@@ -196,19 +227,16 @@ class ContainerController(BaseStorageServer):
196
227
  if len(account_hosts) != len(account_devices):
197
228
  # This shouldn't happen unless there's a bug in the proxy,
198
229
  # but if there is, we want to know about it.
199
- self.logger.error(_(
230
+ self.logger.error(
200
231
  'ERROR Account update failed: different '
201
232
  'numbers of hosts and devices in request: '
202
- '"%(hosts)s" vs "%(devices)s"') % {
233
+ '"%(hosts)s" vs "%(devices)s"', {
203
234
  'hosts': req.headers.get('X-Account-Host', ''),
204
235
  'devices': req.headers.get('X-Account-Device', '')})
205
236
  return HTTPBadRequest(req=req)
206
237
 
207
238
  if account_partition:
208
- # zip is lazy on py3, but we need a list, so force evaluation.
209
- # On py2 it's an extra list copy, but the list is so small
210
- # (one element per replica in account ring, usually 3) that it
211
- # doesn't matter.
239
+ # zip is lazy, but we need a list, so force evaluation.
212
240
  updates = list(zip(account_hosts, account_devices))
213
241
  else:
214
242
  updates = []
@@ -242,18 +270,18 @@ class ContainerController(BaseStorageServer):
242
270
  if account_response.status == HTTP_NOT_FOUND:
243
271
  account_404s += 1
244
272
  elif not is_success(account_response.status):
245
- self.logger.error(_(
273
+ self.logger.error(
246
274
  'ERROR Account update failed '
247
275
  'with %(ip)s:%(port)s/%(device)s (will retry '
248
- 'later): Response %(status)s %(reason)s'),
276
+ 'later): Response %(status)s %(reason)s',
249
277
  {'ip': account_ip, 'port': account_port,
250
278
  'device': account_device,
251
279
  'status': account_response.status,
252
280
  'reason': account_response.reason})
253
281
  except (Exception, Timeout):
254
- self.logger.exception(_(
282
+ self.logger.exception(
255
283
  'ERROR account update failed with '
256
- '%(ip)s:%(port)s/%(device)s (will retry later)'),
284
+ '%(ip)s:%(port)s/%(device)s (will retry later)',
257
285
  {'ip': account_ip, 'port': account_port,
258
286
  'device': account_device})
259
287
  if updates and account_404s == len(updates):
@@ -282,6 +310,11 @@ class ContainerController(BaseStorageServer):
282
310
  """
283
311
  if not config_true_value(
284
312
  req.headers.get('x-backend-accept-redirect', False)):
313
+ # We want to avoid fetching shard ranges for the (more
314
+ # time-sensitive) object-server update, so allow some misplaced
315
+ # objects to land between when we've started sharding and when the
316
+ # proxy learns about it. Note that this path is also used by old,
317
+ # pre-sharding updaters during a rolling upgrade.
285
318
  return None
286
319
 
287
320
  shard_ranges = broker.get_shard_ranges(
@@ -294,7 +327,15 @@ class ContainerController(BaseStorageServer):
294
327
  # in preference to the parent, which is the desired result.
295
328
  containing_range = shard_ranges[0]
296
329
  location = "/%s/%s" % (containing_range.name, obj_name)
297
- headers = {'Location': location,
330
+ if location != quote(location) and not config_true_value(
331
+ req.headers.get('x-backend-accept-quoted-location', False)):
332
+ # Sender expects the destination to be unquoted, but it isn't safe
333
+ # to send unquoted. Eat the update for now and let the sharder
334
+ # move it later. Should only come up during rolling upgrades.
335
+ return None
336
+
337
+ headers = {'Location': quote(location),
338
+ 'X-Backend-Location-Is-Quoted': 'true',
298
339
  'X-Backend-Redirect-Timestamp':
299
340
  containing_range.timestamp.internal}
300
341
 
@@ -311,8 +352,7 @@ class ContainerController(BaseStorageServer):
311
352
  @timing_stats()
312
353
  def DELETE(self, req):
313
354
  """Handle HTTP DELETE request."""
314
- drive, part, account, container, obj = split_and_validate_path(
315
- req, 4, 5, True)
355
+ drive, part, account, container, obj = get_obj_name_and_placement(req)
316
356
  req_timestamp = valid_timestamp(req)
317
357
  try:
318
358
  check_drive(self.root, drive, self.mount_check)
@@ -322,14 +362,12 @@ class ContainerController(BaseStorageServer):
322
362
  # auto create accounts)
323
363
  obj_policy_index = self.get_and_validate_policy_index(req) or 0
324
364
  broker = self._get_container_broker(drive, part, account, container)
325
- if account.startswith(self.auto_create_account_prefix) and obj and \
326
- not os.path.exists(broker.db_file):
327
- try:
328
- broker.initialize(req_timestamp.internal, obj_policy_index)
329
- except DatabaseAlreadyExists:
330
- pass
331
- if not os.path.exists(broker.db_file):
365
+ if obj:
366
+ self._maybe_autocreate(broker, req_timestamp, account,
367
+ obj_policy_index, req)
368
+ elif not os.path.exists(broker.db_file):
332
369
  return HTTPNotFound()
370
+
333
371
  if obj: # delete object
334
372
  # redirect if a shard range exists for the object name
335
373
  redirect = self._redirect_to_shard(req, broker, obj)
@@ -396,11 +434,25 @@ class ContainerController(BaseStorageServer):
396
434
  broker.update_status_changed_at(timestamp)
397
435
  return recreated
398
436
 
437
+ def _should_autocreate(self, account, req):
438
+ auto_create_header = req.headers.get('X-Backend-Auto-Create')
439
+ if auto_create_header:
440
+ # If the caller included an explicit X-Backend-Auto-Create header,
441
+ # assume they know the behavior they want
442
+ return config_true_value(auto_create_header)
443
+ if account.startswith(self.shards_account_prefix):
444
+ # we have to specical case this subset of the
445
+ # auto_create_account_prefix because we don't want the updater
446
+ # accidently auto-creating shards; only the sharder creates
447
+ # shards and it will explicitly tell the server to do so
448
+ return False
449
+ return account.startswith(self.auto_create_account_prefix)
450
+
399
451
  def _maybe_autocreate(self, broker, req_timestamp, account,
400
- policy_index):
452
+ policy_index, req):
401
453
  created = False
402
- if account.startswith(self.auto_create_account_prefix) and \
403
- not os.path.exists(broker.db_file):
454
+ should_autocreate = self._should_autocreate(account, req)
455
+ if should_autocreate and not os.path.exists(broker.db_file):
404
456
  if policy_index is None:
405
457
  raise HTTPBadRequest(
406
458
  'X-Backend-Storage-Policy-Index header is required')
@@ -433,8 +485,7 @@ class ContainerController(BaseStorageServer):
433
485
  @timing_stats()
434
486
  def PUT(self, req):
435
487
  """Handle HTTP PUT request."""
436
- drive, part, account, container, obj = split_and_validate_path(
437
- req, 4, 5, True)
488
+ drive, part, account, container, obj = get_obj_name_and_placement(req)
438
489
  req_timestamp = valid_timestamp(req)
439
490
  if 'x-container-sync-to' in req.headers:
440
491
  err, sync_to, realm, realm_key = validate_sync_to(
@@ -448,59 +499,42 @@ class ContainerController(BaseStorageServer):
448
499
  return HTTPInsufficientStorage(drive=drive, request=req)
449
500
  if not self.check_free_space(drive):
450
501
  return HTTPInsufficientStorage(drive=drive, request=req)
451
- requested_policy_index = self.get_and_validate_policy_index(req)
452
- broker = self._get_container_broker(drive, part, account, container)
453
- if obj: # put container object
454
- # obj put expects the policy_index header, default is for
455
- # legacy support during upgrade.
456
- obj_policy_index = requested_policy_index or 0
457
- self._maybe_autocreate(broker, req_timestamp, account,
458
- obj_policy_index)
459
- # redirect if a shard exists for this object name
460
- response = self._redirect_to_shard(req, broker, obj)
461
- if response:
462
- return response
463
-
464
- broker.put_object(obj, req_timestamp.internal,
465
- int(req.headers['x-size']),
466
- wsgi_to_str(req.headers['x-content-type']),
467
- wsgi_to_str(req.headers['x-etag']), 0,
468
- obj_policy_index,
469
- wsgi_to_str(req.headers.get(
470
- 'x-content-type-timestamp')),
471
- wsgi_to_str(req.headers.get('x-meta-timestamp')))
472
- return HTTPCreated(request=req)
473
502
 
503
+ broker = self._get_container_broker(drive, part, account, container)
504
+ if obj:
505
+ return self.PUT_object(req, broker, account, obj, req_timestamp)
474
506
  record_type = req.headers.get('x-backend-record-type', '').lower()
475
507
  if record_type == RECORD_TYPE_SHARD:
476
- try:
477
- # validate incoming data...
478
- shard_ranges = [ShardRange.from_dict(sr)
479
- for sr in json.loads(req.body)]
480
- except (ValueError, KeyError, TypeError) as err:
481
- return HTTPBadRequest('Invalid body: %r' % err)
482
- created = self._maybe_autocreate(broker, req_timestamp, account,
483
- requested_policy_index)
484
- self._update_metadata(req, broker, req_timestamp, 'PUT')
485
- if shard_ranges:
486
- # TODO: consider writing the shard ranges into the pending
487
- # file, but if so ensure an all-or-none semantic for the write
488
- broker.merge_shard_ranges(shard_ranges)
489
- else: # put container
490
- if requested_policy_index is None:
491
- # use the default index sent by the proxy if available
492
- new_container_policy = req.headers.get(
493
- 'X-Backend-Storage-Policy-Default', int(POLICIES.default))
494
- else:
495
- new_container_policy = requested_policy_index
496
- created = self._update_or_create(req, broker,
497
- req_timestamp.internal,
498
- new_container_policy,
499
- requested_policy_index)
500
- self._update_metadata(req, broker, req_timestamp, 'PUT')
501
- resp = self.account_update(req, account, container, broker)
502
- if resp:
503
- return resp
508
+ return self.PUT_shard(req, broker, account, req_timestamp)
509
+ else:
510
+ return self.PUT_container(req, broker, account,
511
+ container, req_timestamp)
512
+
513
+ @timing_stats()
514
+ def PUT_object(self, req, broker, account, obj, req_timestamp):
515
+ """Put object into container."""
516
+ # obj put expects the policy_index header, default is for
517
+ # legacy support during upgrade.
518
+ requested_policy_index = self.get_and_validate_policy_index(req)
519
+ obj_policy_index = requested_policy_index or 0
520
+ self._maybe_autocreate(
521
+ broker, req_timestamp, account, obj_policy_index, req)
522
+ # redirect if a shard exists for this object name
523
+ response = self._redirect_to_shard(req, broker, obj)
524
+ if response:
525
+ return response
526
+
527
+ broker.put_object(obj, req_timestamp.internal,
528
+ int(req.headers['x-size']),
529
+ wsgi_to_str(req.headers['x-content-type']),
530
+ wsgi_to_str(req.headers['x-etag']), 0,
531
+ obj_policy_index,
532
+ wsgi_to_str(req.headers.get(
533
+ 'x-content-type-timestamp')),
534
+ wsgi_to_str(req.headers.get('x-meta-timestamp')))
535
+ return HTTPCreated(request=req)
536
+
537
+ def _create_ok_resp(self, req, broker, created):
504
538
  if created:
505
539
  return HTTPCreated(request=req,
506
540
  headers={'x-backend-storage-policy-index':
@@ -510,12 +544,50 @@ class ContainerController(BaseStorageServer):
510
544
  headers={'x-backend-storage-policy-index':
511
545
  broker.storage_policy_index})
512
546
 
547
+ @timing_stats()
548
+ def PUT_shard(self, req, broker, account, req_timestamp):
549
+ """Put shards into container."""
550
+ requested_policy_index = self.get_and_validate_policy_index(req)
551
+ try:
552
+ # validate incoming data...
553
+ shard_ranges = [ShardRange.from_dict(sr)
554
+ for sr in json.loads(req.body)]
555
+ except (ValueError, KeyError, TypeError) as err:
556
+ return HTTPBadRequest('Invalid body: %r' % err)
557
+ created = self._maybe_autocreate(
558
+ broker, req_timestamp, account, requested_policy_index, req)
559
+ self._update_metadata(req, broker, req_timestamp, 'PUT')
560
+ if shard_ranges:
561
+ # TODO: consider writing the shard ranges into the pending
562
+ # file, but if so ensure an all-or-none semantic for the write
563
+ broker.merge_shard_ranges(shard_ranges)
564
+ return self._create_ok_resp(req, broker, created)
565
+
566
+ @timing_stats()
567
+ def PUT_container(self, req, broker, account, container, req_timestamp):
568
+ """Update or create container."""
569
+ requested_policy_index = self.get_and_validate_policy_index(req)
570
+ if requested_policy_index is None:
571
+ # use the default index sent by the proxy if available
572
+ new_container_policy = req.headers.get(
573
+ 'X-Backend-Storage-Policy-Default', int(POLICIES.default))
574
+ else:
575
+ new_container_policy = requested_policy_index
576
+ created = self._update_or_create(req, broker,
577
+ req_timestamp.internal,
578
+ new_container_policy,
579
+ requested_policy_index)
580
+ self._update_metadata(req, broker, req_timestamp, 'PUT')
581
+ resp = self.account_update(req, account, container, broker)
582
+ if resp:
583
+ return resp
584
+ return self._create_ok_resp(req, broker, created)
585
+
513
586
  @public
514
587
  @timing_stats(sample_rate=0.1)
515
588
  def HEAD(self, req):
516
589
  """Handle HTTP HEAD request."""
517
- drive, part, account, container, obj = split_and_validate_path(
518
- req, 4, 5, True)
590
+ drive, part, account, container, obj = get_obj_name_and_placement(req)
519
591
  out_content_type = listing_formats.get_listing_content_type(req)
520
592
  try:
521
593
  check_drive(self.root, drive, self.mount_check)
@@ -534,33 +606,46 @@ class ContainerController(BaseStorageServer):
534
606
  if value != '' and (key.lower() in self.save_headers or
535
607
  is_sys_or_user_meta('container', key)))
536
608
  headers['Content-Type'] = out_content_type
609
+ headers['Content-Length'] = 0
537
610
  resp = HTTPNoContent(request=req, headers=headers, charset='utf-8')
538
- resp.last_modified = math.ceil(float(headers['X-PUT-Timestamp']))
611
+ resp.last_modified = Timestamp(headers['X-PUT-Timestamp']).ceil()
539
612
  return resp
540
613
 
541
- def update_data_record(self, record):
614
+ def update_shard_record(self, record, shard_record_full=True):
615
+ """
616
+ Return the shard_range database record as a dict, the keys will depend
617
+ on the database fields provided in the record.
618
+
619
+ :param record: shard entry record, either ShardRange or Namespace.
620
+ :param shard_record_full: boolean, when true the timestamp field is
621
+ added as "last_modified" in iso format.
622
+ :returns: dict suitable for listing responses
623
+ """
624
+ response = dict(record)
625
+ if shard_record_full:
626
+ created = record.timestamp
627
+ response['last_modified'] = Timestamp(created).isoformat
628
+ return response
629
+
630
+ def update_object_record(self, record):
542
631
  """
543
- Perform any mutations to container listing records that are common to
544
- all serialization formats, and returns it as a dict.
632
+ Perform mutation to container listing records that are common to all
633
+ serialization formats, and returns it as a dict.
545
634
 
546
635
  Converts created time to iso timestamp.
547
636
  Replaces size with 'swift_bytes' content type parameter.
548
637
 
549
- :params record: object entry record
638
+ :param record: object entry record
550
639
  :returns: modified record
551
640
  """
552
- if isinstance(record, ShardRange):
553
- created = record.timestamp
554
- response = dict(record)
555
- else:
556
- (name, created, size, content_type, etag) = record[:5]
557
- name_ = name.decode('utf8') if six.PY2 else name
558
- if content_type is None:
559
- return {'subdir': name_}
560
- response = {
561
- 'bytes': size, 'hash': etag, 'name': name_,
562
- 'content_type': content_type}
563
- override_bytes_from_content_type(response, logger=self.logger)
641
+ # record is object info
642
+ (name, created, size, content_type, etag) = record[:5]
643
+ if content_type is None:
644
+ return {'subdir': name}
645
+ response = {
646
+ 'bytes': size, 'hash': etag, 'name': name,
647
+ 'content_type': content_type}
648
+ override_bytes_from_content_type(response, logger=self.logger)
564
649
  response['last_modified'] = Timestamp(created).isoformat
565
650
  return response
566
651
 
@@ -582,6 +667,19 @@ class ContainerController(BaseStorageServer):
582
667
  ``sharded``, then the listing will be a list of shard ranges;
583
668
  otherwise the response body will be a list of objects.
584
669
 
670
+ * Both shard range and object listings may be filtered according to
671
+ the constraints described below. However, the
672
+ ``X-Backend-Ignore-Shard-Name-Filter`` header may be used to override
673
+ the application of the ``marker``, ``end_marker``, ``includes`` and
674
+ ``reverse`` parameters to shard range listings. These parameters will
675
+ be ignored if the header has the value 'sharded' and the current db
676
+ sharding state is also 'sharded'. Note that this header does not
677
+ override the ``states`` constraint on shard range listings.
678
+
679
+ * The order of both shard range and object listings may be reversed by
680
+ using a ``reverse`` query string parameter with a
681
+ value in :attr:`swift.common.utils.TRUE_VALUES`.
682
+
585
683
  * Both shard range and object listings may be constrained to a name
586
684
  range by the ``marker`` and ``end_marker`` query string parameters.
587
685
  Object listings will only contain objects whose names are greater
@@ -610,11 +708,11 @@ class ContainerController(BaseStorageServer):
610
708
  either the string or integer representation of
611
709
  :data:`~swift.common.utils.ShardRange.STATES`.
612
710
 
613
- Two alias values may be used in a ``states`` parameter value:
614
- ``listing`` will cause the listing to include all shard ranges in a
615
- state suitable for contributing to an object listing; ``updating``
616
- will cause the listing to include all shard ranges in a state
617
- suitable to accept an object update.
711
+ Alias values may be used in a ``states`` parameter value. The
712
+ ``listing`` alias will cause the listing to include all shard ranges
713
+ in a state suitable for contributing to an object listing. The
714
+ ``updating`` alias will cause the listing to include all shard ranges
715
+ in a state suitable to accept an object update.
618
716
 
619
717
  If either of these aliases is used then the shard range listing will
620
718
  if necessary be extended with a synthesised 'filler' range in order
@@ -623,6 +721,23 @@ class ContainerController(BaseStorageServer):
623
721
  uncovered tail of the requested name range and will point back to the
624
722
  same container.
625
723
 
724
+ The ``auditing`` alias will cause the listing to include all shard
725
+ ranges in a state useful to the sharder while auditing a shard
726
+ container. This alias will not cause a 'filler' range to be added,
727
+ but will cause the container's own shard range to be included in the
728
+ listing. For now, ``auditing`` is only supported when
729
+ 'X-Backend-Record-Shard-Format' is 'full'.
730
+
731
+ * Shard range listings can be simplified to include only Namespace
732
+ only attributes (name, lower and upper) if the caller send the header
733
+ ``X-Backend-Record-Shard-Format`` with value 'namespace' as a hint
734
+ that it would prefer namespaces. If this header doesn't exist or the
735
+ value is 'full', the listings will default to include all attributes
736
+ of shard ranges. But if params has includes/marker/end_marker then
737
+ the response will be full shard ranges, regardless the header of
738
+ ``X-Backend-Record-Shard-Format``. The response header
739
+ ``X-Backend-Record-Type`` will tell the user what type it gets back.
740
+
626
741
  * Listings are not normally returned from a deleted container. However,
627
742
  the ``X-Backend-Override-Deleted`` header may be used with a value in
628
743
  :attr:`swift.common.utils.TRUE_VALUES` to force a shard range
@@ -632,23 +747,8 @@ class ContainerController(BaseStorageServer):
632
747
  :param req: an instance of :class:`swift.common.swob.Request`
633
748
  :returns: an instance of :class:`swift.common.swob.Response`
634
749
  """
635
- drive, part, account, container, obj = split_and_validate_path(
636
- req, 4, 5, True)
637
- path = get_param(req, 'path')
638
- prefix = get_param(req, 'prefix')
639
- delimiter = get_param(req, 'delimiter')
640
- marker = get_param(req, 'marker', '')
641
- end_marker = get_param(req, 'end_marker')
642
- limit = constraints.CONTAINER_LISTING_LIMIT
643
- given_limit = get_param(req, 'limit')
644
- reverse = config_true_value(get_param(req, 'reverse'))
645
- if given_limit and given_limit.isdigit():
646
- limit = int(given_limit)
647
- if limit > constraints.CONTAINER_LISTING_LIMIT:
648
- return HTTPPreconditionFailed(
649
- request=req,
650
- body='Maximum limit is %d'
651
- % constraints.CONTAINER_LISTING_LIMIT)
750
+ drive, part, account, container, obj = get_obj_name_and_placement(req)
751
+ params = validate_container_params(req)
652
752
  out_content_type = listing_formats.get_listing_content_type(req)
653
753
  try:
654
754
  check_drive(self.root, drive, self.mount_check)
@@ -659,55 +759,158 @@ class ContainerController(BaseStorageServer):
659
759
  stale_reads_ok=True)
660
760
  info, is_deleted = broker.get_info_is_deleted()
661
761
  record_type = req.headers.get('x-backend-record-type', '').lower()
662
- if record_type == 'auto' and info.get('db_state') in (SHARDING,
663
- SHARDED):
762
+ db_state = info.get('db_state')
763
+ if record_type == 'auto' and db_state in (SHARDING, SHARDED):
664
764
  record_type = 'shard'
665
765
  if record_type == 'shard':
666
- override_deleted = info and config_true_value(
667
- req.headers.get('x-backend-override-deleted', False))
668
- resp_headers = gen_resp_headers(
669
- info, is_deleted=is_deleted and not override_deleted)
670
- if is_deleted and not override_deleted:
671
- return HTTPNotFound(request=req, headers=resp_headers)
672
- resp_headers['X-Backend-Record-Type'] = 'shard'
673
- includes = get_param(req, 'includes')
674
- states = get_param(req, 'states')
675
- fill_gaps = False
676
- if states:
677
- states = list_from_csv(states)
678
- fill_gaps = any(('listing' in states, 'updating' in states))
679
- try:
680
- states = broker.resolve_shard_range_states(states)
681
- except ValueError:
682
- return HTTPBadRequest(request=req, body='Bad state')
683
- include_deleted = config_true_value(
684
- req.headers.get('x-backend-include-deleted', False))
766
+ return self.GET_shard(req, broker, container, params, info,
767
+ is_deleted, out_content_type)
768
+ else:
769
+ return self.GET_object(req, broker, container, params, info,
770
+ is_deleted, out_content_type)
771
+
772
+ @timing_stats()
773
+ def GET_shard(self, req, broker, container, params, info,
774
+ is_deleted, out_content_type):
775
+ """
776
+ Returns a list of persisted shard ranges or namespaces in response.
777
+
778
+ :param req: swob.Request object
779
+ :param broker: container DB broker object
780
+ :param container: container name
781
+ :param params: the request params.
782
+ :param info: the global info for the container
783
+ :param is_deleted: the is_deleted status for the container.
784
+ :param out_content_type: content type as a string.
785
+ :returns: an instance of :class:`swift.common.swob.Response`
786
+ """
787
+ override_deleted = info and config_true_value(
788
+ req.headers.get('x-backend-override-deleted', False))
789
+ resp_headers = gen_resp_headers(
790
+ info, is_deleted=is_deleted and not override_deleted)
791
+
792
+ if is_deleted and not override_deleted:
793
+ return HTTPNotFound(request=req, headers=resp_headers)
794
+
795
+ marker = params.get('marker', '')
796
+ end_marker = params.get('end_marker')
797
+ reverse = config_true_value(params.get('reverse'))
798
+ states = params.get('states')
799
+ includes = params.get('includes')
800
+ include_deleted = config_true_value(
801
+ req.headers.get('x-backend-include-deleted', False))
802
+
803
+ resp_headers['X-Backend-Record-Type'] = 'shard'
804
+ override_filter_hdr = req.headers.get(
805
+ 'x-backend-override-shard-name-filter', '').lower()
806
+ if override_filter_hdr == info.get('db_state') == 'sharded':
807
+ # respect the request to send back *all* ranges if the db is in
808
+ # sharded state
809
+ resp_headers['X-Backend-Override-Shard-Name-Filter'] = 'true'
810
+ marker = end_marker = includes = None
811
+ reverse = False
812
+ fill_gaps = include_own = False
813
+ if states:
814
+ states = list_from_csv(states)
815
+ fill_gaps = any(('listing' in states, 'updating' in states))
816
+ # The 'auditing' state alias is used by the sharder during
817
+ # shard audit; if the shard is shrinking then it needs to get
818
+ # acceptor shard ranges, which may be the root container
819
+ # itself, so use include_own.
820
+ include_own = 'auditing' in states
821
+ try:
822
+ states = broker.resolve_shard_range_states(states)
823
+ except ValueError:
824
+ return HTTPBadRequest(request=req, body='Bad state')
825
+
826
+ # For record type of 'shard', user can specify an additional header
827
+ # to ask for list of Namespaces instead of full ShardRanges.
828
+ # This will allow proxy server who is going to retrieve Namespace
829
+ # to talk to older version of container servers who don't support
830
+ # Namespace yet during upgrade.
831
+ shard_format = req.headers.get(
832
+ 'x-backend-record-shard-format', 'full').lower()
833
+ if shard_format == 'namespace':
834
+ resp_headers['X-Backend-Record-Shard-Format'] = 'namespace'
835
+ # Namespace GET does not support all the options of Shard Range
836
+ # GET: 'x-backend-include-deleted' cannot be supported because
837
+ # there is no way for a Namespace to indicate the deleted state;
838
+ # the 'auditing' state query parameter is not supported because it
839
+ # is specific to the sharder which only requests full shard ranges.
840
+ if include_deleted:
841
+ return HTTPBadRequest(
842
+ request=req, body='No include_deleted for namespace GET')
843
+ if include_own:
844
+ return HTTPBadRequest(
845
+ request=req, body='No auditing state for namespace GET')
846
+ shard_format_full = False
847
+ container_list = broker.get_namespaces(
848
+ marker, end_marker, includes, reverse, states, fill_gaps)
849
+ else:
850
+ resp_headers['X-Backend-Record-Shard-Format'] = 'full'
851
+ shard_format_full = True
685
852
  container_list = broker.get_shard_ranges(
686
853
  marker, end_marker, includes, reverse, states=states,
687
- include_deleted=include_deleted, fill_gaps=fill_gaps)
688
- else:
689
- resp_headers = gen_resp_headers(info, is_deleted=is_deleted)
690
- if is_deleted:
691
- return HTTPNotFound(request=req, headers=resp_headers)
692
- resp_headers['X-Backend-Record-Type'] = 'object'
693
- # Use the retired db while container is in process of sharding,
694
- # otherwise use current db
695
- src_broker = broker.get_brokers()[0]
696
- container_list = src_broker.list_objects_iter(
697
- limit, marker, end_marker, prefix, delimiter, path,
698
- storage_policy_index=info['storage_policy_index'],
699
- reverse=reverse)
700
- return self.create_listing(req, out_content_type, info, resp_headers,
701
- broker.metadata, container_list, container)
702
-
703
- def create_listing(self, req, out_content_type, info, resp_headers,
704
- metadata, container_list, container):
854
+ include_deleted=include_deleted, fill_gaps=fill_gaps,
855
+ include_own=include_own)
856
+ listing = [self.update_shard_record(record, shard_format_full)
857
+ for record in container_list]
858
+ return self._create_GET_response(req, out_content_type, info,
859
+ resp_headers, broker.metadata,
860
+ container, listing)
861
+
862
+ @timing_stats()
863
+ def GET_object(self, req, broker, container, params, info,
864
+ is_deleted, out_content_type):
865
+ """
866
+ Returns a list of objects in response.
867
+
868
+ :param req: swob.Request object
869
+ :param broker: container DB broker object
870
+ :param container: container name
871
+ :param params: the request params.
872
+ :param info: the global info for the container
873
+ :param is_deleted: the is_deleted status for the container.
874
+ :param out_content_type: content type as a string.
875
+ :returns: an instance of :class:`swift.common.swob.Response`
876
+ """
877
+ marker = params.get('marker', '')
878
+ end_marker = params.get('end_marker')
879
+ reverse = config_true_value(params.get('reverse'))
880
+ path = params.get('path')
881
+ prefix = params.get('prefix')
882
+ delimiter = params.get('delimiter')
883
+ limit = params['limit']
884
+ requested_policy_index = self.get_and_validate_policy_index(req)
885
+ resp_headers = gen_resp_headers(info, is_deleted=is_deleted)
886
+ if is_deleted:
887
+ return HTTPNotFound(request=req, headers=resp_headers)
888
+ resp_headers['X-Backend-Record-Type'] = 'object'
889
+ storage_policy_index = (
890
+ requested_policy_index if requested_policy_index is not None
891
+ else info['storage_policy_index'])
892
+ resp_headers['X-Backend-Record-Storage-Policy-Index'] = \
893
+ storage_policy_index
894
+ # Use the retired db while container is in process of sharding,
895
+ # otherwise use current db
896
+ src_broker = broker.get_brokers()[0]
897
+ container_list = src_broker.list_objects_iter(
898
+ limit, marker, end_marker, prefix, delimiter, path,
899
+ storage_policy_index=storage_policy_index,
900
+ reverse=reverse, allow_reserved=req.allow_reserved_names)
901
+ listing = [self.update_object_record(record)
902
+ for record in container_list]
903
+ return self._create_GET_response(req, out_content_type, info,
904
+ resp_headers, broker.metadata,
905
+ container, listing)
906
+
907
+ def _create_GET_response(self, req, out_content_type, info, resp_headers,
908
+ metadata, container, listing):
705
909
  for key, (value, _timestamp) in metadata.items():
706
910
  if value and (key.lower() in self.save_headers or
707
911
  is_sys_or_user_meta('container', key)):
708
912
  resp_headers[str_to_wsgi(key)] = str_to_wsgi(value)
709
- listing = [self.update_data_record(record)
710
- for record in container_list]
913
+
711
914
  if out_content_type.endswith('/xml'):
712
915
  body = listing_formats.container_to_xml(listing, container)
713
916
  elif out_content_type.endswith('/json'):
@@ -717,7 +920,7 @@ class ContainerController(BaseStorageServer):
717
920
 
718
921
  ret = Response(request=req, headers=resp_headers, body=body,
719
922
  content_type=out_content_type, charset='utf-8')
720
- ret.last_modified = math.ceil(float(resp_headers['X-PUT-Timestamp']))
923
+ ret.last_modified = Timestamp(resp_headers['X-PUT-Timestamp']).ceil()
721
924
  if not ret.body:
722
925
  ret.status_int = HTTP_NO_CONTENT
723
926
  return ret
@@ -751,7 +954,7 @@ class ContainerController(BaseStorageServer):
751
954
  """
752
955
  Handle HTTP UPDATE request (merge_items RPCs coming from the proxy.)
753
956
  """
754
- drive, part, account, container = split_and_validate_path(req, 4)
957
+ drive, part, account, container = get_container_name_and_placement(req)
755
958
  req_timestamp = valid_timestamp(req)
756
959
  try:
757
960
  check_drive(self.root, drive, self.mount_check)
@@ -763,7 +966,7 @@ class ContainerController(BaseStorageServer):
763
966
  requested_policy_index = self.get_and_validate_policy_index(req)
764
967
  broker = self._get_container_broker(drive, part, account, container)
765
968
  self._maybe_autocreate(broker, req_timestamp, account,
766
- requested_policy_index)
969
+ requested_policy_index, req)
767
970
  try:
768
971
  objs = json.load(req.environ['wsgi.input'])
769
972
  except ValueError as err:
@@ -774,8 +977,15 @@ class ContainerController(BaseStorageServer):
774
977
  @public
775
978
  @timing_stats()
776
979
  def POST(self, req):
777
- """Handle HTTP POST request."""
778
- drive, part, account, container = split_and_validate_path(req, 4)
980
+ """
981
+ Handle HTTP POST request.
982
+
983
+ A POST request will update the container's ``put_timestamp``, unless
984
+ it has an ``X-Backend-No-Timestamp-Update`` header with a truthy value.
985
+
986
+ :param req: an instance of :class:`~swift.common.swob.Request`.
987
+ """
988
+ drive, part, account, container = get_container_name_and_placement(req)
779
989
  req_timestamp = valid_timestamp(req)
780
990
  if 'x-container-sync-to' in req.headers:
781
991
  err, sync_to, realm, realm_key = validate_sync_to(
@@ -792,7 +1002,9 @@ class ContainerController(BaseStorageServer):
792
1002
  broker = self._get_container_broker(drive, part, account, container)
793
1003
  if broker.is_deleted():
794
1004
  return HTTPNotFound(request=req)
795
- broker.update_put_timestamp(req_timestamp.internal)
1005
+ if not config_true_value(
1006
+ req.headers.get('x-backend-no-timestamp-update', False)):
1007
+ broker.update_put_timestamp(req_timestamp.internal)
796
1008
  self._update_metadata(req, broker, req_timestamp, 'POST')
797
1009
  return HTTPNoContent(request=req)
798
1010
 
@@ -800,7 +1012,7 @@ class ContainerController(BaseStorageServer):
800
1012
  start_time = time.time()
801
1013
  req = Request(env)
802
1014
  self.logger.txn_id = req.headers.get('x-trans-id', None)
803
- if not check_utf8(wsgi_to_str(req.path_info)):
1015
+ if not check_utf8(wsgi_to_str(req.path_info), internal=True):
804
1016
  res = HTTPPreconditionFailed(body='Invalid UTF8 or contains NULL')
805
1017
  else:
806
1018
  try:
@@ -812,8 +1024,8 @@ class ContainerController(BaseStorageServer):
812
1024
  except HTTPException as error_response:
813
1025
  res = error_response
814
1026
  except (Exception, Timeout):
815
- self.logger.exception(_(
816
- 'ERROR __call__ error with %(method)s %(path)s '),
1027
+ self.logger.exception(
1028
+ 'ERROR __call__ error with %(method)s %(path)s ',
817
1029
  {'method': req.method, 'path': req.path})
818
1030
  res = HTTPInternalServerError(body=traceback.format_exc())
819
1031
  if self.log_requests:
@@ -834,3 +1046,12 @@ def app_factory(global_conf, **local_conf):
834
1046
  conf = global_conf.copy()
835
1047
  conf.update(local_conf)
836
1048
  return ContainerController(conf)
1049
+
1050
+
1051
+ def main():
1052
+ conf_file, options = parse_options(test_config=True)
1053
+ sys.exit(run_wsgi(conf_file, 'container-server', **options))
1054
+
1055
+
1056
+ if __name__ == '__main__':
1057
+ main()