swift 2.32.0__py2.py3-none-any.whl → 2.34.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. swift/account/auditor.py +11 -0
  2. swift/account/reaper.py +11 -1
  3. swift/account/replicator.py +22 -0
  4. swift/account/server.py +13 -12
  5. swift-2.32.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
  6. swift-2.32.0.data/scripts/swift-config → swift/cli/config.py +1 -1
  7. swift-2.32.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
  8. swift-2.32.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
  9. swift-2.32.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
  10. swift/cli/info.py +131 -3
  11. swift-2.32.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
  12. swift-2.32.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
  13. swift-2.32.0.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +9 -18
  14. swift-2.32.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  15. swift/cli/relinker.py +1 -1
  16. swift/cli/reload.py +141 -0
  17. swift/cli/ringbuilder.py +24 -0
  18. swift/common/daemon.py +12 -2
  19. swift/common/db.py +14 -9
  20. swift/common/db_auditor.py +2 -2
  21. swift/common/db_replicator.py +6 -0
  22. swift/common/exceptions.py +12 -0
  23. swift/common/http_protocol.py +76 -3
  24. swift/common/manager.py +120 -5
  25. swift/common/memcached.py +24 -25
  26. swift/common/middleware/account_quotas.py +144 -43
  27. swift/common/middleware/backend_ratelimit.py +166 -24
  28. swift/common/middleware/catch_errors.py +1 -3
  29. swift/common/middleware/cname_lookup.py +3 -5
  30. swift/common/middleware/container_sync.py +6 -10
  31. swift/common/middleware/crypto/crypto_utils.py +4 -5
  32. swift/common/middleware/crypto/decrypter.py +4 -5
  33. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  34. swift/common/middleware/proxy_logging.py +57 -43
  35. swift/common/middleware/ratelimit.py +6 -7
  36. swift/common/middleware/recon.py +6 -7
  37. swift/common/middleware/s3api/acl_handlers.py +10 -1
  38. swift/common/middleware/s3api/controllers/__init__.py +3 -0
  39. swift/common/middleware/s3api/controllers/acl.py +3 -2
  40. swift/common/middleware/s3api/controllers/logging.py +2 -2
  41. swift/common/middleware/s3api/controllers/multi_upload.py +31 -15
  42. swift/common/middleware/s3api/controllers/obj.py +20 -1
  43. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  44. swift/common/middleware/s3api/s3api.py +6 -0
  45. swift/common/middleware/s3api/s3request.py +190 -74
  46. swift/common/middleware/s3api/s3response.py +48 -8
  47. swift/common/middleware/s3api/s3token.py +2 -2
  48. swift/common/middleware/s3api/utils.py +2 -1
  49. swift/common/middleware/slo.py +508 -310
  50. swift/common/middleware/staticweb.py +45 -14
  51. swift/common/middleware/tempauth.py +6 -4
  52. swift/common/middleware/tempurl.py +134 -93
  53. swift/common/middleware/x_profile/exceptions.py +1 -4
  54. swift/common/middleware/x_profile/html_viewer.py +9 -10
  55. swift/common/middleware/x_profile/profile_model.py +1 -2
  56. swift/common/middleware/xprofile.py +1 -2
  57. swift/common/request_helpers.py +101 -8
  58. swift/common/statsd_client.py +207 -0
  59. swift/common/storage_policy.py +1 -1
  60. swift/common/swob.py +5 -2
  61. swift/common/utils/__init__.py +331 -1774
  62. swift/common/utils/base.py +138 -0
  63. swift/common/utils/config.py +443 -0
  64. swift/common/utils/logs.py +999 -0
  65. swift/common/utils/timestamp.py +23 -2
  66. swift/common/wsgi.py +19 -3
  67. swift/container/auditor.py +11 -0
  68. swift/container/backend.py +136 -31
  69. swift/container/reconciler.py +11 -2
  70. swift/container/replicator.py +64 -7
  71. swift/container/server.py +276 -146
  72. swift/container/sharder.py +86 -42
  73. swift/container/sync.py +11 -1
  74. swift/container/updater.py +12 -2
  75. swift/obj/auditor.py +20 -3
  76. swift/obj/diskfile.py +63 -25
  77. swift/obj/expirer.py +154 -47
  78. swift/obj/mem_diskfile.py +2 -1
  79. swift/obj/mem_server.py +1 -0
  80. swift/obj/reconstructor.py +28 -4
  81. swift/obj/replicator.py +63 -24
  82. swift/obj/server.py +76 -59
  83. swift/obj/updater.py +12 -2
  84. swift/obj/watchers/dark_data.py +72 -34
  85. swift/proxy/controllers/account.py +3 -2
  86. swift/proxy/controllers/base.py +254 -148
  87. swift/proxy/controllers/container.py +274 -289
  88. swift/proxy/controllers/obj.py +120 -166
  89. swift/proxy/server.py +17 -13
  90. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +14 -4
  91. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/METADATA +9 -7
  92. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/RECORD +97 -120
  93. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +39 -0
  94. swift-2.34.0.dist-info/pbr.json +1 -0
  95. swift-2.32.0.data/scripts/swift-account-auditor +0 -23
  96. swift-2.32.0.data/scripts/swift-account-info +0 -52
  97. swift-2.32.0.data/scripts/swift-account-reaper +0 -23
  98. swift-2.32.0.data/scripts/swift-account-replicator +0 -34
  99. swift-2.32.0.data/scripts/swift-account-server +0 -23
  100. swift-2.32.0.data/scripts/swift-container-auditor +0 -23
  101. swift-2.32.0.data/scripts/swift-container-info +0 -56
  102. swift-2.32.0.data/scripts/swift-container-reconciler +0 -21
  103. swift-2.32.0.data/scripts/swift-container-replicator +0 -34
  104. swift-2.32.0.data/scripts/swift-container-server +0 -23
  105. swift-2.32.0.data/scripts/swift-container-sharder +0 -37
  106. swift-2.32.0.data/scripts/swift-container-sync +0 -23
  107. swift-2.32.0.data/scripts/swift-container-updater +0 -23
  108. swift-2.32.0.data/scripts/swift-dispersion-report +0 -24
  109. swift-2.32.0.data/scripts/swift-form-signature +0 -20
  110. swift-2.32.0.data/scripts/swift-init +0 -119
  111. swift-2.32.0.data/scripts/swift-object-auditor +0 -29
  112. swift-2.32.0.data/scripts/swift-object-expirer +0 -33
  113. swift-2.32.0.data/scripts/swift-object-info +0 -60
  114. swift-2.32.0.data/scripts/swift-object-reconstructor +0 -33
  115. swift-2.32.0.data/scripts/swift-object-relinker +0 -23
  116. swift-2.32.0.data/scripts/swift-object-replicator +0 -37
  117. swift-2.32.0.data/scripts/swift-object-server +0 -27
  118. swift-2.32.0.data/scripts/swift-object-updater +0 -23
  119. swift-2.32.0.data/scripts/swift-proxy-server +0 -23
  120. swift-2.32.0.data/scripts/swift-recon +0 -24
  121. swift-2.32.0.data/scripts/swift-ring-builder +0 -37
  122. swift-2.32.0.data/scripts/swift-ring-builder-analyzer +0 -22
  123. swift-2.32.0.data/scripts/swift-ring-composer +0 -22
  124. swift-2.32.0.dist-info/pbr.json +0 -1
  125. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
  126. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
  127. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/top_level.txt +0 -0
