swift 2.23.3__py3-none-any.whl → 2.35.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. swift/__init__.py +29 -50
  2. swift/account/auditor.py +21 -118
  3. swift/account/backend.py +33 -28
  4. swift/account/reaper.py +37 -28
  5. swift/account/replicator.py +22 -0
  6. swift/account/server.py +60 -26
  7. swift/account/utils.py +28 -11
  8. swift-2.23.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +154 -14
  17. swift/cli/manage_shard_ranges.py +705 -37
  18. swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.3.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  23. swift/cli/relinker.py +807 -126
  24. swift/cli/reload.py +135 -0
  25. swift/cli/ringbuilder.py +217 -20
  26. swift/cli/ringcomposer.py +0 -1
  27. swift/cli/shard-info.py +4 -3
  28. swift/common/base_storage_server.py +9 -20
  29. swift/common/bufferedhttp.py +48 -74
  30. swift/common/constraints.py +20 -15
  31. swift/common/container_sync_realms.py +9 -11
  32. swift/common/daemon.py +25 -8
  33. swift/common/db.py +195 -128
  34. swift/common/db_auditor.py +168 -0
  35. swift/common/db_replicator.py +95 -55
  36. swift/common/digest.py +141 -0
  37. swift/common/direct_client.py +144 -33
  38. swift/common/error_limiter.py +93 -0
  39. swift/common/exceptions.py +25 -1
  40. swift/common/header_key_dict.py +2 -9
  41. swift/common/http_protocol.py +373 -0
  42. swift/common/internal_client.py +129 -59
  43. swift/common/linkat.py +3 -4
  44. swift/common/manager.py +284 -67
  45. swift/common/memcached.py +390 -145
  46. swift/common/middleware/__init__.py +4 -0
  47. swift/common/middleware/account_quotas.py +211 -46
  48. swift/common/middleware/acl.py +3 -8
  49. swift/common/middleware/backend_ratelimit.py +230 -0
  50. swift/common/middleware/bulk.py +22 -34
  51. swift/common/middleware/catch_errors.py +1 -3
  52. swift/common/middleware/cname_lookup.py +6 -11
  53. swift/common/middleware/container_quotas.py +1 -1
  54. swift/common/middleware/container_sync.py +39 -17
  55. swift/common/middleware/copy.py +12 -0
  56. swift/common/middleware/crossdomain.py +22 -9
  57. swift/common/middleware/crypto/__init__.py +2 -1
  58. swift/common/middleware/crypto/crypto_utils.py +11 -15
  59. swift/common/middleware/crypto/decrypter.py +28 -11
  60. swift/common/middleware/crypto/encrypter.py +12 -17
  61. swift/common/middleware/crypto/keymaster.py +8 -15
  62. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  63. swift/common/middleware/dlo.py +15 -11
  64. swift/common/middleware/domain_remap.py +5 -4
  65. swift/common/middleware/etag_quoter.py +128 -0
  66. swift/common/middleware/formpost.py +73 -70
  67. swift/common/middleware/gatekeeper.py +8 -1
  68. swift/common/middleware/keystoneauth.py +33 -3
  69. swift/common/middleware/list_endpoints.py +4 -4
  70. swift/common/middleware/listing_formats.py +85 -49
  71. swift/common/middleware/memcache.py +4 -95
  72. swift/common/middleware/name_check.py +3 -2
  73. swift/common/middleware/proxy_logging.py +160 -92
  74. swift/common/middleware/ratelimit.py +17 -10
  75. swift/common/middleware/read_only.py +6 -4
  76. swift/common/middleware/recon.py +59 -22
  77. swift/common/middleware/s3api/acl_handlers.py +25 -3
  78. swift/common/middleware/s3api/acl_utils.py +6 -1
  79. swift/common/middleware/s3api/controllers/__init__.py +6 -0
  80. swift/common/middleware/s3api/controllers/acl.py +3 -2
  81. swift/common/middleware/s3api/controllers/bucket.py +242 -137
  82. swift/common/middleware/s3api/controllers/logging.py +2 -2
  83. swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
  84. swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
  85. swift/common/middleware/s3api/controllers/obj.py +112 -8
  86. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  87. swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
  88. swift/common/middleware/s3api/controllers/tagging.py +57 -0
  89. swift/common/middleware/s3api/controllers/versioning.py +36 -7
  90. swift/common/middleware/s3api/etree.py +22 -9
  91. swift/common/middleware/s3api/exception.py +0 -4
  92. swift/common/middleware/s3api/s3api.py +113 -41
  93. swift/common/middleware/s3api/s3request.py +384 -218
  94. swift/common/middleware/s3api/s3response.py +126 -23
  95. swift/common/middleware/s3api/s3token.py +16 -17
  96. swift/common/middleware/s3api/schema/delete.rng +1 -1
  97. swift/common/middleware/s3api/subresource.py +7 -10
  98. swift/common/middleware/s3api/utils.py +27 -10
  99. swift/common/middleware/slo.py +665 -358
  100. swift/common/middleware/staticweb.py +64 -37
  101. swift/common/middleware/symlink.py +51 -18
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +191 -173
  104. swift/common/middleware/versioned_writes/__init__.py +51 -0
  105. swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
  106. swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
  107. swift/common/middleware/x_profile/exceptions.py +1 -4
  108. swift/common/middleware/x_profile/html_viewer.py +18 -19
  109. swift/common/middleware/x_profile/profile_model.py +1 -2
  110. swift/common/middleware/xprofile.py +10 -10
  111. swift-2.23.3.data/scripts/swift-container-server → swift/common/recon.py +13 -8
  112. swift/common/registry.py +147 -0
  113. swift/common/request_helpers.py +324 -57
  114. swift/common/ring/builder.py +67 -25
  115. swift/common/ring/composite_builder.py +1 -1
  116. swift/common/ring/ring.py +177 -51
  117. swift/common/ring/utils.py +1 -1
  118. swift/common/splice.py +10 -6
  119. swift/common/statsd_client.py +205 -0
  120. swift/common/storage_policy.py +49 -44
  121. swift/common/swob.py +86 -102
  122. swift/common/{utils.py → utils/__init__.py} +2163 -2772
  123. swift/common/utils/base.py +131 -0
  124. swift/common/utils/config.py +433 -0
  125. swift/common/utils/ipaddrs.py +256 -0
  126. swift/common/utils/libc.py +345 -0
  127. swift/common/utils/logs.py +859 -0
  128. swift/common/utils/timestamp.py +412 -0
  129. swift/common/wsgi.py +553 -535
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +490 -231
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +358 -165
  135. swift/container/sharder.py +1540 -684
  136. swift/container/sync.py +94 -88
  137. swift/container/updater.py +53 -32
  138. swift/obj/auditor.py +153 -35
  139. swift/obj/diskfile.py +466 -217
  140. swift/obj/expirer.py +406 -124
  141. swift/obj/mem_diskfile.py +7 -4
  142. swift/obj/mem_server.py +1 -0
  143. swift/obj/reconstructor.py +523 -262
  144. swift/obj/replicator.py +249 -188
  145. swift/obj/server.py +207 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +652 -139
  149. swift/obj/watchers/__init__.py +0 -0
  150. swift/obj/watchers/dark_data.py +213 -0
  151. swift/proxy/controllers/account.py +11 -11
  152. swift/proxy/controllers/base.py +848 -604
  153. swift/proxy/controllers/container.py +433 -92
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1000 -489
  156. swift/proxy/server.py +185 -112
  157. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
  158. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
  159. swift-2.35.0.dist-info/RECORD +201 -0
  160. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  161. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  162. swift-2.35.0.dist-info/pbr.json +1 -0
  163. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  164. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  165. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  166. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  167. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  168. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  169. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  170. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  171. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  172. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  173. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  174. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  175. swift-2.23.3.data/scripts/swift-account-auditor +0 -23
  176. swift-2.23.3.data/scripts/swift-account-info +0 -51
  177. swift-2.23.3.data/scripts/swift-account-reaper +0 -23
  178. swift-2.23.3.data/scripts/swift-account-replicator +0 -34
  179. swift-2.23.3.data/scripts/swift-account-server +0 -23
  180. swift-2.23.3.data/scripts/swift-container-auditor +0 -23
  181. swift-2.23.3.data/scripts/swift-container-info +0 -55
  182. swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
  183. swift-2.23.3.data/scripts/swift-container-replicator +0 -34
  184. swift-2.23.3.data/scripts/swift-container-sharder +0 -37
  185. swift-2.23.3.data/scripts/swift-container-sync +0 -23
  186. swift-2.23.3.data/scripts/swift-container-updater +0 -23
  187. swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
  188. swift-2.23.3.data/scripts/swift-form-signature +0 -20
  189. swift-2.23.3.data/scripts/swift-init +0 -119
  190. swift-2.23.3.data/scripts/swift-object-auditor +0 -29
  191. swift-2.23.3.data/scripts/swift-object-expirer +0 -33
  192. swift-2.23.3.data/scripts/swift-object-info +0 -60
  193. swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
  194. swift-2.23.3.data/scripts/swift-object-relinker +0 -41
  195. swift-2.23.3.data/scripts/swift-object-replicator +0 -37
  196. swift-2.23.3.data/scripts/swift-object-server +0 -27
  197. swift-2.23.3.data/scripts/swift-object-updater +0 -23
  198. swift-2.23.3.data/scripts/swift-proxy-server +0 -23
  199. swift-2.23.3.data/scripts/swift-recon +0 -24
  200. swift-2.23.3.data/scripts/swift-ring-builder +0 -24
  201. swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
  202. swift-2.23.3.data/scripts/swift-ring-composer +0 -22
  203. swift-2.23.3.dist-info/RECORD +0 -220
  204. swift-2.23.3.dist-info/pbr.json +0 -1
  205. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
  206. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
