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
@@ -17,6 +17,10 @@ import re
17
17
  from swift.common.wsgi import WSGIContext
18
18
 
19
19
 
20
+ def app_property(name):
21
+ return property(lambda self: getattr(self.app, name))
22
+
23
+
20
24
  class RewriteContext(WSGIContext):
21
25
  base_re = None
22
26
 
@@ -18,10 +18,44 @@
18
18
  given account quota (in bytes) is exceeded while DELETE requests are still
19
19
  allowed.
20
20
 
21
- ``account_quotas`` uses the ``x-account-meta-quota-bytes`` metadata entry to
22
- store the quota. Write requests to this metadata entry are only permitted for
23
- resellers. There is no quota limit if ``x-account-meta-quota-bytes`` is not
24
- set.
21
+ ``account_quotas`` uses the following metadata entries to store the account
22
+ quota
23
+
24
+ +---------------------------------------------+-------------------------------+
25
+ |Metadata | Use |
26
+ +=============================================+===============================+
27
+ | X-Account-Meta-Quota-Bytes (obsoleted) | Maximum overall bytes stored |
28
+ | | in account across containers. |
29
+ +---------------------------------------------+-------------------------------+
30
+ | X-Account-Quota-Bytes | Maximum overall bytes stored |
31
+ | | in account across containers. |
32
+ +---------------------------------------------+-------------------------------+
33
+ | X-Account-Quota-Bytes-Policy-<policyname> | Maximum overall bytes stored |
34
+ | | in account across containers, |
35
+ | | for the given policy. |
36
+ +---------------------------------------------+-------------------------------+
37
+ | X-Account-Quota-Count | Maximum object count under |
38
+ | | account. |
39
+ +---------------------------------------------+-------------------------------+
40
+ | X-Account-Quota-Count-Policy-<policyname> | Maximum object count under |
41
+ | | account, for the given policy.|
42
+ +---------------------------------------------+-------------------------------+
43
+
44
+
45
+ Write requests to those metadata entries are only permitted for resellers.
46
+ There is no overall byte or object count limit set if the corresponding
47
+ metadata entries are not set.
48
+
49
+ Additionally, account quotas, of type quota-bytes or quota-count, may be set
50
+ for each storage policy, using metadata of the form ``x-account-<quota type>-\
51
+ policy-<policy name>``. Again, only resellers may update these metadata, and
52
+ there will be no limit for a particular policy if the corresponding metadata
53
+ is not set.
54
+
55
+ .. note::
56
+ Per-policy quotas need not sum to the overall account quota, and the sum of
57
+ all :ref:`container_quotas` for a given policy need not sum to the account's
58
+ policy quota.
25
59
 
26
60
  The ``account_quotas`` middleware should be added to the pipeline in your
27
61
  ``/etc/swift/proxy-server.conf`` file just after any auth middleware.
@@ -54,8 +88,9 @@ account size has been updated.
54
88
 
55
89
  from swift.common.swob import HTTPForbidden, HTTPBadRequest, \
56
90
  HTTPRequestEntityTooLarge, wsgify
57
- from swift.common.utils import register_swift_info
58
- from swift.proxy.controllers.base import get_account_info
91
+ from swift.common.registry import register_swift_info
92
+ from swift.common.storage_policy import POLICIES
93
+ from swift.proxy.controllers.base import get_account_info, get_container_info
59
94
 
60
95
 
61
96
  class AccountQuotaMiddleware(object):
@@ -67,12 +102,74 @@ class AccountQuotaMiddleware(object):
67
102
  def __init__(self, app, *args, **kwargs):
68
103
  self.app = app
69
104
 
