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.
- swift/account/auditor.py +11 -0
- swift/account/reaper.py +11 -1
- swift/account/replicator.py +22 -0
- swift/account/server.py +13 -12
- swift-2.32.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
- swift-2.32.0.data/scripts/swift-config → swift/cli/config.py +1 -1
- swift-2.32.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
- swift-2.32.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
- swift-2.32.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
- swift/cli/info.py +131 -3
- swift-2.32.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
- swift-2.32.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
- swift-2.32.0.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +9 -18
- swift-2.32.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
- swift/cli/relinker.py +1 -1
- swift/cli/reload.py +141 -0
- swift/cli/ringbuilder.py +24 -0
- swift/common/daemon.py +12 -2
- swift/common/db.py +14 -9
- swift/common/db_auditor.py +2 -2
- swift/common/db_replicator.py +6 -0
- swift/common/exceptions.py +12 -0
- swift/common/http_protocol.py +76 -3
- swift/common/manager.py +120 -5
- swift/common/memcached.py +24 -25
- swift/common/middleware/account_quotas.py +144 -43
- swift/common/middleware/backend_ratelimit.py +166 -24
- swift/common/middleware/catch_errors.py +1 -3
- swift/common/middleware/cname_lookup.py +3 -5
- swift/common/middleware/container_sync.py +6 -10
- swift/common/middleware/crypto/crypto_utils.py +4 -5
- swift/common/middleware/crypto/decrypter.py +4 -5
- swift/common/middleware/crypto/kms_keymaster.py +2 -1
- swift/common/middleware/proxy_logging.py +57 -43
- swift/common/middleware/ratelimit.py +6 -7
- swift/common/middleware/recon.py +6 -7
- swift/common/middleware/s3api/acl_handlers.py +10 -1
- swift/common/middleware/s3api/controllers/__init__.py +3 -0
- swift/common/middleware/s3api/controllers/acl.py +3 -2
- swift/common/middleware/s3api/controllers/logging.py +2 -2
- swift/common/middleware/s3api/controllers/multi_upload.py +31 -15
- swift/common/middleware/s3api/controllers/obj.py +20 -1
- swift/common/middleware/s3api/controllers/object_lock.py +44 -0
- swift/common/middleware/s3api/s3api.py +6 -0
- swift/common/middleware/s3api/s3request.py +190 -74
- swift/common/middleware/s3api/s3response.py +48 -8
- swift/common/middleware/s3api/s3token.py +2 -2
- swift/common/middleware/s3api/utils.py +2 -1
- swift/common/middleware/slo.py +508 -310
- swift/common/middleware/staticweb.py +45 -14
- swift/common/middleware/tempauth.py +6 -4
- swift/common/middleware/tempurl.py +134 -93
- swift/common/middleware/x_profile/exceptions.py +1 -4
- swift/common/middleware/x_profile/html_viewer.py +9 -10
- swift/common/middleware/x_profile/profile_model.py +1 -2
- swift/common/middleware/xprofile.py +1 -2
- swift/common/request_helpers.py +101 -8
- swift/common/statsd_client.py +207 -0
- swift/common/storage_policy.py +1 -1
- swift/common/swob.py +5 -2
- swift/common/utils/__init__.py +331 -1774
- swift/common/utils/base.py +138 -0
- swift/common/utils/config.py +443 -0
- swift/common/utils/logs.py +999 -0
- swift/common/utils/timestamp.py +23 -2
- swift/common/wsgi.py +19 -3
- swift/container/auditor.py +11 -0
- swift/container/backend.py +136 -31
- swift/container/reconciler.py +11 -2
- swift/container/replicator.py +64 -7
- swift/container/server.py +276 -146
- swift/container/sharder.py +86 -42
- swift/container/sync.py +11 -1
- swift/container/updater.py +12 -2
- swift/obj/auditor.py +20 -3
- swift/obj/diskfile.py +63 -25
- swift/obj/expirer.py +154 -47
- swift/obj/mem_diskfile.py +2 -1
- swift/obj/mem_server.py +1 -0
- swift/obj/reconstructor.py +28 -4
- swift/obj/replicator.py +63 -24
- swift/obj/server.py +76 -59
- swift/obj/updater.py +12 -2
- swift/obj/watchers/dark_data.py +72 -34
- swift/proxy/controllers/account.py +3 -2
- swift/proxy/controllers/base.py +254 -148
- swift/proxy/controllers/container.py +274 -289
- swift/proxy/controllers/obj.py +120 -166
- swift/proxy/server.py +17 -13
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +14 -4
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/METADATA +9 -7
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/RECORD +97 -120
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +39 -0
- swift-2.34.0.dist-info/pbr.json +1 -0
- swift-2.32.0.data/scripts/swift-account-auditor +0 -23
- swift-2.32.0.data/scripts/swift-account-info +0 -52
- swift-2.32.0.data/scripts/swift-account-reaper +0 -23
- swift-2.32.0.data/scripts/swift-account-replicator +0 -34
- swift-2.32.0.data/scripts/swift-account-server +0 -23
- swift-2.32.0.data/scripts/swift-container-auditor +0 -23
- swift-2.32.0.data/scripts/swift-container-info +0 -56
- swift-2.32.0.data/scripts/swift-container-reconciler +0 -21
- swift-2.32.0.data/scripts/swift-container-replicator +0 -34
- swift-2.32.0.data/scripts/swift-container-server +0 -23
- swift-2.32.0.data/scripts/swift-container-sharder +0 -37
- swift-2.32.0.data/scripts/swift-container-sync +0 -23
- swift-2.32.0.data/scripts/swift-container-updater +0 -23
- swift-2.32.0.data/scripts/swift-dispersion-report +0 -24
- swift-2.32.0.data/scripts/swift-form-signature +0 -20
- swift-2.32.0.data/scripts/swift-init +0 -119
- swift-2.32.0.data/scripts/swift-object-auditor +0 -29
- swift-2.32.0.data/scripts/swift-object-expirer +0 -33
- swift-2.32.0.data/scripts/swift-object-info +0 -60
- swift-2.32.0.data/scripts/swift-object-reconstructor +0 -33
- swift-2.32.0.data/scripts/swift-object-relinker +0 -23
- swift-2.32.0.data/scripts/swift-object-replicator +0 -37
- swift-2.32.0.data/scripts/swift-object-server +0 -27
- swift-2.32.0.data/scripts/swift-object-updater +0 -23
- swift-2.32.0.data/scripts/swift-proxy-server +0 -23
- swift-2.32.0.data/scripts/swift-recon +0 -24
- swift-2.32.0.data/scripts/swift-ring-builder +0 -37
- swift-2.32.0.data/scripts/swift-ring-builder-analyzer +0 -22
- swift-2.32.0.data/scripts/swift-ring-composer +0 -22
- swift-2.32.0.dist-info/pbr.json +0 -1
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
- {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
- {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,
|
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(
|
107
|
-
|
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
|
111
|
+
def _filter_complete_listing(self, req, namespaces):
|
114
112
|
"""
|
115
|
-
Filter namespaces
|
116
|
-
|
113
|
+
Filter complete list of namespaces to return only those specified by
|
114
|
+
the request constraints.
|
117
115
|
|
118
|
-
:param req:
|
119
|
-
:param
|
120
|
-
|
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
|
130
|
+
return namespaces
|
135
131
|
|
136
|
-
def
|
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
|
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
|
-
:
|
146
|
-
|
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
|
-
|
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
|
-
|
156
|
-
|
157
|
-
if ns_bound_list:
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
210
|
-
|
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
|
214
|
-
|
215
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
220
|
-
#
|
221
|
-
#
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
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
|
-
:
|
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
|
-
#
|
262
|
-
#
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
266
|
-
|
267
|
-
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
271
|
-
|
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
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
309
|
-
# Here we either got
|
310
|
-
# got
|
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
|
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
|
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',
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
#
|
335
|
-
#
|
336
|
-
#
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
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
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
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
|
-
|
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
|
-
|
455
|
-
|
456
|
-
|
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
|
461
|
-
#
|
462
|
-
# 2. a root container returns a (stale)
|
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
|
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
|
473
|
-
# or if the
|
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(
|
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,
|
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
|
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
|
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
|
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(
|
525
|
+
params['end_marker'] = str_to_wsgi(namespace.lower_str)
|
538
526
|
else:
|
539
|
-
params['end_marker'] = str_to_wsgi(
|
527
|
+
params['end_marker'] = str_to_wsgi(namespace.end_marker)
|
540
528
|
|
541
529
|
headers = {}
|
542
|
-
if ((
|
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 >
|
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 <
|
547
|
+
if just_past < namespace:
|
558
548
|
continue
|
559
549
|
|
560
550
|
if last_name_was_subdir and str(
|
561
|
-
|
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,
|
557
|
+
i, namespace, namespace.name, headers)
|
568
558
|
objs, shard_resp = self._get_container_listing(
|
569
|
-
req,
|
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
|
-
|
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
|