swift/container/server.py CHANGED
@@ -15,15 +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
26
- from six.moves.urllib.parse import quote
24
+ from urllib.parse import quote
27
25
 
28
26
  import swift.common.db
29
27
  from swift.container.sync_store import ContainerSyncStore
@@ -32,16 +30,17 @@ from swift.container.backend import ContainerBroker, DATADIR, \
32
30
  from swift.container.replicator import ContainerReplicatorRpc
33
31
  from swift.common.db import DatabaseAlreadyExists
34
32
  from swift.common.container_sync_realms import ContainerSyncRealms
35
- from swift.common.request_helpers import get_param, \
36
- 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
37
36
  from swift.common.utils import get_logger, hash_path, public, \
38
37
  Timestamp, storage_directory, validate_sync_to, \
39
38
  config_true_value, timing_stats, replication, \
40
39
  override_bytes_from_content_type, get_log_line, \
41
40
  config_fallocate_value, fs_has_free_space, list_from_csv, \
42
- ShardRange
43
- from swift.common.constraints import valid_timestamp, check_utf8, check_drive
44
- 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
45
44
  from swift.common.bufferedhttp import http_connect
46
45
  from swift.common.exceptions import ConnectionTimeout
47
46
  from swift.common.http import HTTP_NO_CONTENT, HTTP_NOT_FOUND, is_success
