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.
Files changed (208) hide show
  1. swift/__init__.py +29 -50
  2. swift/account/auditor.py +21 -118
  3. swift/account/backend.py +33 -28
  4. swift/account/reaper.py +37 -28
  5. swift/account/replicator.py +22 -0
  6. swift/account/server.py +60 -26
  7. swift/account/utils.py +28 -11
  8. swift-2.23.2.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.2.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.2.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.2.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.2.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +183 -29
  17. swift/cli/manage_shard_ranges.py +708 -37
  18. swift-2.23.2.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.2.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.2.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.2.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  23. swift/cli/relinker.py +807 -126
  24. swift/cli/reload.py +135 -0
  25. swift/cli/ringbuilder.py +217 -20
  26. swift/cli/ringcomposer.py +0 -1
  27. swift/cli/shard-info.py +4 -3
  28. swift/common/base_storage_server.py +9 -20
  29. swift/common/bufferedhttp.py +48 -74
  30. swift/common/constraints.py +20 -15
  31. swift/common/container_sync_realms.py +9 -11
  32. swift/common/daemon.py +25 -8
  33. swift/common/db.py +198 -127
  34. swift/common/db_auditor.py +168 -0
  35. swift/common/db_replicator.py +95 -55
  36. swift/common/digest.py +141 -0
  37. swift/common/direct_client.py +144 -33
  38. swift/common/error_limiter.py +93 -0
  39. swift/common/exceptions.py +25 -1
  40. swift/common/header_key_dict.py +2 -9
  41. swift/common/http_protocol.py +373 -0
  42. swift/common/internal_client.py +129 -59
  43. swift/common/linkat.py +3 -4
  44. swift/common/manager.py +284 -67
  45. swift/common/memcached.py +396 -147
  46. swift/common/middleware/__init__.py +4 -0
  47. swift/common/middleware/account_quotas.py +211 -46
  48. swift/common/middleware/acl.py +3 -8
  49. swift/common/middleware/backend_ratelimit.py +230 -0
  50. swift/common/middleware/bulk.py +22 -34
  51. swift/common/middleware/catch_errors.py +1 -3
  52. swift/common/middleware/cname_lookup.py +6 -11
  53. swift/common/middleware/container_quotas.py +1 -1
  54. swift/common/middleware/container_sync.py +39 -17
  55. swift/common/middleware/copy.py +12 -0
  56. swift/common/middleware/crossdomain.py +22 -9
  57. swift/common/middleware/crypto/__init__.py +2 -1
  58. swift/common/middleware/crypto/crypto_utils.py +11 -15
  59. swift/common/middleware/crypto/decrypter.py +28 -11
  60. swift/common/middleware/crypto/encrypter.py +12 -17
  61. swift/common/middleware/crypto/keymaster.py +8 -15
  62. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  63. swift/common/middleware/dlo.py +15 -11
  64. swift/common/middleware/domain_remap.py +5 -4
  65. swift/common/middleware/etag_quoter.py +128 -0
  66. swift/common/middleware/formpost.py +73 -70
  67. swift/common/middleware/gatekeeper.py +8 -1
  68. swift/common/middleware/keystoneauth.py +33 -3
  69. swift/common/middleware/list_endpoints.py +4 -4
  70. swift/common/middleware/listing_formats.py +85 -49
  71. swift/common/middleware/memcache.py +4 -81
  72. swift/common/middleware/name_check.py +3 -2
  73. swift/common/middleware/proxy_logging.py +160 -92
  74. swift/common/middleware/ratelimit.py +17 -10
  75. swift/common/middleware/read_only.py +6 -4
  76. swift/common/middleware/recon.py +59 -22
  77. swift/common/middleware/s3api/acl_handlers.py +25 -3
  78. swift/common/middleware/s3api/acl_utils.py +6 -1
  79. swift/common/middleware/s3api/controllers/__init__.py +6 -0
  80. swift/common/middleware/s3api/controllers/acl.py +3 -2
  81. swift/common/middleware/s3api/controllers/bucket.py +242 -137
  82. swift/common/middleware/s3api/controllers/logging.py +2 -2
  83. swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
  84. swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
  85. swift/common/middleware/s3api/controllers/obj.py +112 -8
  86. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  87. swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
  88. swift/common/middleware/s3api/controllers/tagging.py +57 -0
  89. swift/common/middleware/s3api/controllers/versioning.py +36 -7
  90. swift/common/middleware/s3api/etree.py +22 -9
  91. swift/common/middleware/s3api/exception.py +0 -4
  92. swift/common/middleware/s3api/s3api.py +113 -41
  93. swift/common/middleware/s3api/s3request.py +384 -218
  94. swift/common/middleware/s3api/s3response.py +126 -23
  95. swift/common/middleware/s3api/s3token.py +16 -17
  96. swift/common/middleware/s3api/schema/delete.rng +1 -1
  97. swift/common/middleware/s3api/subresource.py +7 -10
  98. swift/common/middleware/s3api/utils.py +27 -10
  99. swift/common/middleware/slo.py +665 -358
  100. swift/common/middleware/staticweb.py +64 -37
  101. swift/common/middleware/symlink.py +52 -19
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +192 -174
  104. swift/common/middleware/versioned_writes/__init__.py +51 -0
  105. swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
  106. swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
  107. swift/common/middleware/x_profile/exceptions.py +1 -4
  108. swift/common/middleware/x_profile/html_viewer.py +18 -19
  109. swift/common/middleware/x_profile/profile_model.py +1 -2
  110. swift/common/middleware/xprofile.py +10 -10
  111. swift-2.23.2.data/scripts/swift-container-server → swift/common/recon.py +13 -8
  112. swift/common/registry.py +147 -0
  113. swift/common/request_helpers.py +324 -57
  114. swift/common/ring/builder.py +67 -25
  115. swift/common/ring/composite_builder.py +1 -1
  116. swift/common/ring/ring.py +177 -51
  117. swift/common/ring/utils.py +1 -1
  118. swift/common/splice.py +10 -6
  119. swift/common/statsd_client.py +205 -0
  120. swift/common/storage_policy.py +49 -44
  121. swift/common/swob.py +86 -102
  122. swift/common/{utils.py → utils/__init__.py} +2191 -2762
  123. swift/common/utils/base.py +131 -0
  124. swift/common/utils/config.py +433 -0
  125. swift/common/utils/ipaddrs.py +256 -0
  126. swift/common/utils/libc.py +345 -0
  127. swift/common/utils/logs.py +859 -0
  128. swift/common/utils/timestamp.py +412 -0
  129. swift/common/wsgi.py +555 -536
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +552 -227
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +397 -176
  135. swift/container/sharder.py +1580 -639
  136. swift/container/sync.py +94 -88
  137. swift/container/updater.py +53 -32
  138. swift/obj/auditor.py +153 -35
  139. swift/obj/diskfile.py +466 -217
  140. swift/obj/expirer.py +406 -124
  141. swift/obj/mem_diskfile.py +7 -4
  142. swift/obj/mem_server.py +1 -0
  143. swift/obj/reconstructor.py +523 -262
  144. swift/obj/replicator.py +249 -188
  145. swift/obj/server.py +213 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +653 -139
  149. swift/obj/watchers/__init__.py +0 -0
  150. swift/obj/watchers/dark_data.py +213 -0
  151. swift/proxy/controllers/account.py +11 -11
  152. swift/proxy/controllers/base.py +848 -604
  153. swift/proxy/controllers/container.py +452 -86
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1009 -490
  156. swift/proxy/server.py +185 -112
  157. swift-2.35.0.dist-info/AUTHORS +501 -0
  158. swift-2.35.0.dist-info/LICENSE +202 -0
  159. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/METADATA +52 -61
  160. swift-2.35.0.dist-info/RECORD +201 -0
  161. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  162. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  163. swift-2.35.0.dist-info/pbr.json +1 -0
  164. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  165. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  166. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  167. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  168. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  169. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  170. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  171. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  172. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  173. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  174. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  175. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  176. swift-2.23.2.data/scripts/swift-account-auditor +0 -23
  177. swift-2.23.2.data/scripts/swift-account-info +0 -51
  178. swift-2.23.2.data/scripts/swift-account-reaper +0 -23
  179. swift-2.23.2.data/scripts/swift-account-replicator +0 -34
  180. swift-2.23.2.data/scripts/swift-account-server +0 -23
  181. swift-2.23.2.data/scripts/swift-container-auditor +0 -23
  182. swift-2.23.2.data/scripts/swift-container-info +0 -51
  183. swift-2.23.2.data/scripts/swift-container-reconciler +0 -21
  184. swift-2.23.2.data/scripts/swift-container-replicator +0 -34
  185. swift-2.23.2.data/scripts/swift-container-sharder +0 -33
  186. swift-2.23.2.data/scripts/swift-container-sync +0 -23
  187. swift-2.23.2.data/scripts/swift-container-updater +0 -23
  188. swift-2.23.2.data/scripts/swift-dispersion-report +0 -24
  189. swift-2.23.2.data/scripts/swift-form-signature +0 -20
  190. swift-2.23.2.data/scripts/swift-init +0 -119
  191. swift-2.23.2.data/scripts/swift-object-auditor +0 -29
  192. swift-2.23.2.data/scripts/swift-object-expirer +0 -33
  193. swift-2.23.2.data/scripts/swift-object-info +0 -60
  194. swift-2.23.2.data/scripts/swift-object-reconstructor +0 -33
  195. swift-2.23.2.data/scripts/swift-object-relinker +0 -41
  196. swift-2.23.2.data/scripts/swift-object-replicator +0 -37
  197. swift-2.23.2.data/scripts/swift-object-server +0 -27
  198. swift-2.23.2.data/scripts/swift-object-updater +0 -23
  199. swift-2.23.2.data/scripts/swift-proxy-server +0 -23
  200. swift-2.23.2.data/scripts/swift-recon +0 -24
  201. swift-2.23.2.data/scripts/swift-ring-builder +0 -24
  202. swift-2.23.2.data/scripts/swift-ring-builder-analyzer +0 -22
  203. swift-2.23.2.data/scripts/swift-ring-composer +0 -22
  204. swift-2.23.2.dist-info/DESCRIPTION.rst +0 -166
  205. swift-2.23.2.dist-info/RECORD +0 -220
  206. swift-2.23.2.dist-info/metadata.json +0 -1
  207. swift-2.23.2.dist-info/pbr.json +0 -1
  208. {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 six
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, ShardRange
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
- from swift.proxy.controllers.base import Controller, delay_denial, \
28
- cors_validation, set_info_cache, clear_info_cache
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
- HTTPNotFound, HTTPServiceUnavailable, str_to_wsgi, wsgi_to_str, \
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 GETorHEAD(self, req):
91
- """Handler for HTTP GET/HEAD requests."""
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
- part = self.app.container_ring.get_part(
106
- self.account_name, self.container_name)
107
- concurrency = self.app.container_ring.replica_count \
108
- if self.app.concurrent_gets else 1
109
- node_iter = self.app.iter_nodes(self.app.container_ring, part)
110
- params = req.params
111
- params['format'] = 'json'
112
- record_type = req.headers.get('X-Backend-Record-Type', '').lower()
113
- if not record_type:
114
- record_type = 'auto'
115
- req.headers['X-Backend-Record-Type'] = 'auto'
116
- params['states'] = 'listing'
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
- def _get_from_shards(self, req, resp):
151
- # construct listing using shards described by the response body
152
- shard_ranges = [ShardRange.from_dict(data)
153
- for data in json.loads(resp.body)]
154
- self.app.logger.debug('GET listing from %s shards for: %s',
155
- len(shard_ranges), req.path_qs)
156
- if not shard_ranges:
157
- # can't find ranges or there was a problem getting the ranges. So
158
- # return what we have.
159
- return resp
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 = int(req.params.get('limit', CONTAINER_LISTING_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
- for shard_range in shard_ranges:
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 shard range to be
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
- last_name = objects[-1].get('name',
180
- objects[-1].get('subdir', u''))
181
- params['marker'] = bytes_to_wsgi(last_name.encode('utf-8'))
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 shard range are not fetched. This prevents a misplaced
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 shard_range:
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(shard_range.lower_str)
522
+ params['end_marker'] = str_to_wsgi(namespace.lower_str)
194
523
  else:
195
- params['end_marker'] = str_to_wsgi(shard_range.end_marker)
524
+ params['end_marker'] = str_to_wsgi(namespace.end_marker)
196
525
 
197
- if (shard_range.account == self.account_name and
198
- shard_range.container == self.container_name):
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 = {'X-Backend-Record-Type': 'object'}
530
+ headers['X-Backend-Record-Type'] = 'object'
201
531
  else:
202
- headers = None
203
- self.app.logger.debug('Getting from %s %s with %s',
204
- shard_range, shard_range.name, headers)
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, shard_range.account, shard_range.container,
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 errors or empty shard containers
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.app.logger.debug('Setting object count to %s' % len(objects))
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
- return self.GETorHEAD(req)
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
- clear_info_cache(self.app, req.environ,
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
- clear_info_cache(self.app, req.environ,
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
- clear_info_cache(self.app, req.environ,
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-Method: UPDATE`` header to access.
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)
@@ -16,8 +16,9 @@
16
16
  import json
17
17
  from time import time
18
18
 
19
- from swift.common.utils import public, get_hmac, get_swift_info, \
20
- streq_const_time
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