@@ -14,15 +14,12 @@
14
14
  # limitations under the License.
15
15
 
16
16
  import json
17
- import random
18
17
 
19
18
  import six
20
19
  from six.moves.urllib.parse import unquote
21
20
 
22
- from swift.common.memcached import MemcacheConnectionError
23
21
  from swift.common.utils import public, private, csv_append, Timestamp, \
24
- config_true_value, ShardRange, cache_from_env, filter_namespaces, \
25
- NamespaceBoundList
22
+ config_true_value, cache_from_env, filter_namespaces, NamespaceBoundList
26
23
  from swift.common.constraints import check_metadata, CONTAINER_LISTING_LIMIT
27
24
  from swift.common.http import HTTP_ACCEPTED, is_success
28
25
  from swift.common.request_helpers import get_sys_meta_prefix, get_param, \
@@ -30,7 +27,7 @@ from swift.common.request_helpers import get_sys_meta_prefix, get_param, \
30
27
  from swift.proxy.controllers.base import Controller, delay_denial, NodeIter, \
31
28
  cors_validation, set_info_cache, clear_info_cache, get_container_info, \
32
29
  record_cache_op_metrics, get_cache_key, headers_from_container_info, \
33
- update_headers
30
+ update_headers, set_namespaces_in_cache, get_namespaces_from_cache
34
31
  from swift.common.storage_policy import POLICIES
35
32
  from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \
36
33
  HTTPServiceUnavailable, str_to_wsgi, wsgi_to_str, Response
@@ -103,22 +100,22 @@ class ContainerController(Controller):
103
100
  self.account_name, self.container_name)
104
101
  concurrency = self.app.container_ring.replica_count \
105
102
  if self.app.get_policy_options(None).concurrent_gets else 1
106
- node_iter = NodeIter(self.app, self.app.container_ring, part,
107
- self.logger, req)
103
+ node_iter = NodeIter(
104
+ 'container', self.app, self.app.container_ring, part,
105
+ self.logger, req)
108
106
  resp = self.GETorHEAD_base(
109
107
  req, 'Container', node_iter, part,
110
108
  req.swift_entity_path, concurrency)
111
109
  return resp
112
110
 
113
- def _make_namespaces_response_body(self, req, ns_bound_list):
111
+ def _filter_complete_listing(self, req, namespaces):
114
112
  """
115
- Filter namespaces according to request constraints and return a
116
- serialised list of namespaces.
113
+ Filter complete list of namespaces to return only those specified by
114
+ the request constraints.
117
115
 
118
- :param req: the request object.
119
- :param ns_bound_list: an instance of
120
- :class:`~swift.common.utils.NamespaceBoundList`.
121
- :return: a serialised list of namespaces.
116
+ :param req: a :class:`~swift.common.swob.Request`.
117
+ :param namespaces: a list of :class:`~swift.common.utils.Namespace`.
118
+ :return: a list of :class:`~swift.common.utils.Namespace`.
122
119
  """
123
120
  marker = get_param(req, 'marker', '')
124
121
  end_marker = get_param(req, 'end_marker')
@@ -126,171 +123,150 @@ class ContainerController(Controller):
126
123
  reverse = config_true_value(get_param(req, 'reverse'))
127
124
  if reverse:
128
125
  marker, end_marker = end_marker, marker
129
- namespaces = ns_bound_list.get_namespaces()
130
126
  namespaces = filter_namespaces(
131
127
  namespaces, includes, marker, end_marker)
132
128
  if reverse:
133
129
  namespaces.reverse()