@@ -54,6 +53,7 @@ from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPConflict, \
54
53
  HTTPPreconditionFailed, HTTPMethodNotAllowed, Request, Response, \
55
54
  HTTPInsufficientStorage, HTTPException, HTTPMovedPermanently, \
56
55
  wsgi_to_str, str_to_wsgi
56
+ from swift.common.wsgi import run_wsgi
57
57
 
58
58
 
59
59
  def gen_resp_headers(info, is_deleted=False):
@@ -84,6 +84,33 @@ def gen_resp_headers(info, is_deleted=False):
84
84
  return headers
85
85
 
86
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
+
87
114
  class ContainerController(BaseStorageServer):
88
115
  """WSGI Controller for the container server."""
89
116
 
@@ -115,8 +142,7 @@ class ContainerController(BaseStorageServer):
115
142
  self.replicator_rpc = ContainerReplicatorRpc(
116
143
  self.root, DATADIR, ContainerBroker, self.mount_check,
117
144
  logger=self.logger)
118
- self.auto_create_account_prefix = \
119
- conf.get('auto_create_account_prefix') or '.'
145
+ self.auto_create_account_prefix = AUTO_CREATE_ACCOUNT_PREFIX
120
146
  self.shards_account_prefix = (
121
147
  self.auto_create_account_prefix + 'shards_')
122
148
  if config_true_value(conf.get('allow_versions', 'f')):
@@ -128,6 +154,8 @@ class ContainerController(BaseStorageServer):
128
154
  'be ignored in a future release.')
129
155
  swift.common.db.DB_PREALLOCATION = \
130
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'))
131
159
  self.sync_store = ContainerSyncStore(self.root,
132
160
  self.logger,
133
161
  self.mount_check)
@@ -199,19 +227,16 @@ class ContainerController(BaseStorageServer):
199
227
  if len(account_hosts) != len(account_devices):