105
+ def validate_and_translate_quotas(self, request, quota_type):
106
+ new_quotas = {}
107
+ new_quotas[None] = request.headers.get(
108
+ 'X-Account-%s' % quota_type)
109
+ if request.headers.get(
110
+ 'X-Remove-Account-%s' % quota_type):
111
+ new_quotas[None] = '' # X-Remove dominates if both are present
112
+
113
+ for policy in POLICIES:
114
+ tail = 'Account-%s-Policy-%s' % (quota_type, policy.name)
115
+ if request.headers.get('X-Remove-' + tail):
116
+ new_quotas[policy.idx] = ''
117
+ else:
118
+ quota = request.headers.pop('X-' + tail, None)
119
+ new_quotas[policy.idx] = quota
120
+
121
+ if request.environ.get('reseller_request') is True:
122
+ if any(quota and not quota.isdigit()
123
+ for quota in new_quotas.values()):
124
+ raise HTTPBadRequest()
125
+ for idx, quota in new_quotas.items():
126
+ if idx is None:
127
+ hdr = 'X-Account-Sysmeta-%s' % quota_type
128
+ else:
129
+ hdr = 'X-Account-Sysmeta-%s-Policy-%d' % (quota_type, idx)
130
+ request.headers[hdr] = quota
131
+ elif any(quota is not None for quota in new_quotas.values()):
132
+ # deny quota set for non-reseller
133
+ raise HTTPForbidden()
134
+
135
+ def handle_account(self, request):
136
+ if request.method in ("POST", "PUT"):
137
+ # Support old meta format
138
+ for legacy_header in [
139
+ 'X-Account-Meta-Quota-Bytes',
140
+ 'X-Remove-Account-Meta-Quota-Bytes',
141
+ ]:
142
+ new_header = legacy_header.replace('-Meta-', '-')
143
+ legacy_value = request.headers.get(legacy_header)
144
+ if legacy_value is not None and not \
145
+ request.headers.get(new_header):
146
+ request.headers[new_header] = legacy_value
147
+ # account request, so we pay attention to the quotas
148
+ self.validate_and_translate_quotas(request, "Quota-Bytes")
149
+ self.validate_and_translate_quotas(request, "Quota-Count")
150
+ resp = request.get_response(self.app)
151
+ # Non-resellers can't update quotas, but they *can* see them
152
+ # Global quotas
153
+ postfixes = ('Quota-Bytes', 'Quota-Count')
154
+ for postfix in postfixes:
155
+ value = resp.headers.get('X-Account-Sysmeta-%s' % postfix)
156
+ if value:
157
+ resp.headers['X-Account-%s' % postfix] = value
158
+
159
+ # Per policy quotas
160
+ for policy in POLICIES:
161
+ infixes = ('Quota-Bytes-Policy', 'Quota-Count-Policy')
162
+ for infix in infixes:
163
+ value = resp.headers.get('X-Account-Sysmeta-%s-%d' % (
164
+ infix, policy.idx))
165
+ if value:
166
+ resp.headers['X-Account-%s-%s' % (
167
+ infix, policy.name)] = value
168
+ return resp
169
+
70
170
  @wsgify
71
171
  def __call__(self, request):
72
172
 
73
- if request.method not in ("POST", "PUT"):
74
- return self.app
75
-
76
173
  try:
77
174
  ver, account, container, obj = request.split_path(
78
175
  2, 4, rest_with_last=True)
@@ -80,57 +177,125 @@ class AccountQuotaMiddleware(object):
80
177
  return self.app
81
178
 
82
179
  if not container:
83
- # account request, so we pay attention to the quotas
84
- new_quota = request.headers.get(
85
- 'X-Account-Meta-Quota-Bytes')
86
- remove_quota = request.headers.get(
87
- 'X-Remove-Account-Meta-Quota-Bytes')
88
- else:
89
- # container or object request; even if the quota headers are set
90
- # in the request, they're meaningless
91
- new_quota = remove_quota = None
92
-
93
- if remove_quota:
94
- new_quota = 0 # X-Remove dominates if both are present
180
+ return self.handle_account(request)
181
+ # container or object request; even if the quota headers are set
182
+ # in the request, they're meaningless
95
183
 
96
- if request.environ.get('reseller_request') is True:
97
- if new_quota and not new_quota.isdigit():
98
- return HTTPBadRequest()
184
+ if not (request.method == "PUT" and obj):
99
185
  return self.app
186
+ # OK, object PUT
100
187
 