134
- return json.dumps([dict(ns) for ns in namespaces]).encode('ascii')
130
+ return namespaces
135
131
 
136
- def _get_shard_ranges_from_cache(self, req, headers):
132
+ def _get_listing_namespaces_from_cache(self, req, headers):
137
133
  """
138
134
  Try to fetch shard namespace data from cache and, if successful, return
139
- a response. Also return the cache state.
140
-
141
- The response body will be a list of dicts each of which describes
142
- a Namespace (i.e. includes the keys ``lower``, ``upper`` and ``name``).
135
+ a list of Namespaces. Also return the cache state.
143
136
 
144
137
  :param req: an instance of ``swob.Request``.
145
- :param headers: Headers to be sent with request.
146
- :return: a tuple comprising (an instance of ``swob.Response``or
147
- ``None`` if no namespaces were found in cache, the cache state).
138
+ :return: a tuple comprising (a list instance of ``Namespace`` objects
139
+ or ``None`` if no namespaces were found in cache, the cache state).
148
140
  """
149
- infocache = req.environ.setdefault('swift.infocache', {})
150
- memcache = cache_from_env(req.environ, True)
151
- cache_key = get_cache_key(self.account_name,
152
- self.container_name,
141
+ cache_key = get_cache_key(self.account_name, self.container_name,
153
142
  shard='listing')
154
-
155
- resp_body = None
156
- ns_bound_list = infocache.get(cache_key)
157
- if ns_bound_list:
158
- cache_state = 'infocache_hit'
159
- resp_body = self._make_namespaces_response_body(req, ns_bound_list)
160
- elif memcache:
161
- skip_chance = \
162
- self.app.container_listing_shard_ranges_skip_cache
163
- if skip_chance and random.random() < skip_chance:
164
- cache_state = 'skip'
165
- else:
166
- try:
167
- cached_namespaces = memcache.get(
168
- cache_key, raise_on_error=True)
169
- if cached_namespaces:
170
- cache_state = 'hit'
171
- if six.PY2:
172
- # json.loads() in memcache.get will convert json
173
- # 'string' to 'unicode' with python2, here we cast
174
- # 'unicode' back to 'str'
175
- cached_namespaces = [
176
- [lower.encode('utf-8'), name.encode('utf-8')]
177
- for lower, name in cached_namespaces]
178
- ns_bound_list = NamespaceBoundList(cached_namespaces)
179
- resp_body = self._make_namespaces_response_body(
180
- req, ns_bound_list)
181
- else:
182
- cache_state = 'miss'
183
- except MemcacheConnectionError:
184
- cache_state = 'error'
185
-
186
- if resp_body is None:
187
- resp = None
188
- else:
189
- # shard ranges can be returned from cache
190
- infocache[cache_key] = ns_bound_list
191
- self.logger.debug('Found %d shards in cache for %s',
192
- len(ns_bound_list.bounds), req.path_qs)
193
- headers.update({'x-backend-record-type': 'shard',
194
- 'x-backend-cached-results': 'true'})
195
- # mimic GetOrHeadHandler.get_working_response...
196
- # note: server sets charset with content_type but proxy
197
- # GETorHEAD_base does not, so don't set it here either
198
- resp = Response(request=req, body=resp_body)
199
- update_headers(resp, headers)
200
- resp.last_modified = Timestamp(headers['x-put-timestamp']).ceil()
201
- resp.environ['swift_x_timestamp'] = headers.get('x-timestamp')
202
- resp.accept_ranges = 'bytes'
203
- resp.content_type = 'application/json'
204
-
205
- return resp, cache_state
206
-
207
- def _store_shard_ranges_in_cache(self, req, resp):
143
+ skip_chance = self.app.container_listing_shard_ranges_skip_cache
144
+ ns_bound_list, cache_state = get_namespaces_from_cache(
145
+ req, cache_key, skip_chance)
146
+ if not ns_bound_list:
147
+ return None, None, cache_state
148
+
149
+ # Namespaces found in cache so there is no need to go to backend,
150
+ # but we need to build response headers: mimic
151
+ # GetOrHeadHandler.get_working_response...
152
+ # note: server sets charset with content_type but proxy
153
+ # GETorHEAD_base does not, so don't set it here either
154
+ namespaces = ns_bound_list.get_namespaces()
155
+ self.logger.debug('Found %d shards in cache for %s',
156
+ len(namespaces), req.path_qs)
157
+ headers.update({'x-backend-record-type': 'shard',
158
+ 'x-backend-record-shard-format': 'namespace',
159
+ 'x-backend-cached-results': 'true'})
160
+ resp = Response(request=req)
161
+ update_headers(resp, headers)
162
+ resp.last_modified = Timestamp(headers['x-put-timestamp']).ceil()
163
+ resp.environ['swift_x_timestamp'] = headers.get('x-timestamp')
164
+ resp.accept_ranges = 'bytes'
165
+ resp.content_type = 'application/json'
166
+ namespaces = self._filter_complete_listing(req, namespaces)
167
+ return resp, namespaces, cache_state
168
+
169
+ def _set_listing_namespaces_in_cache(self, req, namespaces):
208
170
  """
209
- Parse shard ranges returned from backend, store them in both infocache
210
- and memcache.
171
+ Store a list of namespaces in both infocache and memcache.
172
+
173
+ Note: the returned list of namespaces may not be identical to the given
174
+ list. Any gaps in the given namespaces will be 'lost' as a result of
175
+ compacting the list of namespaces to a NamespaceBoundList for caching.
176
+ That is ok. When the cached NamespaceBoundList is transformed back to
177
+ Namespaces to perform a listing, the Namespace before each gap will
178
+ have expanded to include the gap, which means that the backend GET to
179
+ that shard will have an end_marker beyond that shard's upper bound, and
180
+ equal to the next available shard's lower. At worst, some misplaced
181
+ objects, in the gap above the shard's upper, may be included in the
182
+ shard's response.
211
183
 
212
184
  :param req: the request object.
213
- :param resp: the response object for the shard range listing.
214
- :return: an instance of
215
- :class:`~swift.common.utils.NamespaceBoundList`.
185
+ :param namespaces: a list of :class:`~swift.common.utils.Namespace`
186
+ objects.
187
+ :return: a list of :class:`~swift.common.utils.Namespace` objects.
216
188
  """
217
- # Note: Any gaps in the response's shard ranges will be 'lost' as a
218
- # result of compacting the list of shard ranges to a
219
- # NamespaceBoundList. That is ok. When the cached NamespaceBoundList is
220
- # transformed back to shard range Namespaces to perform a listing, the
221
- # Namespace before each gap will have expanded to include the gap,
222
- # which means that the backend GET to that shard will have an
223
- # end_marker beyond that shard's upper bound, and equal to the next
224
- # available shard's lower. At worst, some misplaced objects, in the gap
225
- # above the shard's upper, may be included in the shard's response.
226
- data = self._parse_listing_response(req, resp)
227
- backend_shard_ranges = self._parse_shard_ranges(req, data, resp)
228
- if backend_shard_ranges is None:
229
- return None
230
-
231
- ns_bound_list = NamespaceBoundList.parse(backend_shard_ranges)
232
- if resp.headers.get('x-backend-sharding-state') == 'sharded':
233
- # cache in infocache even if no shard ranges returned; this
234
- # is unexpected but use that result for this request
235
- infocache = req.environ.setdefault('swift.infocache', {})
236
- cache_key = get_cache_key(
237
- self.account_name, self.container_name, shard='listing')
238
- infocache[cache_key] = ns_bound_list
239
- memcache = cache_from_env(req.environ, True)
240
- if memcache and ns_bound_list:
241
- # cache in memcache only if shard ranges as expected
242
- self.logger.info('Caching listing shards for %s (%d shards)',
243
- cache_key, len(ns_bound_list.bounds))
244
- memcache.set(cache_key, ns_bound_list.bounds,
245
- time=self.app.recheck_listing_shard_ranges)
246
- return ns_bound_list
247
-
248
- def _get_shard_ranges_from_backend(self, req):
189
+ cache_key = get_cache_key(self.account_name, self.container_name,
190
+ shard='listing')
191
+ ns_bound_list = NamespaceBoundList.parse(namespaces)
192
+ # cache in infocache even if no namespaces returned; this
193
+ # is unexpected but use that result for this request
194
+ set_cache_state = set_namespaces_in_cache(
195
+ req, cache_key, ns_bound_list,
196
+ self.app.recheck_listing_shard_ranges)
197
+ if set_cache_state == 'set':
198
+ self.logger.info(
199
+ 'Caching listing namespaces for %s (%d namespaces)',
200
+ cache_key, len(ns_bound_list.bounds))
201
+ # return the de-gapped namespaces
202
+ return ns_bound_list.get_namespaces()
203
+
204
+ def _get_listing_namespaces_from_backend(self, req, cache_enabled):
249
205
  """
250
- Make a backend request for shard ranges and return a response.
251
-
252
- The response body will be a list of dicts each of which describes
253
- a Namespace (i.e. includes the keys ``lower``, ``upper`` and ``name``).
254
- If the response headers indicate that the response body contains a
255
- complete list of shard ranges for a sharded container then the response
256
- body will be transformed to a ``NamespaceBoundsList`` and cached.
206
+ Fetch shard namespace data from the backend and, if successful, return
207
+ a list of Namespaces.
257
208
 
258
209
  :param req: an instance of ``swob.Request``.
259
- :return: an instance of ``swob.Response``.
210
+ :param cache_enabled: a boolean which should be True if memcache is
211
+ available to cache the returned data, False otherwise.
212
+ :return: a list instance of ``Namespace`` objects or ``None`` if no
213
+ namespace data was returned from the backend.
260
214
  """
261
- # Note: We instruct the backend server to ignore name constraints in
262
- # request params if returning shard ranges so that the response can
263
- # potentially be cached, but we only cache it if the container state is
264
- # 'sharded'. We don't attempt to cache shard ranges for a 'sharding'
265
- # container as they may include the container itself as a 'gap filler'
266
- # for shard ranges that have not yet cleaved; listings from 'gap
267
- # filler' shard ranges are likely to become stale as the container
268
- # continues to cleave objects to its shards and caching them is
269
- # therefore more likely to result in stale or incomplete listings on
270
- # subsequent container GETs.
271
- req.headers['x-backend-override-shard-name-filter'] = 'sharded'
215
+ # Instruct the backend server to 'automatically' return namespaces
216
+ # of shards in a 'listing' state if the container is sharded, and
217
+ # that the more compact 'namespace' format is sufficient. Older
218
+ # container servers may still respond with the 'full' shard range
219
+ # format.
220
+ req.headers['X-Backend-Record-Type'] = 'auto'
221
+ req.headers['X-Backend-Record-Shard-Format'] = 'namespace'
222
+ # 'x-backend-include-deleted' is not expected in 'auto' requests to
223
+ # the proxy (it's not supported for objects and is used by the
224
+ # sharder when explicitly fetching 'shard' record type), but we
225
+ # explicitly set it to false here just in case. A newer container
226
+ # server would ignore it when returning namespaces, but an older
227
+ # container server would include unwanted deleted shard range.
228
+ req.headers['X-Backend-Include-Deleted'] = 'false'
229
+ params = req.params
230
+ params['states'] = 'listing'
231
+ req.params = params
232
+ if cache_enabled:
233
+ # Instruct the backend server to ignore name constraints in
234
+ # request params if returning namespaces so that the response
235
+ # can potentially be cached, but only if the container state is
236
+ # 'sharded'. We don't attempt to cache namespaces for a
237
+ # 'sharding' container as they may include the container itself
238
+ # as a 'gap filler' for shards that have not yet cleaved;
239
+ # listings from 'gap filler' namespaces are likely to become
240
+ # stale as the container continues to cleave objects to its
241
+ # shards and caching them is therefore more likely to result in
242
+ # stale or incomplete listings on subsequent container GETs.
243
+ req.headers['x-backend-override-shard-name-filter'] = 'sharded'
272
244
  resp = self._GETorHEAD_from_backend(req)
273
-
274
- sharding_state = resp.headers.get(
275
- 'x-backend-sharding-state', '').lower()
276
245
  resp_record_type = resp.headers.get(
277
246
  'x-backend-record-type', '').lower()
247
+ sharding_state = resp.headers.get(
248
+ 'x-backend-sharding-state', '').lower()
278
249
  complete_listing = config_true_value(resp.headers.pop(
279
250
  'x-backend-override-shard-name-filter', False))
280
- # given that we sent 'x-backend-override-shard-name-filter=sharded' we
281
- # should only receive back 'x-backend-override-shard-name-filter=true'
282
- # if the sharding state is 'sharded', but check them both anyway...
283
- if (resp_record_type == 'shard' and
284
- sharding_state == 'sharded' and
285
- complete_listing):
286
- ns_bound_list = self._store_shard_ranges_in_cache(req, resp)
287
- if ns_bound_list:
288
- resp.body = self._make_namespaces_response_body(
289
- req, ns_bound_list)
290
- return resp
251
+ if resp_record_type == 'shard':
252
+ data = self._parse_listing_response(req, resp)
253
+ namespaces = self._parse_namespaces(req, data, resp)
254
+ # given that we sent
255
+ # 'x-backend-override-shard-name-filter=sharded' we should only
256
+ # receive back 'x-backend-override-shard-name-filter=true' if
257
+ # the sharding state is 'sharded', but check them both
258
+ # anyway...
259
+ if (namespaces and
260
+ sharding_state == 'sharded' and
261
+ complete_listing):
262
+ namespaces = self._set_listing_namespaces_in_cache(
263
+ req, namespaces)
264
+ namespaces = self._filter_complete_listing(req, namespaces)
265
+ else:
266
+ namespaces = None
267
+ return resp, namespaces
291
268
 
292
- def _record_shard_listing_cache_metrics(
293
- self, cache_state, resp, resp_record_type, info):
269
+ def _record_shard_listing_cache_metrics(self, cache_state, resp, info):
294
270
  """
295
271
  Record a single cache operation by shard listing into its
296
272
  corresponding metrics.
@@ -299,21 +275,19 @@ class ContainerController(Controller):
299
275
  infocache_hit, memcache hit, miss, error, skip, force_skip
300
276
  and disabled.
301
277
  :param resp: the response from either backend or cache hit.
302
- :param resp_record_type: indicates the type of response record, e.g.
303
- 'shard' for shard range listing, 'object' for object listing.
304
278
  :param info: the cached container info.
305
279
  """
306
280
  should_record = False
307
281
  if is_success(resp.status_int):
308
- if resp_record_type == 'shard':
309
- # Here we either got shard ranges by hitting the cache, or we
310
- # got shard ranges from backend successfully for cache_state
282
+ if resp.headers.get('X-Backend-Record-Type', '') == 'shard':
283
+ # Here we either got namespaces by hitting the cache, or we
284
+ # got namespaces from backend successfully for cache_state
311
285
  # other than cache hit. Note: it's possible that later we find
312
- # that shard ranges can't be parsed.
286
+ # that namespaces can't be parsed.
313
287
  should_record = True
314
288
  elif (info and is_success(info['status'])
315
289
  and info.get('sharding_state') == 'sharded'):
316
- # The shard listing request failed when getting shard ranges from
290
+ # The shard listing request failed when getting namespaces from
317
291
  # backend.
318
292
  # Note: In the absence of 'info' we cannot assume the container is
319
293
  # sharded, so we don't increment the metric if 'info' is None. Even
@@ -327,36 +301,58 @@ class ContainerController(Controller):
327
301
 
328
302
  if should_record:
329
303
  record_cache_op_metrics(
330
- self.logger, 'shard_listing', cache_state, resp)
331
-
332
- def _GET_using_cache(self, req, info):
333
- # It may be possible to fulfil the request from cache: we only reach
334
- # here if request record_type is 'shard' or 'auto', so if the container
335
- # state is 'sharded' then look for cached shard ranges. However, if
336
- # X-Newest is true then we always fetch from the backend servers.
337
- headers = headers_from_container_info(info)
338
- if config_true_value(req.headers.get('x-newest', False)):
339
- cache_state = 'force_skip'
340
- self.logger.debug(
341
- 'Skipping shard cache lookup (x-newest) for %s', req.path_qs)
342
- elif (headers and info and is_success(info['status']) and
343
- info.get('sharding_state') == 'sharded'):
344
- # container is sharded so we may have the shard ranges cached; only
345
- # use cached values if all required backend headers available.
346
- resp, cache_state = self._get_shard_ranges_from_cache(req, headers)
347
- if resp:
348
- return resp, cache_state
304
+ self.logger, self.server_type.lower(), 'shard_listing',
305
+ cache_state, resp)
306
+
307
+ def _GET_auto(self, req):
308
+ # This is an object listing but the backend may be sharded.
309
+ # Only lookup container info from cache and skip the backend HEAD,
310
+ # since we are going to GET the backend container anyway.
311
+ info = get_container_info(
312
+ req.environ, self.app, swift_source=None, cache_only=True)
313
+ memcache = cache_from_env(req.environ, True)
314
+ cache_enabled = self.app.recheck_listing_shard_ranges > 0 and memcache
315
+ resp = namespaces = None
316
+ if cache_enabled:
317
+ # if the container is sharded we may look for namespaces in cache
318
+ headers = headers_from_container_info(info)
319
+ if config_true_value(req.headers.get('x-newest', False)):
320
+ cache_state = 'force_skip'
321
+ self.logger.debug(
322
+ 'Skipping shard cache lookup (x-newest) for %s',
323
+ req.path_qs)
324
+ elif (headers and is_success(info['status']) and
325
+ info.get('sharding_state') == 'sharded'):
326
+ # container is sharded so we may have the namespaces cached,
327
+ # but only use cached namespaces if all required response
328
+ # headers are also available from cache.
329
+ resp, namespaces, cache_state = \
330
+ self._get_listing_namespaces_from_cache(req, headers)
331
+ else:
332
+ # container metadata didn't support a cache lookup, this could
333
+ # be the case that container metadata was not in cache and we
334
+ # don't know if the container was sharded, or the case that the
335
+ # sharding state in metadata indicates the container was
336
+ # unsharded.
337
+ cache_state = 'bypass'
349
338
  else:
