swift 2.23.2__py3-none-any.whl → 2.35.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.2.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
- swift-2.23.2.data/scripts/swift-config → swift/cli/config.py +2 -2
- swift/cli/container_deleter.py +5 -11
- swift-2.23.2.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
- swift/cli/dispersion_report.py +10 -9
- swift-2.23.2.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
- swift/cli/form_signature.py +3 -7
- swift-2.23.2.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
- swift/cli/info.py +183 -29
- swift/cli/manage_shard_ranges.py +708 -37
- swift-2.23.2.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
- swift-2.23.2.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
- swift/cli/recon.py +196 -67
- swift-2.23.2.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
- swift-2.23.2.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 +198 -127
- 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 +396 -147
- 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 -81
- 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 +52 -19
- swift/common/middleware/tempauth.py +76 -58
- swift/common/middleware/tempurl.py +192 -174
- 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.2.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} +2191 -2762
- 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 +555 -536
- swift/container/auditor.py +14 -100
- swift/container/backend.py +552 -227
- swift/container/reconciler.py +126 -37
- swift/container/replicator.py +96 -22
- swift/container/server.py +397 -176
- swift/container/sharder.py +1580 -639
- 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 +213 -122
- swift/obj/ssync_receiver.py +145 -85
- swift/obj/ssync_sender.py +113 -54
- swift/obj/updater.py +653 -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 +452 -86
- swift/proxy/controllers/info.py +3 -2
- swift/proxy/controllers/obj.py +1009 -490
- swift/proxy/server.py +185 -112
- swift-2.35.0.dist-info/AUTHORS +501 -0
- swift-2.35.0.dist-info/LICENSE +202 -0
- {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/METADATA +52 -61
- swift-2.35.0.dist-info/RECORD +201 -0
- {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
- {swift-2.23.2.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.2.data/scripts/swift-account-auditor +0 -23
- swift-2.23.2.data/scripts/swift-account-info +0 -51
- swift-2.23.2.data/scripts/swift-account-reaper +0 -23
- swift-2.23.2.data/scripts/swift-account-replicator +0 -34
- swift-2.23.2.data/scripts/swift-account-server +0 -23
- swift-2.23.2.data/scripts/swift-container-auditor +0 -23
- swift-2.23.2.data/scripts/swift-container-info +0 -51
- swift-2.23.2.data/scripts/swift-container-reconciler +0 -21
- swift-2.23.2.data/scripts/swift-container-replicator +0 -34
- swift-2.23.2.data/scripts/swift-container-sharder +0 -33
- swift-2.23.2.data/scripts/swift-container-sync +0 -23
- swift-2.23.2.data/scripts/swift-container-updater +0 -23
- swift-2.23.2.data/scripts/swift-dispersion-report +0 -24
- swift-2.23.2.data/scripts/swift-form-signature +0 -20
- swift-2.23.2.data/scripts/swift-init +0 -119
- swift-2.23.2.data/scripts/swift-object-auditor +0 -29
- swift-2.23.2.data/scripts/swift-object-expirer +0 -33
- swift-2.23.2.data/scripts/swift-object-info +0 -60
- swift-2.23.2.data/scripts/swift-object-reconstructor +0 -33
- swift-2.23.2.data/scripts/swift-object-relinker +0 -41
- swift-2.23.2.data/scripts/swift-object-replicator +0 -37
- swift-2.23.2.data/scripts/swift-object-server +0 -27
- swift-2.23.2.data/scripts/swift-object-updater +0 -23
- swift-2.23.2.data/scripts/swift-proxy-server +0 -23
- swift-2.23.2.data/scripts/swift-recon +0 -24
- swift-2.23.2.data/scripts/swift-ring-builder +0 -24
- swift-2.23.2.data/scripts/swift-ring-builder-analyzer +0 -22
- swift-2.23.2.data/scripts/swift-ring-composer +0 -22
- swift-2.23.2.dist-info/DESCRIPTION.rst +0 -166
- swift-2.23.2.dist-info/RECORD +0 -220
- swift-2.23.2.dist-info/metadata.json +0 -1
- swift-2.23.2.dist-info/pbr.json +0 -1
- {swift-2.23.2.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,67 +395,193 @@ class ContainerController(Controller):
|
|
147
395
|
'False'))
|
148
396
|
return resp
|
149
397
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
return
|
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
|
457
|
+
# maintained in the request environ so that loops can be avoided by
|
458
|
+
# forcing an object listing if the same container is visited again.
|
459
|
+
# This can happen in at least two scenarios:
|
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
|
463
|
+
# shard that has shrunk into the root, in which case the shrunken
|
464
|
+
# shard may return the root's namespace.
|
465
|
+
shard_listing_history = req.environ.setdefault(
|
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]
|
478
|
+
shard_listing_history.append((self.account_name, self.container_name))
|
479
|
+
self.logger.debug('GET listing from %s shards for: %s',
|
480
|
+
len(namespaces), req.path_qs)
|
160
481
|
|
161
482
|
objects = []
|
162
|
-
req_limit =
|
483
|
+
req_limit = constrain_req_limit(req, CONTAINER_LISTING_LIMIT)
|
163
484
|
params = req.params.copy()
|
164
485
|
params.pop('states', None)
|
165
486
|
req.headers.pop('X-Backend-Record-Type', None)
|
166
487
|
reverse = config_true_value(params.get('reverse'))
|
167
488
|
marker = wsgi_to_str(params.get('marker'))
|
168
489
|
end_marker = wsgi_to_str(params.get('end_marker'))
|
490
|
+
prefix = wsgi_to_str(params.get('prefix'))
|
169
491
|
|
170
492
|
limit = req_limit
|
171
|
-
|
493
|
+
all_resp_status = []
|
494
|
+
for i, namespace in enumerate(namespaces):
|
172
495
|
params['limit'] = limit
|
173
496
|
# Always set marker to ensure that object names less than or equal
|
174
497
|
# to those already in the listing are not fetched; if the listing
|
175
498
|
# is empty then the original request marker, if any, is used. This
|
176
|
-
# allows misplaced objects below the expected
|
499
|
+
# allows misplaced objects below the expected namespace to be
|
177
500
|
# included in the listing.
|
501
|
+
last_name = ''
|
502
|
+
last_name_was_subdir = False
|
178
503
|
if objects:
|
179
|
-
|
180
|
-
|
181
|
-
|
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)
|
182
511
|
elif marker:
|
183
512
|
params['marker'] = str_to_wsgi(marker)
|
184
513
|
else:
|
185
514
|
params['marker'] = ''
|
186
515
|
# Always set end_marker to ensure that misplaced objects beyond the
|
187
|
-
# expected
|
516
|
+
# expected namespace are not fetched. This prevents a misplaced
|
188
517
|
# object obscuring correctly placed objects in the next shard
|
189
518
|
# range.
|
190
|
-
if end_marker and end_marker in
|
519
|
+
if end_marker and end_marker in namespace:
|
191
520
|
params['end_marker'] = str_to_wsgi(end_marker)
|
192
521
|
elif reverse:
|
193
|
-
params['end_marker'] = str_to_wsgi(
|
522
|
+
params['end_marker'] = str_to_wsgi(namespace.lower_str)
|
194
523
|
else:
|
195
|
-
params['end_marker'] = str_to_wsgi(
|
524
|
+
params['end_marker'] = str_to_wsgi(namespace.end_marker)
|
196
525
|
|
197
|
-
|
198
|
-
|
526
|
+
headers = {}
|
527
|
+
if ((namespace.account, namespace.container) in
|
528
|
+
shard_listing_history):
|
199
529
|
# directed back to same container - force GET of objects
|
200
|
-
headers
|
530
|
+
headers['X-Backend-Record-Type'] = 'object'
|
201
531
|
else:
|
202
|
-
headers =
|
203
|
-
|
204
|
-
|
532
|
+
headers['X-Backend-Record-Type'] = 'auto'
|
533
|
+
if config_true_value(req.headers.get('x-newest', False)):
|
534
|
+
headers['X-Newest'] = 'true'
|
535
|
+
|
536
|
+
if prefix:
|
537
|
+
if prefix > namespace:
|
538
|
+
continue
|
539
|
+
try:
|
540
|
+
just_past = prefix[:-1] + chr(ord(prefix[-1]) + 1)
|
541
|
+
except ValueError:
|
542
|
+
pass
|
543
|
+
else:
|
544
|
+
if just_past < namespace:
|
545
|
+
continue
|
546
|
+
|
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)
|
205
555
|
objs, shard_resp = self._get_container_listing(
|
206
|
-
req,
|
556
|
+
req, namespace.account, namespace.container,
|
207
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))
|
208
582
|
|
209
583
|
if not objs:
|
210
|
-
# tolerate
|
584
|
+
# tolerate empty shard containers
|
211
585
|
continue
|
212
586
|
|
213
587
|
objects.extend(objs)
|
@@ -217,8 +591,6 @@ class ContainerController(Controller):
|
|
217
591
|
break
|
218
592
|
last_name = objects[-1].get('name',
|
219
593
|
objects[-1].get('subdir', u''))
|
220
|
-
if six.PY2:
|
221
|
-
last_name = last_name.encode('utf8')
|
222
594
|
if end_marker and reverse and end_marker >= last_name:
|
223
595
|
break
|
224
596
|
if end_marker and not reverse and end_marker <= last_name:
|
@@ -228,7 +600,7 @@ class ContainerController(Controller):
|
|
228
600
|
constrained = any(req.params.get(constraint) for constraint in (
|
229
601
|
'marker', 'end_marker', 'path', 'prefix', 'delimiter'))
|
230
602
|
if not constrained and len(objects) < req_limit:
|
231
|
-
self.
|
603
|
+
self.logger.debug('Setting object count to %s' % len(objects))
|
232
604
|
# prefer the actual listing stats over the potentially outdated
|
233
605
|
# root stats. This condition is only likely when a sharded
|
234
606
|
# container is shrinking or in tests; typically a sharded container
|
@@ -240,19 +612,16 @@ class ContainerController(Controller):
|
|
240
612
|
[o['bytes'] for o in objects])
|
241
613
|
return resp
|
242
614
|
|
243
|
-
@public
|
244
|
-
@delay_denial
|
245
|
-
@cors_validation
|
246
|
-
def GET(self, req):
|
247
|
-
"""Handler for HTTP GET requests."""
|
248
|
-
return self.GETorHEAD(req)
|
249
|
-
|
250
615
|
@public
|
251
616
|
@delay_denial
|
252
617
|
@cors_validation
|
253
618
|
def HEAD(self, req):
|
254
619
|
"""Handler for HTTP HEAD requests."""
|
255
|
-
|
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)
|
256
625
|
|
257
626
|
@public
|
258
627
|
@cors_validation
|
@@ -303,8 +672,7 @@ class ContainerController(Controller):
|
|
303
672
|
resp = self.make_requests(
|
304
673
|
req, self.app.container_ring,
|
305
674
|
container_partition, 'PUT', req.swift_entity_path, headers)
|
306
|
-
|
307
|
-
self.account_name, self.container_name)
|
675
|
+
self._clear_container_info_cache(req)
|
308
676
|
return resp
|
309
677
|
|
310
678
|
@public
|
@@ -329,8 +697,7 @@ class ContainerController(Controller):
|
|
329
697
|
container_partition, containers = self.app.container_ring.get_nodes(
|
330
698
|
self.account_name, self.container_name)
|
331
699
|
headers = self.generate_request_headers(req, transfer=True)
|
332
|
-
|
333
|
-
self.account_name, self.container_name)
|
700
|
+
self._clear_container_info_cache(req)
|
334
701
|
resp = self.make_requests(
|
335
702
|
req, self.app.container_ring, container_partition, 'POST',
|
336
703
|
req.swift_entity_path, [headers] * len(containers))
|
@@ -348,8 +715,7 @@ class ContainerController(Controller):
|
|
348
715
|
self.account_name, self.container_name)
|
349
716
|
headers = self._backend_requests(req, len(containers),
|
350
717
|
account_partition, accounts)
|
351
|
-
|
352
|
-
self.account_name, self.container_name)
|
718
|
+
self._clear_container_info_cache(req)
|
353
719
|
resp = self.make_requests(
|
354
720
|
req, self.app.container_ring, container_partition, 'DELETE',
|
355
721
|
req.swift_entity_path, headers)
|
@@ -366,7 +732,7 @@ class ContainerController(Controller):
|
|
366
732
|
similar to a merge_items REPLICATE request.
|
367
733
|
|
368
734
|
Not client facing; internal clients or middlewares must include
|
369
|
-
``X-Backend-Allow-
|
735
|
+
``X-Backend-Allow-Private-Methods: true`` header to access.
|
370
736
|
"""
|
371
737
|
container_partition, containers = self.app.container_ring.get_nodes(
|
372
738
|
self.account_name, self.container_name)
|
swift/proxy/controllers/info.py
CHANGED
@@ -16,8 +16,9 @@
|
|
16
16
|
import json
|
17
17
|
from time import time
|
18
18
|
|
19
|
-
from swift.common.utils import public,
|
20
|
-
|
19
|
+
from swift.common.utils import public, streq_const_time
|
20
|
+
from swift.common.digest import get_hmac
|
21
|
+
from swift.common.registry import get_swift_info
|
21
22
|
from swift.proxy.controllers.base import Controller, delay_denial
|
22
23
|
from swift.common.swob import HTTPOk, HTTPForbidden, HTTPUnauthorized
|
23
24
|
|