200
228
  # This shouldn't happen unless there's a bug in the proxy,
201
229
  # but if there is, we want to know about it.
202
- self.logger.error(_(
230
+ self.logger.error(
203
231
  'ERROR Account update failed: different '
204
232
  'numbers of hosts and devices in request: '
205
- '"%(hosts)s" vs "%(devices)s"') % {
233
+ '"%(hosts)s" vs "%(devices)s"', {
206
234
  'hosts': req.headers.get('X-Account-Host', ''),
207
235
  'devices': req.headers.get('X-Account-Device', '')})
208
236
  return HTTPBadRequest(req=req)
209
237
 
210
238
  if account_partition:
211
- # zip is lazy on py3, but we need a list, so force evaluation.
212
- # On py2 it's an extra list copy, but the list is so small
213
- # (one element per replica in account ring, usually 3) that it
214
- # doesn't matter.
239
+ # zip is lazy, but we need a list, so force evaluation.
215
240
  updates = list(zip(account_hosts, account_devices))
216
241
  else:
217
242
  updates = []
@@ -245,18 +270,18 @@ class ContainerController(BaseStorageServer):
245
270
  if account_response.status == HTTP_NOT_FOUND:
246
271
  account_404s += 1
247
272
  elif not is_success(account_response.status):
248
- self.logger.error(_(
273
+ self.logger.error(
249
274
  'ERROR Account update failed '
250
275
  'with %(ip)s:%(port)s/%(device)s (will retry '
251
- 'later): Response %(status)s %(reason)s'),
276
+ 'later): Response %(status)s %(reason)s',
252
277
  {'ip': account_ip, 'port': account_port,
253
278
  'device': account_device,
254
279
  'status': account_response.status,
255
280
  'reason': account_response.reason})
256
281
  except (Exception, Timeout):
257
- self.logger.exception(_(
282
+ self.logger.exception(
258
283
  'ERROR account update failed with '
259
- '%(ip)s:%(port)s/%(device)s (will retry later)'),
284
+ '%(ip)s:%(port)s/%(device)s (will retry later)',
260
285
  {'ip': account_ip, 'port': account_port,
261
286
  'device': account_device})
262
287
  if updates and account_404s == len(updates):
@@ -327,8 +352,7 @@ class ContainerController(BaseStorageServer):
327
352
  @timing_stats()
328
353
  def DELETE(self, req):
329
354
  """Handle HTTP DELETE request."""
330
- drive, part, account, container, obj = split_and_validate_path(
331
- req, 4, 5, True)
355
+ drive, part, account, container, obj = get_obj_name_and_placement(req)
332
356
  req_timestamp = valid_timestamp(req)
333
357
  try:
334
358
  check_drive(self.root, drive, self.mount_check)
@@ -461,8 +485,7 @@ class ContainerController(BaseStorageServer):
461
485
  @timing_stats()
462
486
  def PUT(self, req):
463
487
  """Handle HTTP PUT request."""
464
- drive, part, account, container, obj = split_and_validate_path(
465
- req, 4, 5, True)
488
+ drive, part, account, container, obj = get_obj_name_and_placement(req)
466
489
  req_timestamp = valid_timestamp(req)
467
490
  if 'x-container-sync-to' in req.headers:
468
491
  err, sync_to, realm, realm_key = validate_sync_to(
@@ -476,59 +499,42 @@ class ContainerController(BaseStorageServer):
476
499
  return HTTPInsufficientStorage(drive=drive, request=req)
477
500
  if not self.check_free_space(drive):
478
501
  return HTTPInsufficientStorage(drive=drive, request=req)
479
- requested_policy_index = self.get_and_validate_policy_index(req)
480
- broker = self._get_container_broker(drive, part, account, container)
481
- if obj: # put container object
482
- # obj put expects the policy_index header, default is for
483
- # legacy support during upgrade.
484
- obj_policy_index = requested_policy_index or 0
485
- self._maybe_autocreate(
486
- broker, req_timestamp, account, obj_policy_index, req)
487
- # redirect if a shard exists for this object name
488
- response = self._redirect_to_shard(req, broker, obj)
489
- if response:
490
- return response
491
-
492
- broker.put_object(obj, req_timestamp.internal,
493
- int(req.headers['x-size']),
494
- wsgi_to_str(req.headers['x-content-type']),
495
- wsgi_to_str(req.headers['x-etag']), 0,
496
- obj_policy_index,
497
- wsgi_to_str(req.headers.get(
498
- 'x-content-type-timestamp')),
499
- wsgi_to_str(req.headers.get('x-meta-timestamp')))
500
- return HTTPCreated(request=req)
501
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)
502
506
  record_type = req.headers.get('x-backend-record-type', '').lower()
503
507
  if record_type == RECORD_TYPE_SHARD:
504
- try:
505
- # validate incoming data...
506
- shard_ranges = [ShardRange.from_dict(sr)
507
- for sr in json.loads(req.body)]
508
- except (ValueError, KeyError, TypeError) as err:
509
- return HTTPBadRequest('Invalid body: %r' % err)
510
- created = self._maybe_autocreate(
511
- broker, req_timestamp, account, requested_policy_index, req)
512
- self._update_metadata(req, broker, req_timestamp, 'PUT')
513
- if shard_ranges:
514
- # TODO: consider writing the shard ranges into the pending
515
- # file, but if so ensure an all-or-none semantic for the write
516
- broker.merge_shard_ranges(shard_ranges)
517
- else: # put container
518
- if requested_policy_index is None:
519
- # use the default index sent by the proxy if available
520
- new_container_policy = req.headers.get(
521
- 'X-Backend-Storage-Policy-Default', int(POLICIES.default))
522
- else:
523
- new_container_policy = requested_policy_index
524
- created = self._update_or_create(req, broker,
525
- req_timestamp.internal,
526
- new_container_policy,
527
- requested_policy_index)
528
- self._update_metadata(req, broker, req_timestamp, 'PUT')
529
- resp = self.account_update(req, account, container, broker)
530
- if resp:
531
- 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):
532
538
  if created:
533
539
  return HTTPCreated(request=req,
534
540
  headers={'x-backend-storage-policy-index':
@@ -538,12 +544,50 @@ class ContainerController(BaseStorageServer):
538
544
  headers={'x-backend-storage-policy-index':
539
545
  broker.storage_policy_index})
540
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
+
541
586
  @public
542
587
  @timing_stats(sample_rate=0.1)
543
588
  def HEAD(self, req):
544
589
  """Handle HTTP HEAD request."""
545
- drive, part, account, container, obj = split_and_validate_path(
546
- req, 4, 5, True)
590
+ drive, part, account, container, obj = get_obj_name_and_placement(req)
547
591
  out_content_type = listing_formats.get_listing_content_type(req)
548
592
  try:
549
593
  check_drive(self.root, drive, self.mount_check)
@@ -562,33 +606,46 @@ class ContainerController(BaseStorageServer):
562
606
  if value != '' and (key.lower() in self.save_headers or
563
607
  is_sys_or_user_meta('container', key)))
564
608
  headers['Content-Type'] = out_content_type
609
+ headers['Content-Length'] = 0
565
610
  resp = HTTPNoContent(request=req, headers=headers, charset='utf-8')
566
- resp.last_modified = math.ceil(float(headers['X-PUT-Timestamp']))
611
+ resp.last_modified = Timestamp(headers['X-PUT-Timestamp']).ceil()
567
612
  return resp
568
613
 
569
- 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):
570
631
  """
571
- Perform any mutations to container listing records that are common to
572
- 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.
573
634
 
574
635
  Converts created time to iso timestamp.
575
636
  Replaces size with 'swift_bytes' content type parameter.
576
637
 
577
- :params record: object entry record
638
+ :param record: object entry record
578
639
  :returns: modified record
579
640
  """
580
- if isinstance(record, ShardRange):
581
- created = record.timestamp
582
- response = dict(record)
583
- else:
584
- (name, created, size, content_type, etag) = record[:5]
585
- name_ = name.decode('utf8') if six.PY2 else name
586
- if content_type is None:
587
- return {'subdir': name_}
588
- response = {
589
- 'bytes': size, 'hash': etag, 'name': name_,
590
- 'content_type': content_type}
591
- 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)
592
649
  response['last_modified'] = Timestamp(created).isoformat
593
650
  return response
594
651
 
@@ -610,6 +667,19 @@ class ContainerController(BaseStorageServer):
610
667
  ``sharded``, then the listing will be a list of shard ranges;
611
668
  otherwise the response body will be a list of objects.
612
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
+
613
683
  * Both shard range and object listings may be constrained to a name
614
684
  range by the ``marker`` and ``end_marker`` query string parameters.
615
685
  Object listings will only contain objects whose names are greater
@@ -638,11 +708,11 @@ class ContainerController(BaseStorageServer):
638
708
  either the string or integer representation of
639
709
  :data:`~swift.common.utils.ShardRange.STATES`.
640
710
 
641
- Two alias values may be used in a ``states`` parameter value:
642
- ``listing`` will cause the listing to include all shard ranges in a
643
- state suitable for contributing to an object listing; ``updating``
644
- will cause the listing to include all shard ranges in a state
645
- 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.
646
716
 
647
717
  If either of these aliases is used then the shard range listing will
648
718
  if necessary be extended with a synthesised 'filler' range in order
@@ -651,6 +721,23 @@ class ContainerController(BaseStorageServer):
651
721
  uncovered tail of the requested name range and will point back to the
652
722
  same container.
653
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
+
654
741
  * Listings are not normally returned from a deleted container. However,
655
742
  the ``X-Backend-Override-Deleted`` header may be used with a value in
656
743
  :attr:`swift.common.utils.TRUE_VALUES` to force a shard range
@@ -660,23 +747,8 @@ class ContainerController(BaseStorageServer):
660
747
  :param req: an instance of :class:`swift.common.swob.Request`
661
748
  :returns: an instance of :class:`swift.common.swob.Response`
662
749
  """
663
- drive, part, account, container, obj = split_and_validate_path(
664
- req, 4, 5, True)
665
- path = get_param(req, 'path')
666
- prefix = get_param(req, 'prefix')
667
- delimiter = get_param(req, 'delimiter')
668
- marker = get_param(req, 'marker', '')
669
- end_marker = get_param(req, 'end_marker')
670
- limit = constraints.CONTAINER_LISTING_LIMIT
671
- given_limit = get_param(req, 'limit')
672
- reverse = config_true_value(get_param(req, 'reverse'))
673
- if given_limit and given_limit.isdigit():
674
- limit = int(given_limit)
675
- if limit > constraints.CONTAINER_LISTING_LIMIT:
676
- return HTTPPreconditionFailed(
677
- request=req,
678
- body='Maximum limit is %d'
679
- % constraints.CONTAINER_LISTING_LIMIT)
750
+ drive, part, account, container, obj = get_obj_name_and_placement(req)
751
+ params = validate_container_params(req)
680
752
  out_content_type = listing_formats.get_listing_content_type(req)
681
753
  try:
682
754
  check_drive(self.root, drive, self.mount_check)
@@ -687,55 +759,158 @@ class ContainerController(BaseStorageServer):
687
759
  stale_reads_ok=True)
688
760
  info, is_deleted = broker.get_info_is_deleted()
689
761
  record_type = req.headers.get('x-backend-record-type', '').lower()
690
- if record_type == 'auto' and info.get('db_state') in (SHARDING,
691
- SHARDED):
762
+ db_state = info.get('db_state')
763
+ if record_type == 'auto' and db_state in (SHARDING, SHARDED):
692
764
  record_type = 'shard'
693
765
  if record_type == 'shard':
694
- override_deleted = info and config_true_value(
695
- req.headers.get('x-backend-override-deleted', False))
696
- resp_headers = gen_resp_headers(
697
- info, is_deleted=is_deleted and not override_deleted)
698
- if is_deleted and not override_deleted:
699
- return HTTPNotFound(request=req, headers=resp_headers)
700
- resp_headers['X-Backend-Record-Type'] = 'shard'
701
- includes = get_param(req, 'includes')
702
- states = get_param(req, 'states')
703
- fill_gaps = False
704
- if states:
705
- states = list_from_csv(states)
706
- fill_gaps = any(('listing' in states, 'updating' in states))
707
- try:
708
- states = broker.resolve_shard_range_states(states)
709
- except ValueError:
710
- return HTTPBadRequest(request=req, body='Bad state')
711
- include_deleted = config_true_value(
712
- 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
713
852
  container_list = broker.get_shard_ranges(
714
853
  marker, end_marker, includes, reverse, states=states,
715
- include_deleted=include_deleted, fill_gaps=fill_gaps)
716
- else:
717
- resp_headers = gen_resp_headers(info, is_deleted=is_deleted)
718
- if is_deleted:
719
- return HTTPNotFound(request=req, headers=resp_headers)
720
- resp_headers['X-Backend-Record-Type'] = 'object'
721
- # Use the retired db while container is in process of sharding,
722
- # otherwise use current db
723
- src_broker = broker.get_brokers()[0]
724
- container_list = src_broker.list_objects_iter(
725
- limit, marker, end_marker, prefix, delimiter, path,
726
- storage_policy_index=info['storage_policy_index'],
727
- reverse=reverse)
728
- return self.create_listing(req, out_content_type, info, resp_headers,
729
- broker.metadata, container_list, container)
730
-
731
- def create_listing(self, req, out_content_type, info, resp_headers,
732
- 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):
733
909
  for key, (value, _timestamp) in metadata.items():
734
910
  if value and (key.lower() in self.save_headers or
735
911
  is_sys_or_user_meta('container', key)):
736
912
  resp_headers[str_to_wsgi(key)] = str_to_wsgi(value)
737
- listing = [self.update_data_record(record)
738
- for record in container_list]
913
+
739
914
  if out_content_type.endswith('/xml'):
740
915
  body = listing_formats.container_to_xml(listing, container)
741
916
  elif out_content_type.endswith('/json'):
@@ -745,7 +920,7 @@ class ContainerController(BaseStorageServer):
745
920
 
746
921
  ret = Response(request=req, headers=resp_headers, body=body,
747
922
  content_type=out_content_type, charset='utf-8')
748
- ret.last_modified = math.ceil(float(resp_headers['X-PUT-Timestamp']))
923
+ ret.last_modified = Timestamp(resp_headers['X-PUT-Timestamp']).ceil()
749
924
  if not ret.body:
750
925
  ret.status_int = HTTP_NO_CONTENT
751
926
  return ret
@@ -779,7 +954,7 @@ class ContainerController(BaseStorageServer):
779
954
  """
780
955
  Handle HTTP UPDATE request (merge_items RPCs coming from the proxy.)
781
956
  """
782
- drive, part, account, container = split_and_validate_path(req, 4)
957
+ drive, part, account, container = get_container_name_and_placement(req)
783
958
  req_timestamp = valid_timestamp(req)
784
959
  try:
785
960
  check_drive(self.root, drive, self.mount_check)
@@ -802,8 +977,15 @@ class ContainerController(BaseStorageServer):
802
977
  @public
803
978
  @timing_stats()
804
979
  def POST(self, req):
805
- """Handle HTTP POST request."""
806
- 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)
807
989
  req_timestamp = valid_timestamp(req)
808
990
  if 'x-container-sync-to' in req.headers:
809
991
  err, sync_to, realm, realm_key = validate_sync_to(
@@ -820,7 +1002,9 @@ class ContainerController(BaseStorageServer):
820
1002
  broker = self._get_container_broker(drive, part, account, container)
821
1003
  if broker.is_deleted():
822
1004
  return HTTPNotFound(request=req)
823
- 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)
824
1008
  self._update_metadata(req, broker, req_timestamp, 'POST')
825
1009
  return HTTPNoContent(request=req)
826
1010
 
@@ -828,7 +1012,7 @@ class ContainerController(BaseStorageServer):
828
1012
  start_time = time.time()
829
1013
  req = Request(env)
830
1014
  self.logger.txn_id = req.headers.get('x-trans-id', None)
831
- if not check_utf8(wsgi_to_str(req.path_info)):
1015
+ if not check_utf8(wsgi_to_str(req.path_info), internal=True):
832
1016
  res = HTTPPreconditionFailed(body='Invalid UTF8 or contains NULL')
833
1017
  else:
834
1018
  try:
@@ -840,8 +1024,8 @@ class ContainerController(BaseStorageServer):
840
1024
  except HTTPException as error_response:
841
1025
  res = error_response
842
1026
  except (Exception, Timeout):
843
- self.logger.exception(_(
844
- 'ERROR __call__ error with %(method)s %(path)s '),
1027
+ self.logger.exception(
1028
+ 'ERROR __call__ error with %(method)s %(path)s ',
845
1029
  {'method': req.method, 'path': req.path})
846
1030
  res = HTTPInternalServerError(body=traceback.format_exc())
847
1031
  if self.log_requests:
@@ -862,3 +1046,12 @@ def app_factory(global_conf, **local_conf):
862
1046
  conf = global_conf.copy()
863
1047
  conf.update(local_conf)
864
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()