350
- # container metadata didn't support a cache lookup, this could be
351
- # the case that container metadata was not in cache and we don't
352
- # know if the container was sharded, or the case that the sharding
353
- # state in metadata indicates the container was unsharded.
354
- cache_state = 'bypass'
355
- # The request was not fulfilled from cache so send to backend server.
356
- return self._get_shard_ranges_from_backend(req), cache_state
357
-
358
- def GETorHEAD(self, req):
359
- """Handler for HTTP GET/HEAD requests."""
339
+ cache_state = 'disabled'
340
+
341
+ if not namespaces:
342
+ resp, namespaces = self._get_listing_namespaces_from_backend(
343
+ req, cache_enabled)
344
+ self._record_shard_listing_cache_metrics(cache_state, resp, info)
345
+
346
+ if namespaces is not None:
347
+ # we got namespaces, so the container must be sharded; now build
348
+ # the listing from shards
349
+ # NB: the filtered namespaces list may be empty but we still need
350
+ # to build a response body with an empty list of objects
351
+ resp = self._get_from_shards(req, resp, namespaces)
352
+
353
+ return resp
354
+
355
+ def _get_or_head_pre_check(self, req):
360
356
  ai = self.account_info(self.account_name, req)
361
357
  auto_account = self.account_name.startswith(
362
358
  self.app.auto_create_account_prefix)
