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.
- swift/__init__.py +29 -50
- swift/account/auditor.py +21 -118
- swift/account/backend.py +33 -28
- swift/account/reaper.py +37 -28
- swift/account/replicator.py +22 -0
- swift/account/server.py +60 -26
- swift/account/utils.py +28 -11
- swift-2.23.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
- swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
- swift/cli/container_deleter.py +5 -11
- swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
- swift/cli/dispersion_report.py +10 -9
- swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
- swift/cli/form_signature.py +3 -7
- swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
- swift/cli/info.py +154 -14
- swift/cli/manage_shard_ranges.py +705 -37
- swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
- swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
- swift/cli/recon.py +196 -67
- swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
- swift-2.23.3.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
- swift/cli/relinker.py +807 -126
- swift/cli/reload.py +135 -0
- swift/cli/ringbuilder.py +217 -20
- swift/cli/ringcomposer.py +0 -1
- swift/cli/shard-info.py +4 -3
- swift/common/base_storage_server.py +9 -20
- swift/common/bufferedhttp.py +48 -74
- swift/common/constraints.py +20 -15
- swift/common/container_sync_realms.py +9 -11
- swift/common/daemon.py +25 -8
- swift/common/db.py +195 -128
- swift/common/db_auditor.py +168 -0
- swift/common/db_replicator.py +95 -55
- swift/common/digest.py +141 -0
- swift/common/direct_client.py +144 -33
- swift/common/error_limiter.py +93 -0
- swift/common/exceptions.py +25 -1
- swift/common/header_key_dict.py +2 -9
- swift/common/http_protocol.py +373 -0
- swift/common/internal_client.py +129 -59
- swift/common/linkat.py +3 -4
- swift/common/manager.py +284 -67
- swift/common/memcached.py +390 -145
- swift/common/middleware/__init__.py +4 -0
- swift/common/middleware/account_quotas.py +211 -46
- swift/common/middleware/acl.py +3 -8
- swift/common/middleware/backend_ratelimit.py +230 -0
- swift/common/middleware/bulk.py +22 -34
- swift/common/middleware/catch_errors.py +1 -3
- swift/common/middleware/cname_lookup.py +6 -11
- swift/common/middleware/container_quotas.py +1 -1
- swift/common/middleware/container_sync.py +39 -17
- swift/common/middleware/copy.py +12 -0
- swift/common/middleware/crossdomain.py +22 -9
- swift/common/middleware/crypto/__init__.py +2 -1
- swift/common/middleware/crypto/crypto_utils.py +11 -15
- swift/common/middleware/crypto/decrypter.py +28 -11
- swift/common/middleware/crypto/encrypter.py +12 -17
- swift/common/middleware/crypto/keymaster.py +8 -15
- swift/common/middleware/crypto/kms_keymaster.py +2 -1
- swift/common/middleware/dlo.py +15 -11
- swift/common/middleware/domain_remap.py +5 -4
- swift/common/middleware/etag_quoter.py +128 -0
- swift/common/middleware/formpost.py +73 -70
- swift/common/middleware/gatekeeper.py +8 -1
- swift/common/middleware/keystoneauth.py +33 -3
- swift/common/middleware/list_endpoints.py +4 -4
- swift/common/middleware/listing_formats.py +85 -49
- swift/common/middleware/memcache.py +4 -95
- swift/common/middleware/name_check.py +3 -2
- swift/common/middleware/proxy_logging.py +160 -92
- swift/common/middleware/ratelimit.py +17 -10
- swift/common/middleware/read_only.py +6 -4
- swift/common/middleware/recon.py +59 -22
- swift/common/middleware/s3api/acl_handlers.py +25 -3
- swift/common/middleware/s3api/acl_utils.py +6 -1
- swift/common/middleware/s3api/controllers/__init__.py +6 -0
- swift/common/middleware/s3api/controllers/acl.py +3 -2
- swift/common/middleware/s3api/controllers/bucket.py +242 -137
- swift/common/middleware/s3api/controllers/logging.py +2 -2
- swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
- swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
- swift/common/middleware/s3api/controllers/obj.py +112 -8
- swift/common/middleware/s3api/controllers/object_lock.py +44 -0
- swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
- swift/common/middleware/s3api/controllers/tagging.py +57 -0
- swift/common/middleware/s3api/controllers/versioning.py +36 -7
- swift/common/middleware/s3api/etree.py +22 -9
- swift/common/middleware/s3api/exception.py +0 -4
- swift/common/middleware/s3api/s3api.py +113 -41
- swift/common/middleware/s3api/s3request.py +384 -218
- swift/common/middleware/s3api/s3response.py +126 -23
- swift/common/middleware/s3api/s3token.py +16 -17
- swift/common/middleware/s3api/schema/delete.rng +1 -1
- swift/common/middleware/s3api/subresource.py +7 -10
- swift/common/middleware/s3api/utils.py +27 -10
- swift/common/middleware/slo.py +665 -358
- swift/common/middleware/staticweb.py +64 -37
- swift/common/middleware/symlink.py +51 -18
- swift/common/middleware/tempauth.py +76 -58
- swift/common/middleware/tempurl.py +191 -173
- swift/common/middleware/versioned_writes/__init__.py +51 -0
- swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
- swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
- swift/common/middleware/x_profile/exceptions.py +1 -4
- swift/common/middleware/x_profile/html_viewer.py +18 -19
- swift/common/middleware/x_profile/profile_model.py +1 -2
- swift/common/middleware/xprofile.py +10 -10
- swift-2.23.3.data/scripts/swift-container-server → swift/common/recon.py +13 -8
- swift/common/registry.py +147 -0
- swift/common/request_helpers.py +324 -57
- swift/common/ring/builder.py +67 -25
- swift/common/ring/composite_builder.py +1 -1
- swift/common/ring/ring.py +177 -51
- swift/common/ring/utils.py +1 -1
- swift/common/splice.py +10 -6
- swift/common/statsd_client.py +205 -0
- swift/common/storage_policy.py +49 -44
- swift/common/swob.py +86 -102
- swift/common/{utils.py → utils/__init__.py} +2163 -2772
- swift/common/utils/base.py +131 -0
- swift/common/utils/config.py +433 -0
- swift/common/utils/ipaddrs.py +256 -0
- swift/common/utils/libc.py +345 -0
- swift/common/utils/logs.py +859 -0
- swift/common/utils/timestamp.py +412 -0
- swift/common/wsgi.py +553 -535
- swift/container/auditor.py +14 -100
- swift/container/backend.py +490 -231
- swift/container/reconciler.py +126 -37
- swift/container/replicator.py +96 -22
- swift/container/server.py +358 -165
- swift/container/sharder.py +1540 -684
- swift/container/sync.py +94 -88
- swift/container/updater.py +53 -32
- swift/obj/auditor.py +153 -35
- swift/obj/diskfile.py +466 -217
- swift/obj/expirer.py +406 -124
- swift/obj/mem_diskfile.py +7 -4
- swift/obj/mem_server.py +1 -0
- swift/obj/reconstructor.py +523 -262
- swift/obj/replicator.py +249 -188
- swift/obj/server.py +207 -122
- swift/obj/ssync_receiver.py +145 -85
- swift/obj/ssync_sender.py +113 -54
- swift/obj/updater.py +652 -139
- swift/obj/watchers/__init__.py +0 -0
- swift/obj/watchers/dark_data.py +213 -0
- swift/proxy/controllers/account.py +11 -11
- swift/proxy/controllers/base.py +848 -604
- swift/proxy/controllers/container.py +433 -92
- swift/proxy/controllers/info.py +3 -2
- swift/proxy/controllers/obj.py +1000 -489
- swift/proxy/server.py +185 -112
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
- swift-2.35.0.dist-info/RECORD +201 -0
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
- swift-2.35.0.dist-info/pbr.json +1 -0
- swift/locale/de/LC_MESSAGES/swift.po +0 -1216
- swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
- swift/locale/es/LC_MESSAGES/swift.po +0 -1085
- swift/locale/fr/LC_MESSAGES/swift.po +0 -909
- swift/locale/it/LC_MESSAGES/swift.po +0 -894
- swift/locale/ja/LC_MESSAGES/swift.po +0 -965
- swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
- swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
- swift/locale/ru/LC_MESSAGES/swift.po +0 -891
- swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
- swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
- swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
- swift-2.23.3.data/scripts/swift-account-auditor +0 -23
- swift-2.23.3.data/scripts/swift-account-info +0 -51
- swift-2.23.3.data/scripts/swift-account-reaper +0 -23
- swift-2.23.3.data/scripts/swift-account-replicator +0 -34
- swift-2.23.3.data/scripts/swift-account-server +0 -23
- swift-2.23.3.data/scripts/swift-container-auditor +0 -23
- swift-2.23.3.data/scripts/swift-container-info +0 -55
- swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
- swift-2.23.3.data/scripts/swift-container-replicator +0 -34
- swift-2.23.3.data/scripts/swift-container-sharder +0 -37
- swift-2.23.3.data/scripts/swift-container-sync +0 -23
- swift-2.23.3.data/scripts/swift-container-updater +0 -23
- swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
- swift-2.23.3.data/scripts/swift-form-signature +0 -20
- swift-2.23.3.data/scripts/swift-init +0 -119
- swift-2.23.3.data/scripts/swift-object-auditor +0 -29
- swift-2.23.3.data/scripts/swift-object-expirer +0 -33
- swift-2.23.3.data/scripts/swift-object-info +0 -60
- swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
- swift-2.23.3.data/scripts/swift-object-relinker +0 -41
- swift-2.23.3.data/scripts/swift-object-replicator +0 -37
- swift-2.23.3.data/scripts/swift-object-server +0 -27
- swift-2.23.3.data/scripts/swift-object-updater +0 -23
- swift-2.23.3.data/scripts/swift-proxy-server +0 -23
- swift-2.23.3.data/scripts/swift-recon +0 -24
- swift-2.23.3.data/scripts/swift-ring-builder +0 -24
- swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
- swift-2.23.3.data/scripts/swift-ring-composer +0 -22
- swift-2.23.3.dist-info/RECORD +0 -220
- swift-2.23.3.dist-info/pbr.json +0 -1
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
@@ -13,23 +13,23 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
|
-
from swift import gettext_ as _
|
17
16
|
import json
|
18
17
|
|
19
|
-
import
|
20
|
-
from six.moves.urllib.parse import unquote
|
18
|
+
from urllib.parse import unquote
|
21
19
|
|
22
20
|
from swift.common.utils import public, private, csv_append, Timestamp, \
|
23
|
-
config_true_value,
|
21
|
+
config_true_value, cache_from_env, filter_namespaces, NamespaceBoundList
|
24
22
|
from swift.common.constraints import check_metadata, CONTAINER_LISTING_LIMIT
|
25
23
|
from swift.common.http import HTTP_ACCEPTED, is_success
|
26
|
-
from swift.common.request_helpers import get_sys_meta_prefix
|
27
|
-
|
28
|
-
|
24
|
+
from swift.common.request_helpers import get_sys_meta_prefix, get_param, \
|
25
|
+
constrain_req_limit, validate_container_params
|
26
|
+
from swift.proxy.controllers.base import Controller, delay_denial, NodeIter, \
|
27
|
+
cors_validation, set_info_cache, clear_info_cache, get_container_info, \
|
28
|
+
record_cache_op_metrics, get_cache_key, headers_from_container_info, \
|
29
|
+
update_headers, set_namespaces_in_cache, get_namespaces_from_cache
|
29
30
|
from swift.common.storage_policy import POLICIES
|
30
|
-
from swift.common.swob import HTTPBadRequest, HTTPForbidden, \
|
31
|
-
|
32
|
-
bytes_to_wsgi
|
31
|
+
from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \
|
32
|
+
HTTPServiceUnavailable, str_to_wsgi, wsgi_to_str, Response
|
33
33
|
|
34
34
|
|
35
35
|
class ContainerController(Controller):
|
@@ -87,8 +87,271 @@ class ContainerController(Controller):
|
|
87
87
|
return HTTPBadRequest(request=req, body=str(err))
|
88
88
|
return None
|
89
89
|
|
90
|
-
def
|
91
|
-
|
90
|
+
def _clear_container_info_cache(self, req):
|
91
|
+
clear_info_cache(req.environ,
|
92
|
+
self.account_name, self.container_name)
|
93
|
+
clear_info_cache(req.environ,
|
94
|
+
self.account_name, self.container_name, 'listing')
|
95
|
+
# TODO: should we also purge updating shards from cache?
|
96
|
+
|
97
|
+
def _GETorHEAD_from_backend(self, req):
|
98
|
+
part = self.app.container_ring.get_part(
|
99
|
+
self.account_name, self.container_name)
|
100
|
+
concurrency = self.app.container_ring.replica_count \
|
101
|
+
if self.app.get_policy_options(None).concurrent_gets else 1
|
102
|
+
node_iter = NodeIter(
|
103
|
+
'container', self.app, self.app.container_ring, part,
|
104
|
+
self.logger, req)
|
105
|
+
resp = self.GETorHEAD_base(
|
106
|
+
req, 'Container', node_iter, part,
|
107
|
+
req.swift_entity_path, concurrency)
|
108
|
+
return resp
|
109
|
+
|
110
|
+
def _filter_complete_listing(self, req, namespaces):
|
111
|
+
"""
|
112
|
+
Filter complete list of namespaces to return only those specified by
|
113
|
+
the request constraints.
|
114
|
+
|
115
|
+
:param req: a :class:`~swift.common.swob.Request`.
|
116
|
+
:param namespaces: a list of :class:`~swift.common.utils.Namespace`.
|
117
|
+
:return: a list of :class:`~swift.common.utils.Namespace`.
|
118
|
+
"""
|
119
|
+
marker = get_param(req, 'marker', '')
|
120
|
+
end_marker = get_param(req, 'end_marker')
|
121
|
+
includes = get_param(req, 'includes')
|
122
|
+
reverse = config_true_value(get_param(req, 'reverse'))
|
123
|
+
if reverse:
|
124
|
+
marker, end_marker = end_marker, marker
|
125
|
+
namespaces = filter_namespaces(
|
126
|
+
namespaces, includes, marker, end_marker)
|
127
|
+
if reverse:
|
128
|
+
namespaces.reverse()
|
129
|
+
return namespaces
|
130
|
+
|
131
|
+
def _get_listing_namespaces_from_cache(self, req, headers):
|
132
|
+
"""
|
133
|
+
Try to fetch shard namespace data from cache and, if successful, return
|
134
|
+
a list of Namespaces. Also return the cache state.
|
135
|
+
|
136
|
+
:param req: an instance of ``swob.Request``.
|
137
|
+
:return: a tuple comprising (a list instance of ``Namespace`` objects
|
138
|
+
or ``None`` if no namespaces were found in cache, the cache state).
|
139
|
+
"""
|
140
|
+
cache_key = get_cache_key(self.account_name, self.container_name,
|
141
|
+
shard='listing')
|
142
|
+
skip_chance = self.app.container_listing_shard_ranges_skip_cache
|
143
|
+
ns_bound_list, cache_state = get_namespaces_from_cache(
|
144
|
+
req, cache_key, skip_chance)
|
145
|
+
if not ns_bound_list:
|
146
|
+
return None, None, cache_state
|
147
|
+
|
148
|
+
# Namespaces found in cache so there is no need to go to backend,
|
149
|
+
# but we need to build response headers: mimic
|
150
|
+
# GetOrHeadHandler.get_working_response...
|
151
|
+
# note: server sets charset with content_type but proxy
|
152
|
+
# GETorHEAD_base does not, so don't set it here either
|
153
|
+
namespaces = ns_bound_list.get_namespaces()
|
154
|
+
self.logger.debug('Found %d shards in cache for %s',
|
155
|
+
len(namespaces), req.path_qs)
|
156
|
+
headers.update({'x-backend-record-type': 'shard',
|
157
|
+
'x-backend-record-shard-format': 'namespace',
|
158
|
+
'x-backend-cached-results': 'true'})
|
159
|
+
resp = Response(request=req)
|
160
|
+
update_headers(resp, headers)
|
161
|
+
resp.last_modified = Timestamp(headers['x-put-timestamp']).ceil()
|
162
|
+
resp.environ['swift_x_timestamp'] = headers.get('x-timestamp')
|
163
|
+
resp.accept_ranges = 'bytes'
|
164
|
+
resp.content_type = 'application/json'
|
165
|
+
namespaces = self._filter_complete_listing(req, namespaces)
|
166
|
+
return resp, namespaces, cache_state
|
167
|
+
|
168
|
+
def _set_listing_namespaces_in_cache(self, req, namespaces):
|
169
|
+
"""
|
170
|
+
Store a list of namespaces in both infocache and memcache.
|
171
|
+
|
172
|
+
Note: the returned list of namespaces may not be identical to the given
|
173
|
+
list. Any gaps in the given namespaces will be 'lost' as a result of
|
174
|
+
compacting the list of namespaces to a NamespaceBoundList for caching.
|
175
|
+
That is ok. When the cached NamespaceBoundList is transformed back to
|
176
|
+
Namespaces to perform a listing, the Namespace before each gap will
|
177
|
+
have expanded to include the gap, which means that the backend GET to
|
178
|
+
that shard will have an end_marker beyond that shard's upper bound, and
|
179
|
+
equal to the next available shard's lower. At worst, some misplaced
|
180
|
+
objects, in the gap above the shard's upper, may be included in the
|
181
|
+
shard's response.
|
182
|
+
|
183
|
+
:param req: the request object.
|
184
|
+
:param namespaces: a list of :class:`~swift.common.utils.Namespace`
|
185
|
+
objects.
|
186
|
+
:return: a list of :class:`~swift.common.utils.Namespace` objects.
|
187
|
+
"""
|
188
|
+
cache_key = get_cache_key(self.account_name, self.container_name,
|
189
|
+
shard='listing')
|
190
|
+
ns_bound_list = NamespaceBoundList.parse(namespaces)
|
191
|
+
# cache in infocache even if no namespaces returned; this
|
192
|
+
# is unexpected but use that result for this request
|
193
|
+
set_cache_state = set_namespaces_in_cache(
|
194
|
+
req, cache_key, ns_bound_list,
|
195
|
+
self.app.recheck_listing_shard_ranges)
|
196
|
+
if set_cache_state == 'set':
|
197
|
+
self.logger.info(
|
198
|
+
'Caching listing namespaces for %s (%d namespaces)',
|
199
|
+
cache_key, len(ns_bound_list.bounds))
|
200
|
+
# return the de-gapped namespaces
|
201
|
+
return ns_bound_list.get_namespaces()
|
202
|
+
|
203
|
+
def _get_listing_namespaces_from_backend(self, req, cache_enabled):
|
204
|
+
"""
|
205
|
+
Fetch shard namespace data from the backend and, if successful, return
|
206
|
+
a list of Namespaces.
|
207
|
+
|
208
|
+
:param req: an instance of ``swob.Request``.
|
209
|
+
:param cache_enabled: a boolean which should be True if memcache is
|
210
|
+
available to cache the returned data, False otherwise.
|
211
|
+
:return: a list instance of ``Namespace`` objects or ``None`` if no
|
212
|
+
namespace data was returned from the backend.
|
213
|
+
"""
|
214
|
+
# Instruct the backend server to 'automatically' return namespaces
|
215
|
+
# of shards in a 'listing' state if the container is sharded, and
|
216
|
+
# that the more compact 'namespace' format is sufficient. Older
|
217
|
+
# container servers may still respond with the 'full' shard range
|
218
|
+
# format.
|
219
|
+
req.headers['X-Backend-Record-Type'] = 'auto'
|
220
|
+
req.headers['X-Backend-Record-Shard-Format'] = 'namespace'
|
221
|
+
# 'x-backend-include-deleted' is not expected in 'auto' requests to
|
222
|
+
# the proxy (it's not supported for objects and is used by the
|
223
|
+
# sharder when explicitly fetching 'shard' record type), but we
|
224
|
+
# explicitly set it to false here just in case. A newer container
|
225
|
+
# server would ignore it when returning namespaces, but an older
|
226
|
+
# container server would include unwanted deleted shard range.
|
227
|
+
req.headers['X-Backend-Include-Deleted'] = 'false'
|
228
|
+
params = req.params
|
229
|
+
params['states'] = 'listing'
|
230
|
+
req.params = params
|
231
|
+
if cache_enabled:
|
232
|
+
# Instruct the backend server to ignore name constraints in
|
233
|
+
# request params if returning namespaces so that the response
|
234
|
+
# can potentially be cached, but only if the container state is
|
235
|
+
# 'sharded'. We don't attempt to cache namespaces for a
|
236
|
+
# 'sharding' container as they may include the container itself
|
237
|
+
# as a 'gap filler' for shards that have not yet cleaved;
|
238
|
+
# listings from 'gap filler' namespaces are likely to become
|
239
|
+
# stale as the container continues to cleave objects to its
|
240
|
+
# shards and caching them is therefore more likely to result in
|
241
|
+
# stale or incomplete listings on subsequent container GETs.
|
242
|
+
req.headers['x-backend-override-shard-name-filter'] = 'sharded'
|
243
|
+
resp = self._GETorHEAD_from_backend(req)
|
244
|
+
resp_record_type = resp.headers.get(
|
245
|
+
'x-backend-record-type', '').lower()
|
246
|
+
sharding_state = resp.headers.get(
|
247
|
+
'x-backend-sharding-state', '').lower()
|
248
|
+
complete_listing = config_true_value(resp.headers.pop(
|
249
|
+
'x-backend-override-shard-name-filter', False))
|
250
|
+
if resp_record_type == 'shard':
|
251
|
+
data = self._parse_listing_response(req, resp)
|
252
|
+
namespaces = self._parse_namespaces(req, data, resp)
|
253
|
+
# given that we sent
|
254
|
+
# 'x-backend-override-shard-name-filter=sharded' we should only
|
255
|
+
# receive back 'x-backend-override-shard-name-filter=true' if
|
256
|
+
# the sharding state is 'sharded', but check them both
|
257
|
+
# anyway...
|
258
|
+
if (namespaces and
|
259
|
+
sharding_state == 'sharded' and
|
260
|
+
complete_listing):
|
261
|
+
namespaces = self._set_listing_namespaces_in_cache(
|
262
|
+
req, namespaces)
|
263
|
+
namespaces = self._filter_complete_listing(req, namespaces)
|
264
|
+
else:
|
265
|
+
namespaces = None
|
266
|
+
return resp, namespaces
|
267
|
+
|
268
|
+
def _record_shard_listing_cache_metrics(self, cache_state, resp, info):
|
269
|
+
"""
|
270
|
+
Record a single cache operation by shard listing into its
|
271
|
+
corresponding metrics.
|
272
|
+
|
273
|
+
:param cache_state: the state of this cache operation, includes
|
274
|
+
infocache_hit, memcache hit, miss, error, skip, force_skip
|
275
|
+
and disabled.
|
276
|
+
:param resp: the response from either backend or cache hit.
|
277
|
+
:param info: the cached container info.
|
278
|
+
"""
|
279
|
+
should_record = False
|
280
|
+
if is_success(resp.status_int):
|
281
|
+
if resp.headers.get('X-Backend-Record-Type', '') == 'shard':
|
282
|
+
# Here we either got namespaces by hitting the cache, or we
|
283
|
+
# got namespaces from backend successfully for cache_state
|
284
|
+
# other than cache hit. Note: it's possible that later we find
|
285
|
+
# that namespaces can't be parsed.
|
286
|
+
should_record = True
|
287
|
+
elif (info and is_success(info['status'])
|
288
|
+
and info.get('sharding_state') == 'sharded'):
|
289
|
+
# The shard listing request failed when getting namespaces from
|
290
|
+
# backend.
|
291
|
+
# Note: In the absence of 'info' we cannot assume the container is
|
292
|
+
# sharded, so we don't increment the metric if 'info' is None. Even
|
293
|
+
# when we have valid info, we can't be sure that the container is
|
294
|
+
# sharded, but we assume info was correct and increment the failure
|
295
|
+
# metrics.
|
296
|
+
should_record = True
|
297
|
+
# else:
|
298
|
+
# The request failed, but in the absence of info we cannot assume
|
299
|
+
# the container is sharded, so we don't increment the metric.
|
300
|
+
|
301
|
+
if should_record:
|
302
|
+
record_cache_op_metrics(
|
303
|
+
self.logger, self.server_type.lower(), 'shard_listing',
|
304
|
+
cache_state, resp)
|
305
|
+
|
306
|
+
def _GET_auto(self, req):
|
307
|
+
# This is an object listing but the backend may be sharded.
|
308
|
+
# Only lookup container info from cache and skip the backend HEAD,
|
309
|
+
# since we are going to GET the backend container anyway.
|
310
|
+
info = get_container_info(
|
311
|
+
req.environ, self.app, swift_source=None, cache_only=True)
|
312
|
+
memcache = cache_from_env(req.environ, True)
|
313
|
+
cache_enabled = self.app.recheck_listing_shard_ranges > 0 and memcache
|
314
|
+
resp = namespaces = None
|
315
|
+
if cache_enabled:
|
316
|
+
# if the container is sharded we may look for namespaces in cache
|
317
|
+
headers = headers_from_container_info(info)
|
318
|
+
if config_true_value(req.headers.get('x-newest', False)):
|
319
|
+
cache_state = 'force_skip'
|
320
|
+
self.logger.debug(
|
321
|
+
'Skipping shard cache lookup (x-newest) for %s',
|
322
|
+
req.path_qs)
|
323
|
+
elif (headers and is_success(info['status']) and
|
324
|
+
info.get('sharding_state') == 'sharded'):
|
325
|
+
# container is sharded so we may have the namespaces cached,
|
326
|
+
# but only use cached namespaces if all required response
|
327
|
+
# headers are also available from cache.
|
328
|
+
resp, namespaces, cache_state = \
|
329
|
+
self._get_listing_namespaces_from_cache(req, headers)
|
330
|
+
else:
|
331
|
+
# container metadata didn't support a cache lookup, this could
|
332
|
+
# be the case that container metadata was not in cache and we
|
333
|
+
# don't know if the container was sharded, or the case that the
|
334
|
+
# sharding state in metadata indicates the container was
|
335
|
+
# unsharded.
|
336
|
+
cache_state = 'bypass'
|
337
|
+
else:
|
338
|
+
cache_state = 'disabled'
|
339
|
+
|
340
|
+
if not namespaces:
|
341
|
+
resp, namespaces = self._get_listing_namespaces_from_backend(
|
342
|
+
req, cache_enabled)
|
343
|
+
self._record_shard_listing_cache_metrics(cache_state, resp, info)
|
344
|
+
|
345
|
+
if namespaces is not None:
|
346
|
+
# we got namespaces, so the container must be sharded; now build
|
347
|
+
# the listing from shards
|
348
|
+
# NB: the filtered namespaces list may be empty but we still need
|
349
|
+
# to build a response body with an empty list of objects
|
350
|
+
resp = self._get_from_shards(req, resp, namespaces)
|
351
|
+
|
352
|
+
return resp
|
353
|
+
|
354
|
+
def _get_or_head_pre_check(self, req):
|
92
355
|
ai = self.account_info(self.account_name, req)
|
93
356
|
auto_account = self.account_name.startswith(
|
94
357
|
self.app.auto_create_account_prefix)
|
@@ -102,35 +365,20 @@ class ContainerController(Controller):
|
|
102
365
|
# Don't cache this. The lack of account will be cached, and that
|
103
366
|
# is sufficient.
|
104
367
|
return HTTPNotFound(request=req)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
req.params = params
|
118
|
-
resp = self.GETorHEAD_base(
|
119
|
-
req, _('Container'), node_iter, part,
|
120
|
-
req.swift_entity_path, concurrency)
|
121
|
-
resp_record_type = resp.headers.get('X-Backend-Record-Type', '')
|
122
|
-
if all((req.method == "GET", record_type == 'auto',
|
123
|
-
resp_record_type.lower() == 'shard')):
|
124
|
-
resp = self._get_from_shards(req, resp)
|
125
|
-
|
126
|
-
# Cache this. We just made a request to a storage node and got
|
127
|
-
# up-to-date information for the container.
|
128
|
-
resp.headers['X-Backend-Recheck-Container-Existence'] = str(
|
129
|
-
self.app.recheck_container_existence)
|
130
|
-
set_info_cache(self.app, req.environ, self.account_name,
|
131
|
-
self.container_name, resp)
|
368
|
+
return None
|
369
|
+
|
370
|
+
def _get_or_head_post_check(self, req, resp):
|
371
|
+
if not config_true_value(
|
372
|
+
resp.headers.get('X-Backend-Cached-Results')):
|
373
|
+
# Cache container metadata. We just made a request to a storage
|
374
|
+
# node and got up-to-date information for the container.
|
375
|
+
resp.headers['X-Backend-Recheck-Container-Existence'] = str(
|
376
|
+
self.app.recheck_container_existence)
|
377
|
+
set_info_cache(req.environ, self.account_name,
|
378
|
+
self.container_name, resp)
|
379
|
+
|
132
380
|
if 'swift.authorize' in req.environ:
|
133
|
-
req.acl = resp.headers.get('x-container-read')
|
381
|
+
req.acl = wsgi_to_str(resp.headers.get('x-container-read'))
|
134
382
|
aresp = req.environ['swift.authorize'](req)
|
135
383
|
if aresp:
|
136
384
|
# Don't cache this. It doesn't reflect the state of the
|
@@ -147,31 +395,92 @@ class ContainerController(Controller):
|
|
147
395
|
'False'))
|
148
396
|
return resp
|
149
397
|
|
150
|
-
|
151
|
-
|
152
|
-
|
398
|
+
@public
|
399
|
+
@delay_denial
|
400
|
+
@cors_validation
|
401
|
+
def GET(self, req):
|
402
|
+
"""Handler for HTTP GET requests."""
|
403
|
+
# early checks for request validity
|
404
|
+
validate_container_params(req)
|
405
|
+
aresp = self._get_or_head_pre_check(req)
|
406
|
+
if aresp:
|
407
|
+
return aresp
|
408
|
+
|
409
|
+
# Always request json format from the backend. listing_formats
|
410
|
+
# middleware will take care of what the client gets back.
|
411
|
+
# The read-modify-write of params here is because the
|
412
|
+
# Request.params getter dynamically generates a dict of params from
|
413
|
+
# the query string; the setter must be called for new params to
|
414
|
+
# update the query string.
|
415
|
+
params = req.params
|
416
|
+
params['format'] = 'json'
|
417
|
+
req.params = params
|
418
|
+
|
419
|
+
# x-backend-record-type may be sent via internal client e.g. from
|
420
|
+
# the sharder or in probe tests
|
421
|
+
record_type = req.headers.get('X-Backend-Record-Type', '').lower()
|
422
|
+
if record_type in ('object', 'shard'):
|
423
|
+
# Go direct to the backend for HEADs, and GETs that *explicitly*
|
424
|
+
# specify a record type. We won't be reading/writing namespaces in
|
425
|
+
# cache nor building listings from shards. This path is used by
|
426
|
+
# the sharder, manage_shard_ranges and other tools that fetch shard
|
427
|
+
# ranges, and by the proxy itself when explicitly requesting
|
428
|
+
# objects while recursively building a listing from shards.
|
429
|
+
# Note: shard record type could be namespace or full format
|
430
|
+
resp = self._GETorHEAD_from_backend(req)
|
431
|
+
else:
|
432
|
+
# Requests that do not explicitly specify a record type, or specify
|
433
|
+
# 'auto', default to returning an object listing. The listing may
|
434
|
+
# be built from shards and may involve reading/writing namespaces
|
435
|
+
# in cache. This path is used for client requests and by the proxy
|
436
|
+
# itself while recursively building a listing from shards.
|
437
|
+
resp = self._GET_auto(req)
|
438
|
+
resp.headers.pop('X-Backend-Record-Type', None)
|
439
|
+
resp.headers.pop('X-Backend-Record-Shard-Format', None)
|
440
|
+
|
441
|
+
return self._get_or_head_post_check(req, resp)
|
442
|
+
|
443
|
+
def _get_from_shards(self, req, resp, namespaces):
|
444
|
+
"""
|
445
|
+
Construct an object listing using shards described by the list of
|
446
|
+
namespaces.
|
447
|
+
|
448
|
+
:param req: an instance of :class:`~swift.common.swob.Request`.
|
449
|
+
:param resp: an instance of :class:`~swift.common.swob.Response`.
|
450
|
+
:param namespaces: a list of :class:`~swift.common.utils.Namespace`.
|
451
|
+
:return: an instance of :class:`~swift.common.swob.Response`. If an
|
452
|
+
error is encountered while building the listing an instance of
|
453
|
+
``HTTPServiceUnavailable`` may be returned. Otherwise, the given
|
454
|
+
``resp`` is returned with a body that is an object listing.
|
455
|
+
"""
|
456
|
+
# The history of containers that have returned namespaces is
|
153
457
|
# maintained in the request environ so that loops can be avoided by
|
154
458
|
# forcing an object listing if the same container is visited again.
|
155
459
|
# This can happen in at least two scenarios:
|
156
|
-
# 1. a container has filled a gap in its
|
157
|
-
#
|
158
|
-
# 2. a root container returns a (stale)
|
460
|
+
# 1. a container has filled a gap in its namespaces with a
|
461
|
+
# namespace pointing to itself
|
462
|
+
# 2. a root container returns a (stale) namespace pointing to a
|
159
463
|
# shard that has shrunk into the root, in which case the shrunken
|
160
|
-
# shard may return the root's
|
464
|
+
# shard may return the root's namespace.
|
161
465
|
shard_listing_history = req.environ.setdefault(
|
162
466
|
'swift.shard_listing_history', [])
|
467
|
+
policy_key = 'X-Backend-Storage-Policy-Index'
|
468
|
+
if not (shard_listing_history or policy_key in req.headers):
|
469
|
+
# We're handling the original request to the root container: set
|
470
|
+
# the root policy index in the request, unless it is already set,
|
471
|
+
# so that shards will return listings for that policy index.
|
472
|
+
# Note: we only get here if the root responded with namespaces,
|
473
|
+
# or if the namespaces were cached and the cached root container
|
474
|
+
# info has sharding_state==sharded; in both cases we can assume
|
475
|
+
# that the response is "modern enough" to include
|
476
|
+
# 'X-Backend-Storage-Policy-Index'.
|
477
|
+
req.headers[policy_key] = resp.headers[policy_key]
|
163
478
|
shard_listing_history.append((self.account_name, self.container_name))
|
164
|
-
|
165
|
-
|
166
|
-
self.app.logger.debug('GET listing from %s shards for: %s',
|
167
|
-
len(shard_ranges), req.path_qs)
|
168
|
-
if not shard_ranges:
|
169
|
-
# can't find ranges or there was a problem getting the ranges. So
|
170
|
-
# return what we have.
|
171
|
-
return resp
|
479
|
+
self.logger.debug('GET listing from %s shards for: %s',
|
480
|
+
len(namespaces), req.path_qs)
|
172
481
|
|
173
482
|
objects = []
|
174
|
-
req_limit =
|
483
|
+
req_limit = constrain_req_limit(req, CONTAINER_LISTING_LIMIT)
|
175
484
|
params = req.params.copy()
|
176
485
|
params.pop('states', None)
|
177
486
|
req.headers.pop('X-Backend-Record-Type', None)
|
@@ -181,58 +490,98 @@ class ContainerController(Controller):
|
|
181
490
|
prefix = wsgi_to_str(params.get('prefix'))
|
182
491
|
|
183
492
|
limit = req_limit
|
184
|
-
|
493
|
+
all_resp_status = []
|
494
|
+
for i, namespace in enumerate(namespaces):
|
185
495
|
params['limit'] = limit
|
186
496
|
# Always set marker to ensure that object names less than or equal
|
187
497
|
# to those already in the listing are not fetched; if the listing
|
188
498
|
# is empty then the original request marker, if any, is used. This
|
189
|
-
# allows misplaced objects below the expected
|
499
|
+
# allows misplaced objects below the expected namespace to be
|
190
500
|
# included in the listing.
|
501
|
+
last_name = ''
|
502
|
+
last_name_was_subdir = False
|
191
503
|
if objects:
|
192
|
-
|
193
|
-
|
194
|
-
|
504
|
+
last_name_was_subdir = 'subdir' in objects[-1]
|
505
|
+
if last_name_was_subdir:
|
506
|
+
last_name = objects[-1]['subdir']
|
507
|
+
else:
|
508
|
+
last_name = objects[-1]['name']
|
509
|
+
|
510
|
+
params['marker'] = str_to_wsgi(last_name)
|
195
511
|
elif marker:
|
196
512
|
params['marker'] = str_to_wsgi(marker)
|
197
513
|
else:
|
198
514
|
params['marker'] = ''
|
199
515
|
# Always set end_marker to ensure that misplaced objects beyond the
|
200
|
-
# expected
|
516
|
+
# expected namespace are not fetched. This prevents a misplaced
|
201
517
|
# object obscuring correctly placed objects in the next shard
|
202
518
|
# range.
|
203
|
-
if end_marker and end_marker in
|
519
|
+
if end_marker and end_marker in namespace:
|
204
520
|
params['end_marker'] = str_to_wsgi(end_marker)
|
205
521
|
elif reverse:
|
206
|
-
params['end_marker'] = str_to_wsgi(
|
522
|
+
params['end_marker'] = str_to_wsgi(namespace.lower_str)
|
207
523
|
else:
|
208
|
-
params['end_marker'] = str_to_wsgi(
|
524
|
+
params['end_marker'] = str_to_wsgi(namespace.end_marker)
|
209
525
|
|
210
|
-
|
526
|
+
headers = {}
|
527
|
+
if ((namespace.account, namespace.container) in
|
211
528
|
shard_listing_history):
|
212
529
|
# directed back to same container - force GET of objects
|
213
|
-
headers
|
530
|
+
headers['X-Backend-Record-Type'] = 'object'
|
214
531
|
else:
|
215
|
-
headers =
|
532
|
+
headers['X-Backend-Record-Type'] = 'auto'
|
533
|
+
if config_true_value(req.headers.get('x-newest', False)):
|
534
|
+
headers['X-Newest'] = 'true'
|
216
535
|
|
217
536
|
if prefix:
|
218
|
-
if prefix >
|
537
|
+
if prefix > namespace:
|
219
538
|
continue
|
220
539
|
try:
|
221
540
|
just_past = prefix[:-1] + chr(ord(prefix[-1]) + 1)
|
222
541
|
except ValueError:
|
223
542
|
pass
|
224
543
|
else:
|
225
|
-
if just_past <
|
544
|
+
if just_past < namespace:
|
226
545
|
continue
|
227
546
|
|
228
|
-
|
229
|
-
|
547
|
+
if last_name_was_subdir and str(
|
548
|
+
namespace.lower if reverse else namespace.upper
|
549
|
+
).startswith(last_name):
|
550
|
+
continue
|
551
|
+
|
552
|
+
self.logger.debug(
|
553
|
+
'Getting listing part %d from shard %s %s with %s',
|
554
|
+
i, namespace, namespace.name, headers)
|
230
555
|
objs, shard_resp = self._get_container_listing(
|
231
|
-
req,
|
556
|
+
req, namespace.account, namespace.container,
|
232
557
|
headers=headers, params=params)
|
558
|
+
all_resp_status.append(shard_resp.status_int)
|
559
|
+
|
560
|
+
sharding_state = shard_resp.headers.get('x-backend-sharding-state',
|
561
|
+
'unknown')
|
562
|
+
|
563
|
+
if objs is None:
|
564
|
+
# give up if any non-success response from shard containers
|
565
|
+
self.logger.error(
|
566
|
+
'Aborting listing from shards due to bad response: %r'
|
567
|
+
% all_resp_status)
|
568
|
+
return HTTPServiceUnavailable(request=req)
|
569
|
+
shard_policy = shard_resp.headers.get(
|
570
|
+
'X-Backend-Record-Storage-Policy-Index',
|
571
|
+
shard_resp.headers[policy_key]
|
572
|
+
)
|
573
|
+
if shard_policy != req.headers[policy_key]:
|
574
|
+
self.logger.error(
|
575
|
+
'Aborting listing from shards due to bad shard policy '
|
576
|
+
'index: %s (expected %s)',
|
577
|
+
shard_policy, req.headers[policy_key])
|
578
|
+
return HTTPServiceUnavailable(request=req)
|
579
|
+
self.logger.debug(
|
580
|
+
'Found %d objects in shard (state=%s), total = %d',
|
581
|
+
len(objs), sharding_state, len(objs) + len(objects))
|
233
582
|
|
234
583
|
if not objs:
|
235
|
-
# tolerate
|
584
|
+
# tolerate empty shard containers
|
236
585
|
continue
|
237
586
|
|
238
587
|
objects.extend(objs)
|
@@ -242,8 +591,6 @@ class ContainerController(Controller):
|
|
242
591
|
break
|
243
592
|
last_name = objects[-1].get('name',
|
244
593
|
objects[-1].get('subdir', u''))
|
245
|
-
if six.PY2:
|
246
|
-
last_name = last_name.encode('utf8')
|
247
594
|
if end_marker and reverse and end_marker >= last_name:
|
248
595
|
break
|
249
596
|
if end_marker and not reverse and end_marker <= last_name:
|
@@ -253,7 +600,7 @@ class ContainerController(Controller):
|
|
253
600
|
constrained = any(req.params.get(constraint) for constraint in (
|
254
601
|
'marker', 'end_marker', 'path', 'prefix', 'delimiter'))
|
255
602
|
if not constrained and len(objects) < req_limit:
|
256
|
-
self.
|
603
|
+
self.logger.debug('Setting object count to %s' % len(objects))
|
257
604
|
# prefer the actual listing stats over the potentially outdated
|
258
605
|
# root stats. This condition is only likely when a sharded
|
259
606
|
# container is shrinking or in tests; typically a sharded container
|
@@ -265,19 +612,16 @@ class ContainerController(Controller):
|
|
265
612
|
[o['bytes'] for o in objects])
|
266
613
|
return resp
|
267
614
|
|
268
|
-
@public
|
269
|
-
@delay_denial
|
270
|
-
@cors_validation
|
271
|
-
def GET(self, req):
|
272
|
-
"""Handler for HTTP GET requests."""
|
273
|
-
return self.GETorHEAD(req)
|
274
|
-
|
275
615
|
@public
|
276
616
|
@delay_denial
|
277
617
|
@cors_validation
|
278
618
|
def HEAD(self, req):
|
279
619
|
"""Handler for HTTP HEAD requests."""
|
280
|
-
|
620
|
+
aresp = self._get_or_head_pre_check(req)
|
621
|
+
if aresp:
|
622
|
+
return aresp
|
623
|
+
resp = self._GETorHEAD_from_backend(req)
|
624
|
+
return self._get_or_head_post_check(req, resp)
|
281
625
|
|
282
626
|
@public
|
283
627
|
@cors_validation
|
@@ -328,8 +672,7 @@ class ContainerController(Controller):
|
|
328
672
|
resp = self.make_requests(
|
329
673
|
req, self.app.container_ring,
|
330
674
|
container_partition, 'PUT', req.swift_entity_path, headers)
|
331
|
-
|
332
|
-
self.account_name, self.container_name)
|
675
|
+
self._clear_container_info_cache(req)
|
333
676
|
return resp
|
334
677
|
|
335
678
|
@public
|
@@ -354,8 +697,7 @@ class ContainerController(Controller):
|
|
354
697
|
container_partition, containers = self.app.container_ring.get_nodes(
|
355
698
|
self.account_name, self.container_name)
|
356
699
|
headers = self.generate_request_headers(req, transfer=True)
|
357
|
-
|
358
|
-
self.account_name, self.container_name)
|
700
|
+
self._clear_container_info_cache(req)
|
359
701
|
resp = self.make_requests(
|
360
702
|
req, self.app.container_ring, container_partition, 'POST',
|
361
703
|
req.swift_entity_path, [headers] * len(containers))
|
@@ -373,8 +715,7 @@ class ContainerController(Controller):
|
|
373
715
|
self.account_name, self.container_name)
|
374
716
|
headers = self._backend_requests(req, len(containers),
|
375
717
|
account_partition, accounts)
|
376
|
-
|
377
|
-
self.account_name, self.container_name)
|
718
|
+
self._clear_container_info_cache(req)
|
378
719
|
resp = self.make_requests(
|
379
720
|
req, self.app.container_ring, container_partition, 'DELETE',
|
380
721
|
req.swift_entity_path, headers)
|
@@ -391,7 +732,7 @@ class ContainerController(Controller):
|
|
391
732
|
similar to a merge_items REPLICATE request.
|
392
733
|
|
393
734
|
Not client facing; internal clients or middlewares must include
|
394
|
-
``X-Backend-Allow-
|
735
|
+
``X-Backend-Allow-Private-Methods: true`` header to access.
|
395
736
|
"""
|
396
737
|
container_partition, containers = self.app.container_ring.get_nodes(
|
397
738
|
self.account_name, self.container_name)
|