101
- # deny quota set for non-reseller
102
- if new_quota is not None:
103
- return HTTPForbidden()
104
-
105
- if request.method == "POST" or not obj:
188
+ if request.environ.get('reseller_request') is True:
189
+ # but resellers aren't constrained by quotas :-)
106
190
  return self.app
107
191
 
192
+ # Object PUT request
108
193
  content_length = (request.content_length or 0)
109
194
 
110
- account_info = get_account_info(request.environ, self.app)
111
- if not account_info or not account_info['bytes']:
195
+ account_info = get_account_info(request.environ, self.app,
196
+ swift_source='AQ')
197
+ if not account_info:
112
198
  return self.app
199
+
200
+ # Check for quota byte violation
113
201
  try:
114
- quota = int(account_info['meta'].get('quota-bytes', -1))
202
+ quota = int(
203
+ account_info["sysmeta"].get(
204
+ "quota-bytes", account_info["meta"].get("quota-bytes", -1)
205
+ )
206
+ )
115
207
  except ValueError:
208
+ quota = -1
209
+ if quota >= 0:
210
+ new_size = int(account_info['bytes']) + content_length
211
+ if quota < new_size:
212
+ resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
213
+ if 'swift.authorize' in request.environ:
214
+ orig_authorize = request.environ['swift.authorize']
215
+
216
+ def reject_authorize(*args, **kwargs):
217
+ aresp = orig_authorize(*args, **kwargs)
218
+ if aresp:
219
+ return aresp
220
+ return resp
221
+ request.environ['swift.authorize'] = reject_authorize
222
+ else:
223
+ return resp
224
+
225
+ # Check for quota count violation
226
+ try:
227
+ quota = int(account_info['sysmeta'].get('quota-count', -1))
228
+ except ValueError:
229
+ quota = -1
230
+ if quota >= 0:
231
+ new_count = int(account_info['total_object_count']) + 1
232
+ if quota < new_count:
233
+ resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
234
+ if 'swift.authorize' in request.environ:
235
+ orig_authorize = request.environ['swift.authorize']
236
+
237
+ def reject_authorize(*args, **kwargs):
238
+ aresp = orig_authorize(*args, **kwargs)
239
+ if aresp:
240
+ return aresp
241
+ return resp
242
+ request.environ['swift.authorize'] = reject_authorize
243
+ else:
244
+ return resp
245
+
246
+ container_info = get_container_info(request.environ, self.app,
247
+ swift_source='AQ')
248
+ if not container_info:
116
249
  return self.app
117
- if quota < 0:
118
- return self.app
250
+ policy_idx = container_info['storage_policy']
119
251
 
120
- new_size = int(account_info['bytes']) + content_length
121
- if quota < new_size:
122
- resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
123
- if 'swift.authorize' in request.environ:
124
- orig_authorize = request.environ['swift.authorize']
252
+ # Check quota-byte per policy
253
+ sysmeta_key = 'quota-bytes-policy-%s' % policy_idx
254
+ try:
255
+ policy_quota = int(account_info['sysmeta'].get(sysmeta_key, -1))
256
+ except ValueError:
257
+ policy_quota = -1
258
+ if policy_quota >= 0:
259
+ policy_stats = account_info['storage_policies'].get(policy_idx, {})
260
+ new_size = int(policy_stats.get('bytes', 0)) + content_length
261
+ if policy_quota < new_size:
262
+ resp = HTTPRequestEntityTooLarge(
263
+ body='Upload exceeds policy quota.')
264
+ if 'swift.authorize' in request.environ:
265
+ orig_authorize = request.environ['swift.authorize']
266
+
267
+ def reject_authorize(*args, **kwargs):
268
+ aresp = orig_authorize(*args, **kwargs)
269
+ if aresp:
270
+ return aresp
271
+ return resp
272
+ request.environ['swift.authorize'] = reject_authorize
273
+ else:
274
+ return resp
125
275
 