@@ -370,61 +366,9 @@ class ContainerController(Controller):
370
366
  # Don't cache this. The lack of account will be cached, and that
371
367
  # is sufficient.
372
368
  return HTTPNotFound(request=req)
369
+ return None
373
370
 
374
- # The read-modify-write of params here is because the Request.params
375
- # getter dynamically generates a dict of params from the query string;
376
- # the setter must be called for new params to update the query string.
377
- params = req.params
378
- params['format'] = 'json'
379
- # x-backend-record-type may be sent via internal client e.g. from
380
- # the sharder or in probe tests
381
- record_type = req.headers.get('X-Backend-Record-Type', '').lower()
382
- if not record_type:
383
- record_type = 'auto'
384
- req.headers['X-Backend-Record-Type'] = 'auto'
385
- params['states'] = 'listing'
386
- req.params = params
387
-
388
- if (req.method == 'GET'
389
- and get_param(req, 'states') == 'listing'
390
- and record_type != 'object'):
391
- may_get_listing_shards = True
392
- # Only lookup container info from cache and skip the backend HEAD,
393
- # since we are going to GET the backend container anyway.
394
- info = get_container_info(
395
- req.environ, self.app, swift_source=None, cache_only=True)
396
- else:
397
- info = None
398
- may_get_listing_shards = False
399
-
400
- memcache = cache_from_env(req.environ, True)
401
- sr_cache_state = None
402
- if (may_get_listing_shards and
403
- self.app.recheck_listing_shard_ranges > 0
404
- and memcache
405
- and not config_true_value(
406
- req.headers.get('x-backend-include-deleted', False))):
407
- # This GET might be served from cache or might populate cache.
408
- # 'x-backend-include-deleted' is not usually expected in requests
409
- # to the proxy (it is used from sharder to container servers) but
410
- # it is included in the conditions just in case because we don't
411
- # cache deleted shard ranges.
412
- resp, sr_cache_state = self._GET_using_cache(req, info)
413
- else:
414
- resp = self._GETorHEAD_from_backend(req)
415
- if may_get_listing_shards and (
416
- not self.app.recheck_listing_shard_ranges or not memcache):
417
- sr_cache_state = 'disabled'
418
-
419
- resp_record_type = resp.headers.get('X-Backend-Record-Type', '')
420
- if sr_cache_state:
421
- self._record_shard_listing_cache_metrics(
422
- sr_cache_state, resp, resp_record_type, info)
423
-
424
- if all((req.method == "GET", record_type == 'auto',
425
- resp_record_type.lower() == 'shard')):
426
- resp = self._get_from_shards(req, resp)
427
-
371
+ def _get_or_head_post_check(self, req, resp):
428
372
  if not config_true_value(
429
373
  resp.headers.get('X-Backend-Cached-Results')):
