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.
Files changed (206) 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.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +154 -14
  17. swift/cli/manage_shard_ranges.py +705 -37
  18. swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.3.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 +195 -128
  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 +390 -145
  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 -95
  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 +51 -18
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +191 -173
  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.3.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} +2163 -2772
  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 +553 -535
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +490 -231
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +358 -165
  135. swift/container/sharder.py +1540 -684
  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 +207 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +652 -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 +433 -92
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1000 -489
  156. swift/proxy/server.py +185 -112
  157. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
  158. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
  159. swift-2.35.0.dist-info/RECORD +201 -0
  160. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  161. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  162. swift-2.35.0.dist-info/pbr.json +1 -0
  163. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  164. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  165. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  166. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  167. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  168. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  169. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  170. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  171. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  172. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  173. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  174. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  175. swift-2.23.3.data/scripts/swift-account-auditor +0 -23
  176. swift-2.23.3.data/scripts/swift-account-info +0 -51
  177. swift-2.23.3.data/scripts/swift-account-reaper +0 -23
  178. swift-2.23.3.data/scripts/swift-account-replicator +0 -34
  179. swift-2.23.3.data/scripts/swift-account-server +0 -23
  180. swift-2.23.3.data/scripts/swift-container-auditor +0 -23
  181. swift-2.23.3.data/scripts/swift-container-info +0 -55
  182. swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
  183. swift-2.23.3.data/scripts/swift-container-replicator +0 -34
  184. swift-2.23.3.data/scripts/swift-container-sharder +0 -37
  185. swift-2.23.3.data/scripts/swift-container-sync +0 -23
  186. swift-2.23.3.data/scripts/swift-container-updater +0 -23
  187. swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
  188. swift-2.23.3.data/scripts/swift-form-signature +0 -20
  189. swift-2.23.3.data/scripts/swift-init +0 -119
  190. swift-2.23.3.data/scripts/swift-object-auditor +0 -29
  191. swift-2.23.3.data/scripts/swift-object-expirer +0 -33
  192. swift-2.23.3.data/scripts/swift-object-info +0 -60
  193. swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
  194. swift-2.23.3.data/scripts/swift-object-relinker +0 -41
  195. swift-2.23.3.data/scripts/swift-object-replicator +0 -37
  196. swift-2.23.3.data/scripts/swift-object-server +0 -27
  197. swift-2.23.3.data/scripts/swift-object-updater +0 -23
  198. swift-2.23.3.data/scripts/swift-proxy-server +0 -23
  199. swift-2.23.3.data/scripts/swift-recon +0 -24
  200. swift-2.23.3.data/scripts/swift-ring-builder +0 -24
  201. swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
  202. swift-2.23.3.data/scripts/swift-ring-composer +0 -22
  203. swift-2.23.3.dist-info/RECORD +0 -220
  204. swift-2.23.3.dist-info/pbr.json +0 -1
  205. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
  206. {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 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,31 +395,92 @@ 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
- # The history of containers that have returned shard ranges is
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 shard ranges with a
157
- # shard range pointing to itself
158
- # 2. a root container returns a (stale) shard range pointing to a
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 shard range.
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
- shard_ranges = [ShardRange.from_dict(data)
165
- for data in json.loads(resp.body)]
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 = int(req.params.get('limit') or CONTAINER_LISTING_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
- for shard_range in shard_ranges:
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 shard range to be
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
- last_name = objects[-1].get('name',
193
- objects[-1].get('subdir', u''))
194
- 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)
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 shard range are not fetched. This prevents a misplaced
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 shard_range:
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(shard_range.lower_str)
522
+ params['end_marker'] = str_to_wsgi(namespace.lower_str)
207
523
  else:
208
- params['end_marker'] = str_to_wsgi(shard_range.end_marker)
524
+ params['end_marker'] = str_to_wsgi(namespace.end_marker)
209
525
 
210
- if ((shard_range.account, shard_range.container) in
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 = {'X-Backend-Record-Type': 'object'}
530
+ headers['X-Backend-Record-Type'] = 'object'
214
531
  else:
215
- headers = None
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 > shard_range:
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 < shard_range:
544
+ if just_past < namespace:
226
545
  continue
227
546
 
228
- self.app.logger.debug('Getting from %s %s with %s',
229
- shard_range, shard_range.name, headers)
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, shard_range.account, shard_range.container,
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 errors or empty shard containers
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.app.logger.debug('Setting object count to %s' % len(objects))
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
- 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)
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
- clear_info_cache(self.app, req.environ,
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
- clear_info_cache(self.app, req.environ,
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
- clear_info_cache(self.app, req.environ,
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-Method: UPDATE`` header to access.
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)