126
- def reject_authorize(*args, **kwargs):
127
- aresp = orig_authorize(*args, **kwargs)
128
- if aresp:
129
- return aresp
276
+ # Check quota-count per policy
277
+ sysmeta_key = 'quota-count-policy-%s' % policy_idx
278
+ try:
279
+ policy_quota = int(account_info['sysmeta'].get(sysmeta_key, -1))
280
+ except ValueError:
281
+ policy_quota = -1
282
+ if policy_quota >= 0:
283
+ policy_stats = account_info['storage_policies'].get(policy_idx, {})
284
+ new_size = int(policy_stats.get('object_count', 0)) + 1
285
+ if policy_quota < new_size:
286
+ resp = HTTPRequestEntityTooLarge(
287
+ body='Upload exceeds policy quota.')
288
+ if 'swift.authorize' in request.environ:
289
+ orig_authorize = request.environ['swift.authorize']
290
+
291
+ def reject_authorize(*args, **kwargs):
292
+ aresp = orig_authorize(*args, **kwargs)
293
+ if aresp:
294
+ return aresp
295
+ return resp
296
+ request.environ['swift.authorize'] = reject_authorize
297
+ else:
130
298
  return resp
131
- request.environ['swift.authorize'] = reject_authorize
132
- else:
133
- return resp
134
299
 
135
300
  return self.app
136
301
 
@@ -14,8 +14,7 @@
14
14
  # limitations under the License.
15
15
 
16
16
  import json
17
- import six
18
- from six.moves.urllib.parse import unquote, urlparse
17
+ from urllib.parse import unquote, urlparse
19
18
 
20
19
 
21
20
  def clean_acl(name, value):