430
374
  # Cache container metadata. We just made a request to a storage
@@ -433,6 +377,7 @@ class ContainerController(Controller):
433
377
  self.app.recheck_container_existence)
434
378
  set_info_cache(req.environ, self.account_name,
435
379
  self.container_name, resp)
380
+
436
381
  if 'swift.authorize' in req.environ:
437
382
  req.acl = wsgi_to_str(resp.headers.get('x-container-read'))
438
383
  aresp = req.environ['swift.authorize'](req)
@@ -451,17 +396,73 @@ class ContainerController(Controller):
451
396
  'False'))
452
397
  return resp
453
398
 
454
- def _get_from_shards(self, req, resp):
455
- # Construct listing using shards described by the response body.
456
- # The history of containers that have returned shard ranges is
399
+ @public
400
+ @delay_denial
401
+ @cors_validation
402
+ def GET(self, req):
403
+ """Handler for HTTP GET requests."""
404
+ # early checks for request validity
405
+ validate_container_params(req)
406
+ aresp = self._get_or_head_pre_check(req)
407
+ if aresp:
408
+ return aresp
409
+
410
+ # Always request json format from the backend. listing_formats
411
+ # middleware will take care of what the client gets back.
412
+ # The read-modify-write of params here is because the
413
+ # Request.params getter dynamically generates a dict of params from
414
+ # the query string; the setter must be called for new params to
415
+ # update the query string.
416
+ params = req.params
417
+ params['format'] = 'json'
418
+ req.params = params
419
+
420
+ # x-backend-record-type may be sent via internal client e.g. from
421
+ # the sharder or in probe tests
422
+ record_type = req.headers.get('X-Backend-Record-Type', '').lower()
423
+ if record_type in ('object', 'shard'):
424
+ # Go direct to the backend for HEADs, and GETs that *explicitly*
425
+ # specify a record type. We won't be reading/writing namespaces in
426
+ # cache nor building listings from shards. This path is used by
427
+ # the sharder, manage_shard_ranges and other tools that fetch shard
428
+ # ranges, and by the proxy itself when explicitly requesting
429
+ # objects while recursively building a listing from shards.
430
+ # Note: shard record type could be namespace or full format
431
+ resp = self._GETorHEAD_from_backend(req)
432
+ else:
433
+ # Requests that do not explicitly specify a record type, or specify
434
+ # 'auto', default to returning an object listing. The listing may
435
+ # be built from shards and may involve reading/writing namespaces
436
+ # in cache. This path is used for client requests and by the proxy
437
+ # itself while recursively building a listing from shards.
438
+ resp = self._GET_auto(req)
439
+ resp.headers.pop('X-Backend-Record-Type', None)
440
+ resp.headers.pop('X-Backend-Record-Shard-Format', None)
441
+
442
+ return self._get_or_head_post_check(req, resp)
443
+
444
+ def _get_from_shards(self, req, resp, namespaces):
445
+ """
446
+ Construct an object listing using shards described by the list of
447
+ namespaces.
448
+
449
+ :param req: an instance of :class:`~swift.common.swob.Request`.
450
+ :param resp: an instance of :class:`~swift.common.swob.Response`.
451
+ :param namespaces: a list of :class:`~swift.common.utils.Namespace`.
452
+ :return: an instance of :class:`~swift.common.swob.Response`. If an
453
+ error is encountered while building the listing an instance of
454
+ ``HTTPServiceUnavailable`` may be returned. Otherwise, the given
455
+ ``resp`` is returned with a body that is an object listing.
456
+ """
457
+ # The history of containers that have returned namespaces is
457
458
  # maintained in the request environ so that loops can be avoided by