@@ -217,7 +216,7 @@ def parse_acl_v2(data):
217
216
  """
218
217
  if data is None:
219
218
  return None
220
- if data is '':
219
+ if data == '':
221
220
  return {}
222
221
  try:
223
222
  result = json.loads(data)
@@ -294,12 +293,8 @@ def acls_from_account_info(info):
294
293
  if not any((admin_members, readwrite_members, readonly_members)):
295
294
  return None
296
295
 
297
- acls = {
296
+ return {
298
297
  'admin': admin_members,
299
298
  'read-write': readwrite_members,
300
299
  'read-only': readonly_members,
301
300
  }
302
- if six.PY2:
303
- for k in ('admin', 'read-write', 'read-only'):
304
- acls[k] = [v.encode('utf8') for v in acls[k]]
305
- return acls
@@ -0,0 +1,230 @@
1
+ # Copyright (c) 2022 NVIDIA
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
+ # implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import os
16
+ import time
17
+
18
+ from swift.common.request_helpers import split_and_validate_path
19
+ from swift.common.swob import Request, HTTPTooManyBackendRequests, \
20
+ HTTPException
21
+ from swift.common.utils import get_logger, non_negative_float, \
22
+ EventletRateLimiter, readconf
23
+
24
+ RATE_LIMITED_METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'UPDATE',
25
+ 'REPLICATE')
26
+ BACKEND_RATELIMIT_CONFIG_SECTION = 'backend_ratelimit'
27
+ DEFAULT_BACKEND_RATELIMIT_CONF_FILE = 'backend-ratelimit.conf'
28
+ DEFAULT_CONFIG_RELOAD_INTERVAL = 60.0
29
+ DEFAULT_REQUESTS_PER_DEVICE_PER_SECOND = 0.0
30
+ DEFAULT_REQUESTS_PER_DEVICE_RATE_BUFFER = 1.0
31
+
32
+
33
+ class BackendRateLimitMiddleware(object):
34
+ """
35
+ Backend rate-limiting middleware.
36
+
37
+ Rate-limits requests to backend storage node devices. Each (device, request
38
+ method) combination is independently rate-limited. All requests with a
39
+ 'GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'UPDATE' or 'REPLICATE' method are
40
+ rate limited on a per-device basis by both a method-specific rate and an
41
+ overall device rate limit.
42
+
43
+ If a request would cause the rate-limit to be exceeded for the method
44
+ and/or device then a response with a 529 status code is returned.
45
+ """
46
+ def __init__(self, app, filter_conf, logger=None):
47
+ self.app = app
48
+ self.filter_conf = filter_conf
49
+ self.logger = logger or get_logger(self.filter_conf,
50
+ log_route='backend_ratelimit')
51
+ self.requests_per_device_rate_buffer = \
52
+ DEFAULT_REQUESTS_PER_DEVICE_RATE_BUFFER
53
+ # map (device, method) -> rate
54
+ self.requests_per_device_per_second = {}
55
+ # map (device, method) -> RateLimiter, populated on-demand
56
+ self.rate_limiters = {}
57
+
58
+ # some config options are *only* read from filter conf at startup...
59
+ default_conf_path = os.path.join(
60
+ self.filter_conf.get('swift_dir', '/etc/swift'),
61
+ DEFAULT_BACKEND_RATELIMIT_CONF_FILE)
62
+ try:
63
+ self.conf_path = self.filter_conf['backend_ratelimit_conf_path']
64
+ self.is_config_file_expected = True
65
+ except KeyError:
66
+ self.conf_path = default_conf_path
67
+ self.is_config_file_expected = False
68
+ self.config_reload_interval = non_negative_float(
69
+ filter_conf.get('config_reload_interval',
70
+ DEFAULT_CONFIG_RELOAD_INTERVAL))
71
+
72
+ # other conf options are read from filter section at startup but may
73
+ # also be overridden by options in a separate config file...
74
+ self._last_config_reload_attempt = time.time()
75
+ self._apply_config(self.filter_conf)
76
+ self._load_config_file()
77
+
78
+ def _refresh_ratelimiters(self):
79
+ # note: if we ever wanted to prune the ratelimiters (in case devices
80
+ # have been removed) we could inspect each ratelimiter's running_time
81
+ # and remove those with very old running_time
82
+ for (dev, method), rl in self.rate_limiters.items():
83
+ rl.set_max_rate(self.requests_per_device_per_second[method])
84
+ rl.set_rate_buffer(self.requests_per_device_rate_buffer)
85
+
86
+ def _apply_config(self, conf):
87
+ modified = False
88
+ reqs_per_device_rate_buffer = non_negative_float(
89
+ conf.get('requests_per_device_rate_buffer',
90
+ DEFAULT_REQUESTS_PER_DEVICE_RATE_BUFFER))
91
+
92
+ # note: 'None' key holds the aggregate per-device limit for all methods
93
+ reqs_per_device_per_second = {None: non_negative_float(
94
+ conf.get('requests_per_device_per_second', 0.0))}
95
+ for method in RATE_LIMITED_METHODS:
96
+ val = non_negative_float(
97
+ conf.get('%s_requests_per_device_per_second'
98
+ % method.lower(), 0.0))
99
+ reqs_per_device_per_second[method] = val
100
+
101
+ if reqs_per_device_rate_buffer != self.requests_per_device_rate_buffer:
102
+ self.requests_per_device_rate_buffer = reqs_per_device_rate_buffer
103
+ modified = True
104
+ if reqs_per_device_per_second != self.requests_per_device_per_second:
105
+ self.requests_per_device_per_second = reqs_per_device_per_second
106
+ self.is_any_rate_limit_configured = any(
107
+ self.requests_per_device_per_second.values())
108
+ modified = True
109
+ if modified:
110
+ self._refresh_ratelimiters()
111
+ return modified
112
+
113
+ def _load_config_file(self):
114
+ # If conf file can be read then apply its options to the filter conf
115
+ # options, discarding *all* options previously loaded from the conf
116
+ # file i.e. options deleted from the conf file will revert to the
117
+ # filter conf value or default value. If the conf file cannot be read
118
+ # or is invalid, then the current config is left unchanged.
119
+ try:
120
+ new_conf = dict(self.filter_conf) # filter_conf not current conf
121
+ new_conf.update(
122
+ readconf(self.conf_path, BACKEND_RATELIMIT_CONFIG_SECTION))
123
+ modified = self._apply_config(new_conf)
124
+ if modified:
125
+ self.logger.info('Loaded config file %s, config changed',
126
+ self.conf_path)
127
+ elif not self.is_config_file_expected:
128
+ self.logger.info('Loaded new config file %s, config unchanged',
129
+ self.conf_path)
130
+ else:
131
+ self.logger.debug(
132
+ 'Loaded existing config file %s, config unchanged',
133
+ self.conf_path)
134
+ self.is_config_file_expected = True
135
+ except IOError as err:
136
+ if self.is_config_file_expected:
137
+ self.logger.warning(
138
+ 'Failed to load config file, config unchanged: %s', err)
139
+ self.is_config_file_expected = False
140
+ except ValueError as err:
141
+ # ...but if it exists it should be valid
142
+ self.logger.warning('Invalid config file %s, config unchanged: %s',
143
+ self.conf_path, err)
144
+
145
+ def _maybe_reload_config(self):
146
+ if self.config_reload_interval:
147
+ now = time.time()
148
+ if (now - self._last_config_reload_attempt
149
+ >= self.config_reload_interval):
150
+ try:
151
+ self._load_config_file()
152
+ except Exception: # noqa
153
+ self.logger.exception('Error reloading config file')
154
+ finally:
155
+ # always reset last loaded time to avoid re-try storm
156
+ self._last_config_reload_attempt = now
157
+
158
+ def _get_ratelimiter(self, device, method=None):
159
+ """
160
+ Get a rate limiter for the (device, method) combination. If a rate
161
+ limiter does not yet exist for the given (device, method) combination
162
+ then it is created and added to the map of rate limiters.
163
+
164
+ :param: the device.
165
+ :method: the request method; if None then the aggregate rate limiter
166
+ for all requests to the device is returned.
167
+ :returns: an instance of ``EventletRateLimiter``.
168
+ """
169
+ try:
170
+ rl = self.rate_limiters[(device, method)]
171
+ except KeyError:
172
+ rl = EventletRateLimiter(
173
+ max_rate=self.requests_per_device_per_second[method],
174
+ rate_buffer=self.requests_per_device_rate_buffer,
175
+ running_time=time.time(),
176
+ burst_after_idle=True)
177
+ self.rate_limiters[(device, method)] = rl
178
+ return rl
179
+
180
+ def _is_allowed(self, device, method):
181
+ """
182
+ Evaluate backend rate-limiting policies for the incoming request.
183
+
184
+ A request is allowed when neither the per-(device, method) rate-limit
185
+ nor the per-device rate-limit has been reached.
186
+
187
+ Note: a request will be disallowed if the aggregate per-device
188
+ rate-limit has been reached, even if the per-(device, method)
189
+ rate-limit has not been reached for the request's method.
190
+
191
+ :param: the device.
192
+ :method: the request method.
193
+ :returns: boolean, is_allowed.
194
+ """
195
+ return (self._get_ratelimiter(device, None).is_allowed()
196
+ and self._get_ratelimiter(device, method).is_allowed())
197
+
198
+ def __call__(self, env, start_response):
199
+ """
200
+ WSGI entry point.
201
+
202
+ :param env: WSGI environment dictionary
203
+ :param start_response: WSGI callable
204
+ """
205
+ self._maybe_reload_config()
206
+ req = Request(env)
207
+ handler = self.app
208
+ if (self.is_any_rate_limit_configured
209
+ and req.method in RATE_LIMITED_METHODS):
210
+ try:
211
+ device, partition, _ = split_and_validate_path(req, 1, 3, True)
212
+ int(partition) # check it's a valid partition
213
+ except (ValueError, HTTPException):
214
+ # request may not have device/partition e.g. a healthcheck req
215
+ pass
216
+ else:
217
+ if not self._is_allowed(device, req.method):
218
+ self.logger.increment('backend.ratelimit')
219
+ handler = HTTPTooManyBackendRequests()
220
+ return handler(env, start_response)
221
+
222
+
223
+ def filter_factory(global_conf, **local_conf):
224
+ conf = global_conf.copy()
225
+ conf.update(local_conf)
226
+
227
+ def backend_ratelimit_filter(app):
228
+ return BackendRateLimitMiddleware(app, conf)
229
+
230
+ return backend_ratelimit_filter