458
459
  # forcing an object listing if the same container is visited again.
459
460
  # This can happen in at least two scenarios:
460
- # 1. a container has filled a gap in its shard ranges with a
461
- # shard range pointing to itself
462
- # 2. a root container returns a (stale) shard range pointing to a
461
+ # 1. a container has filled a gap in its namespaces with a
462
+ # namespace pointing to itself
463
+ # 2. a root container returns a (stale) namespace pointing to a
463
464
  # shard that has shrunk into the root, in which case the shrunken
464
- # shard may return the root's shard range.
465
+ # shard may return the root's namespace.
465
466
  shard_listing_history = req.environ.setdefault(
466
467
  'swift.shard_listing_history', [])
467
468
  policy_key = 'X-Backend-Storage-Policy-Index'
@@ -469,28 +470,15 @@ class ContainerController(Controller):
469
470
  # We're handling the original request to the root container: set
470
471
  # the root policy index in the request, unless it is already set,
471
472
  # so that shards will return listings for that policy index.
472
- # Note: we only get here if the root responded with shard ranges,
473
- # or if the shard ranges were cached and the cached root container
473
+ # Note: we only get here if the root responded with namespaces,
474
+ # or if the namespaces were cached and the cached root container
474
475
  # info has sharding_state==sharded; in both cases we can assume
475
476
  # that the response is "modern enough" to include
476
477
  # 'X-Backend-Storage-Policy-Index'.
477
478
  req.headers[policy_key] = resp.headers[policy_key]
478
479
  shard_listing_history.append((self.account_name, self.container_name))
479
- # Note: when the response body has been synthesised from cached data,
480
- # each item in the list only has 'name', 'lower' and 'upper' keys. We
481
- # therefore cannot use ShardRange.from_dict(), and the ShardRange
482
- # instances constructed here will only have 'name', 'lower' and 'upper'
483
- # attributes set.
484
- # Ideally we would construct Namespace objects here, but later we use
485
- # the ShardRange account and container properties to access parsed
486
- # parts of the name.
487
- shard_ranges = [ShardRange(**data) for data in json.loads(resp.body)]
488
480
  self.logger.debug('GET listing from %s shards for: %s',
489
- len(shard_ranges), req.path_qs)
490
- if not shard_ranges:
491
- # can't find ranges or there was a problem getting the ranges. So
492
- # return what we have.
493
- return resp
481
+ len(namespaces), req.path_qs)
494
482
 
495
483
  objects = []
496
484
  req_limit = constrain_req_limit(req, CONTAINER_LISTING_LIMIT)
@@ -504,12 +492,12 @@ class ContainerController(Controller):
504
492
 
505
493
  limit = req_limit
506
494
  all_resp_status = []
507
- for i, shard_range in enumerate(shard_ranges):
495
+ for i, namespace in enumerate(namespaces):
508
496
  params['limit'] = limit
509
497
  # Always set marker to ensure that object names less than or equal
510
498
  # to those already in the listing are not fetched; if the listing
511
499
  # is empty then the original request marker, if any, is used. This
512
- # allows misplaced objects below the expected shard range to be
500
+ # allows misplaced objects below the expected namespace to be
513
501
  # included in the listing.
514
502
  last_name = ''
515
503
  last_name_was_subdir = False
@@ -528,45 +516,47 @@ class ContainerController(Controller):
528
516
  else:
529
517
  params['marker'] = ''
530
518
  # Always set end_marker to ensure that misplaced objects beyond the
531
- # expected shard range are not fetched. This prevents a misplaced
519
+ # expected namespace are not fetched. This prevents a misplaced
532
520
  # object obscuring correctly placed objects in the next shard
533
521
  # range.
534
- if end_marker and end_marker in shard_range:
522
+ if end_marker and end_marker in namespace:
535
523
  params['end_marker'] = str_to_wsgi(end_marker)
536
524
  elif reverse:
537
- params['end_marker'] = str_to_wsgi(shard_range.lower_str)
525
+ params['end_marker'] = str_to_wsgi(namespace.lower_str)
538
526
  else:
539
- params['end_marker'] = str_to_wsgi(shard_range.end_marker)
527
+ params['end_marker'] = str_to_wsgi(namespace.end_marker)
540
528
 
541
529
  headers = {}
542
- if ((shard_range.account, shard_range.container) in
530
+ if ((namespace.account, namespace.container) in
543
531
  shard_listing_history):
544
532
  # directed back to same container - force GET of objects
545
533
  headers['X-Backend-Record-Type'] = 'object'
534
+ else:
535
+ headers['X-Backend-Record-Type'] = 'auto'
546
536
  if config_true_value(req.headers.get('x-newest', False)):
547
537
  headers['X-Newest'] = 'true'
548
538
 
549
539
  if prefix:
550
- if prefix > shard_range:
540
+ if prefix > namespace:
551
541
  continue
552
542
  try:
553
543
  just_past = prefix[:-1] + chr(ord(prefix[-1]) + 1)
554
544
  except ValueError:
555
545
  pass
556
546
  else:
557
- if just_past < shard_range:
547
+ if just_past < namespace:
558
548
  continue
559
549
 
560
550
  if last_name_was_subdir and str(
561
- shard_range.lower if reverse else shard_range.upper
551
+ namespace.lower if reverse else namespace.upper
562
552
  ).startswith(last_name):
563
553
  continue
564
554
 
565
555
  self.logger.debug(
566
556
  'Getting listing part %d from shard %s %s with %s',
567
- i, shard_range, shard_range.name, headers)
557
+ i, namespace, namespace.name, headers)
568
558
  objs, shard_resp = self._get_container_listing(
569
- req, shard_range.account, shard_range.container,
559
+ req, namespace.account, namespace.container,
570
560
  headers=headers, params=params)
571
561
  all_resp_status.append(shard_resp.status_int)
572
562
 
@@ -627,21 +617,16 @@ class ContainerController(Controller):
627
617
  [o['bytes'] for o in objects])
628
618
  return resp
629
619
 
630
- @public
631
- @delay_denial
632
- @cors_validation
633
- def GET(self, req):
634
- """Handler for HTTP GET requests."""
635
- # early checks for request validity
636
- validate_container_params(req)
637
- return self.GETorHEAD(req)
638
-
639
620
  @public
640
621
  @delay_denial
641
622
  @cors_validation
642
623
  def HEAD(self, req):
643
624
  """Handler for HTTP HEAD requests."""
644
- return self.GETorHEAD(req)
625
+ aresp = self._get_or_head_pre_check(req)
626
+ if aresp:
627
+ return aresp
628
+ resp = self._GETorHEAD_from_backend(req)
629
+ return self._get_or_head_post_check(req, resp)
645
630
 
646
631
  @public
647
632
  @cors_validation