swift 2.31.1__py2.py3-none-any.whl → 2.32.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- swift/cli/info.py +9 -2
- swift/cli/ringbuilder.py +5 -1
- swift/common/container_sync_realms.py +6 -7
- swift/common/daemon.py +7 -3
- swift/common/db.py +22 -7
- swift/common/db_replicator.py +19 -20
- swift/common/direct_client.py +63 -14
- swift/common/internal_client.py +24 -3
- swift/common/manager.py +43 -44
- swift/common/memcached.py +168 -74
- swift/common/middleware/__init__.py +4 -0
- swift/common/middleware/account_quotas.py +98 -40
- swift/common/middleware/backend_ratelimit.py +6 -4
- swift/common/middleware/crossdomain.py +21 -8
- swift/common/middleware/listing_formats.py +26 -38
- swift/common/middleware/proxy_logging.py +12 -9
- swift/common/middleware/s3api/controllers/bucket.py +8 -2
- swift/common/middleware/s3api/s3api.py +9 -4
- swift/common/middleware/s3api/s3request.py +32 -24
- swift/common/middleware/s3api/s3response.py +10 -1
- swift/common/middleware/tempauth.py +9 -10
- swift/common/middleware/versioned_writes/__init__.py +0 -3
- swift/common/middleware/versioned_writes/object_versioning.py +22 -5
- swift/common/middleware/x_profile/html_viewer.py +1 -1
- swift/common/middleware/xprofile.py +5 -0
- swift/common/request_helpers.py +1 -2
- swift/common/ring/ring.py +22 -19
- swift/common/swob.py +2 -1
- swift/common/{utils.py → utils/__init__.py} +610 -1146
- swift/common/utils/ipaddrs.py +256 -0
- swift/common/utils/libc.py +345 -0
- swift/common/utils/timestamp.py +399 -0
- swift/common/wsgi.py +70 -39
- swift/container/backend.py +106 -38
- swift/container/server.py +11 -2
- swift/container/sharder.py +34 -15
- swift/locale/de/LC_MESSAGES/swift.po +1 -320
- swift/locale/en_GB/LC_MESSAGES/swift.po +1 -347
- swift/locale/es/LC_MESSAGES/swift.po +1 -279
- swift/locale/fr/LC_MESSAGES/swift.po +1 -209
- swift/locale/it/LC_MESSAGES/swift.po +1 -207
- swift/locale/ja/LC_MESSAGES/swift.po +2 -278
- swift/locale/ko_KR/LC_MESSAGES/swift.po +3 -303
- swift/locale/pt_BR/LC_MESSAGES/swift.po +1 -204
- swift/locale/ru/LC_MESSAGES/swift.po +1 -203
- swift/locale/tr_TR/LC_MESSAGES/swift.po +1 -192
- swift/locale/zh_CN/LC_MESSAGES/swift.po +1 -192
- swift/locale/zh_TW/LC_MESSAGES/swift.po +1 -193
- swift/obj/diskfile.py +19 -6
- swift/obj/server.py +20 -6
- swift/obj/ssync_receiver.py +19 -9
- swift/obj/ssync_sender.py +10 -10
- swift/proxy/controllers/account.py +7 -7
- swift/proxy/controllers/base.py +374 -366
- swift/proxy/controllers/container.py +112 -53
- swift/proxy/controllers/obj.py +254 -390
- swift/proxy/server.py +3 -8
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-server +1 -1
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-server +1 -1
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-drive-audit +45 -14
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-server +1 -1
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-proxy-server +1 -1
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/AUTHORS +4 -0
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/METADATA +32 -35
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/RECORD +103 -100
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/WHEEL +1 -1
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/entry_points.txt +0 -1
- swift-2.32.1.dist-info/pbr.json +1 -0
- swift-2.31.1.dist-info/pbr.json +0 -1
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-audit +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-auditor +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-info +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-reaper +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-replicator +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-config +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-auditor +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-info +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-reconciler +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-replicator +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-sharder +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-sync +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-updater +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-dispersion-populate +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-dispersion-report +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-form-signature +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-get-nodes +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-init +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-auditor +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-expirer +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-info +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-reconstructor +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-relinker +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-replicator +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-updater +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-oldies +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-orphans +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-recon +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-recon-cron +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-reconciler-enqueue +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-builder +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-builder-analyzer +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-composer +0 -0
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/LICENSE +0 -0
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/top_level.txt +0 -0
swift/common/memcached.py
CHANGED
@@ -48,7 +48,9 @@ import os
|
|
48
48
|
import six
|
49
49
|
import json
|
50
50
|
import logging
|
51
|
-
|
51
|
+
# the name of 'time' module is changed to 'tm', to avoid changing the
|
52
|
+
# signatures of member functions in this file.
|
53
|
+
import time as tm
|
52
54
|
from bisect import bisect
|
53
55
|
|
54
56
|
from eventlet.green import socket, ssl
|
@@ -99,7 +101,7 @@ def sanitize_timeout(timeout):
|
|
99
101
|
additional second), client beware.
|
100
102
|
"""
|
101
103
|
if timeout > (30 * 24 * 60 * 60):
|
102
|
-
timeout +=
|
104
|
+
timeout += tm.time()
|
103
105
|
return int(timeout)
|
104
106
|
|
105
107
|
|
@@ -121,6 +123,10 @@ class MemcacheConnectionError(Exception):
|
|
121
123
|
pass
|
122
124
|
|
123
125
|
|
126
|
+
class MemcacheIncrNotFoundError(MemcacheConnectionError):
|
127
|
+
pass
|
128
|
+
|
129
|
+
|
124
130
|
class MemcachePoolTimeout(Timeout):
|
125
131
|
pass
|
126
132
|
|
@@ -174,6 +180,29 @@ class MemcacheConnPool(Pool):
|
|
174
180
|
raise
|
175
181
|
|
176
182
|
|
183
|
+
class MemcacheCommand(object):
|
184
|
+
"""
|
185
|
+
Helper class that encapsulates common parameters of a command.
|
186
|
+
|
187
|
+
:param method: the name of the MemcacheRing method that was called.
|
188
|
+
:param key: the memcached key.
|
189
|
+
"""
|
190
|
+
__slots__ = ('method', 'key', 'command', 'hash_key')
|
191
|
+
|
192
|
+
def __init__(self, method, key):
|
193
|
+
self.method = method
|
194
|
+
self.key = key
|
195
|
+
self.command = method.encode()
|
196
|
+
self.hash_key = md5hash(key)
|
197
|
+
|
198
|
+
@property
|
199
|
+
def key_prefix(self):
|
200
|
+
# get the prefix of a user provided memcache key by removing the
|
201
|
+
# content after the last '/', all current usages within swift are using
|
202
|
+
# prefix, such as "shard-updating-v2", "nvratelimit" and etc.
|
203
|
+
return self.key.rsplit('/', 1)[0]
|
204
|
+
|
205
|
+
|
177
206
|
class MemcacheRing(object):
|
178
207
|
"""
|
179
208
|
Simple, consistent-hashed memcache client.
|
@@ -216,18 +245,56 @@ class MemcacheRing(object):
|
|
216
245
|
def memcache_servers(self):
|
217
246
|
return list(self._client_cache.keys())
|
218
247
|
|
219
|
-
|
220
|
-
|
248
|
+
"""
|
249
|
+
Handles exceptions.
|
250
|
+
|
251
|
+
:param server: a server.
|
252
|
+
:param e: an exception.
|
253
|
+
:param cmd: an instance of MemcacheCommand.
|
254
|
+
:param conn_start_time: the time at which the failed operation started.
|
255
|
+
:param action: a verb describing the operation.
|
256
|
+
:param sock: an optional socket that needs to be closed by this method.
|
257
|
+
:param fp: an optional file pointer that needs to be closed by this method.
|
258
|
+
:param got_connection: if ``True``, the server's connection will be reset
|
259
|
+
in the cached connection pool.
|
260
|
+
"""
|
261
|
+
def _exception_occurred(self, server, e, cmd, conn_start_time,
|
262
|
+
action='talking', sock=None,
|
263
|
+
fp=None, got_connection=True):
|
221
264
|
if isinstance(e, Timeout):
|
222
|
-
self.logger.error(
|
223
|
-
|
265
|
+
self.logger.error(
|
266
|
+
"Timeout %(action)s to memcached: %(server)s"
|
267
|
+
": with key_prefix %(key_prefix)s, method %(method)s, "
|
268
|
+
"config_timeout %(config_timeout)s, time_spent %(time_spent)s",
|
269
|
+
{'action': action, 'server': server,
|
270
|
+
'key_prefix': cmd.key_prefix, 'method': cmd.method,
|
271
|
+
'config_timeout': e.seconds,
|
272
|
+
'time_spent': tm.time() - conn_start_time})
|
273
|
+
self.logger.timing_since(
|
274
|
+
'memcached.' + cmd.method + '.timeout.timing',
|
275
|
+
conn_start_time)
|
224
276
|
elif isinstance(e, (socket.error, MemcacheConnectionError)):
|
225
277
|
self.logger.error(
|
226
|
-
"Error %(action)s to memcached: %(server)s:
|
227
|
-
|
278
|
+
"Error %(action)s to memcached: %(server)s: "
|
279
|
+
"with key_prefix %(key_prefix)s, method %(method)s, "
|
280
|
+
"time_spent %(time_spent)s, %(err)s",
|
281
|
+
{'action': action, 'server': server,
|
282
|
+
'key_prefix': cmd.key_prefix, 'method': cmd.method,
|
283
|
+
'time_spent': tm.time() - conn_start_time, 'err': e})
|
284
|
+
self.logger.timing_since(
|
285
|
+
'memcached.' + cmd.method + '.conn_err.timing',
|
286
|
+
conn_start_time)
|
228
287
|
else:
|
229
|
-
self.logger.exception(
|
230
|
-
|
288
|
+
self.logger.exception(
|
289
|
+
"Error %(action)s to memcached: %(server)s"
|
290
|
+
": with key_prefix %(key_prefix)s, method %(method)s, "
|
291
|
+
"time_spent %(time_spent)s",
|
292
|
+
{'action': action, 'server': server,
|
293
|
+
'key_prefix': cmd.key_prefix, 'method': cmd.method,
|
294
|
+
'time_spent': tm.time() - conn_start_time})
|
295
|
+
self.logger.timing_since(
|
296
|
+
'memcached.' + cmd.method + '.errors.timing', conn_start_time)
|
297
|
+
|
231
298
|
try:
|
232
299
|
if fp:
|
233
300
|
fp.close()
|
@@ -245,10 +312,16 @@ class MemcacheRing(object):
|
|
245
312
|
# A new connection will be created the next time it is retrieved
|
246
313
|
self._return_conn(server, None, None)
|
247
314
|
|
315
|
+
if isinstance(e, MemcacheIncrNotFoundError):
|
316
|
+
# these errors can be caused by other greenthreads not yielding to
|
317
|
+
# the incr greenthread often enough, rather than a server problem,
|
318
|
+
# so don't error limit the server
|
319
|
+
return
|
320
|
+
|
248
321
|
if self._error_limit_time <= 0 or self._error_limit_duration <= 0:
|
249
322
|
return
|
250
323
|
|
251
|
-
now =
|
324
|
+
now = tm.time()
|
252
325
|
self._errors[server].append(now)
|
253
326
|
if len(self._errors[server]) > self._error_limit_count:
|
254
327
|
self._errors[server] = [err for err in self._errors[server]
|
@@ -257,14 +330,15 @@ class MemcacheRing(object):
|
|
257
330
|
self._error_limited[server] = now + self._error_limit_duration
|
258
331
|
self.logger.error('Error limiting server %s', server)
|
259
332
|
|
260
|
-
def _get_conns(self,
|
333
|
+
def _get_conns(self, cmd):
|
261
334
|
"""
|
262
335
|
Retrieves a server conn from the pool, or connects a new one.
|
263
336
|
Chooses the server based on a consistent hash of "key".
|
264
337
|
|
338
|
+
:param cmd: an instance of MemcacheCommand.
|
265
339
|
:return: generator to serve memcached connection
|
266
340
|
"""
|
267
|
-
pos = bisect(self._sorted,
|
341
|
+
pos = bisect(self._sorted, cmd.hash_key)
|
268
342
|
served = []
|
269
343
|
any_yielded = False
|
270
344
|
while len(served) < self._tries:
|
@@ -273,7 +347,8 @@ class MemcacheRing(object):
|
|
273
347
|
if server in served:
|
274
348
|
continue
|
275
349
|
served.append(server)
|
276
|
-
|
350
|
+
pool_start_time = tm.time()
|
351
|
+
if self._error_limited[server] > pool_start_time:
|
277
352
|
continue
|
278
353
|
sock = None
|
279
354
|
try:
|
@@ -282,15 +357,15 @@ class MemcacheRing(object):
|
|
282
357
|
any_yielded = True
|
283
358
|
yield server, fp, sock
|
284
359
|
except MemcachePoolTimeout as e:
|
285
|
-
self._exception_occurred(
|
286
|
-
|
287
|
-
|
360
|
+
self._exception_occurred(server, e, cmd, pool_start_time,
|
361
|
+
action='getting a connection',
|
362
|
+
got_connection=False)
|
288
363
|
except (Exception, Timeout) as e:
|
289
364
|
# Typically a Timeout exception caught here is the one raised
|
290
365
|
# by the create() method of this server's MemcacheConnPool
|
291
366
|
# object.
|
292
|
-
self._exception_occurred(
|
293
|
-
|
367
|
+
self._exception_occurred(server, e, cmd, pool_start_time,
|
368
|
+
action='connecting', sock=sock)
|
294
369
|
if not any_yielded:
|
295
370
|
self.logger.error('All memcached servers error-limited')
|
296
371
|
|
@@ -318,7 +393,7 @@ class MemcacheRing(object):
|
|
318
393
|
:param raise_on_error: if True, propagate Timeouts and other errors.
|
319
394
|
By default, errors are ignored.
|
320
395
|
"""
|
321
|
-
|
396
|
+
cmd = MemcacheCommand('set', key)
|
322
397
|
timeout = sanitize_timeout(time)
|
323
398
|
flags = 0
|
324
399
|
if serialize:
|
@@ -329,10 +404,11 @@ class MemcacheRing(object):
|
|
329
404
|
elif not isinstance(value, bytes):
|
330
405
|
value = str(value).encode('utf-8')
|
331
406
|
|
332
|
-
for (server, fp, sock) in self._get_conns(
|
407
|
+
for (server, fp, sock) in self._get_conns(cmd):
|
408
|
+
conn_start_time = tm.time()
|
333
409
|
try:
|
334
410
|
with Timeout(self._io_timeout):
|
335
|
-
sock.sendall(set_msg(
|
411
|
+
sock.sendall(set_msg(cmd.hash_key, flags, timeout, value))
|
336
412
|
# Wait for the set to complete
|
337
413
|
msg = fp.readline().strip()
|
338
414
|
if msg != b'STORED':
|
@@ -352,7 +428,8 @@ class MemcacheRing(object):
|
|
352
428
|
self._return_conn(server, fp, sock)
|
353
429
|
return
|
354
430
|
except (Exception, Timeout) as e:
|
355
|
-
self._exception_occurred(server, e,
|
431
|
+
self._exception_occurred(server, e, cmd, conn_start_time,
|
432
|
+
sock=sock, fp=fp)
|
356
433
|
if raise_on_error:
|
357
434
|
raise MemcacheConnectionError(
|
358
435
|
"No memcached connections succeeded.")
|
@@ -368,19 +445,21 @@ class MemcacheRing(object):
|
|
368
445
|
By default, errors are treated as cache misses.
|
369
446
|
:returns: value of the key in memcache
|
370
447
|
"""
|
371
|
-
|
448
|
+
cmd = MemcacheCommand('get', key)
|
372
449
|
value = None
|
373
|
-
for (server, fp, sock) in self._get_conns(
|
450
|
+
for (server, fp, sock) in self._get_conns(cmd):
|
451
|
+
conn_start_time = tm.time()
|
374
452
|
try:
|
375
453
|
with Timeout(self._io_timeout):
|
376
|
-
sock.sendall(b'get ' +
|
454
|
+
sock.sendall(b'get ' + cmd.hash_key + b'\r\n')
|
377
455
|
line = fp.readline().strip().split()
|
378
456
|
while True:
|
379
457
|
if not line:
|
380
458
|
raise MemcacheConnectionError('incomplete read')
|
381
459
|
if line[0].upper() == b'END':
|
382
460
|
break
|
383
|
-
if line[0].upper() == b'VALUE' and
|
461
|
+
if (line[0].upper() == b'VALUE' and
|
462
|
+
line[1] == cmd.hash_key):
|
384
463
|
size = int(line[3])
|
385
464
|
value = fp.read(size)
|
386
465
|
if int(line[2]) & PICKLE_FLAG:
|
@@ -392,11 +471,29 @@ class MemcacheRing(object):
|
|
392
471
|
self._return_conn(server, fp, sock)
|
393
472
|
return value
|
394
473
|
except (Exception, Timeout) as e:
|
395
|
-
self._exception_occurred(server, e,
|
474
|
+
self._exception_occurred(server, e, cmd, conn_start_time,
|
475
|
+
sock=sock, fp=fp)
|
396
476
|
if raise_on_error:
|
397
477
|
raise MemcacheConnectionError(
|
398
478
|
"No memcached connections succeeded.")
|
399
479
|
|
480
|
+
def _incr_or_decr(self, fp, sock, cmd, delta):
|
481
|
+
sock.sendall(b' '.join([cmd.command, cmd.hash_key, delta]) + b'\r\n')
|
482
|
+
line = fp.readline().strip().split()
|
483
|
+
if not line:
|
484
|
+
raise MemcacheConnectionError('incomplete read')
|
485
|
+
if line[0].upper() == b'NOT_FOUND':
|
486
|
+
return None
|
487
|
+
return int(line[0].strip())
|
488
|
+
|
489
|
+
def _add(self, fp, sock, cmd, add_val, timeout):
|
490
|
+
sock.sendall(b' '.join([
|
491
|
+
b'add', cmd.hash_key, b'0', str(timeout).encode('ascii'),
|
492
|
+
str(len(add_val)).encode('ascii')
|
493
|
+
]) + b'\r\n' + add_val + b'\r\n')
|
494
|
+
line = fp.readline().strip().split()
|
495
|
+
return None if line[0].upper() == b'NOT_STORED' else int(add_val)
|
496
|
+
|
400
497
|
@memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_LOW)
|
401
498
|
def incr(self, key, delta=1, time=0):
|
402
499
|
"""
|
@@ -415,43 +512,33 @@ class MemcacheRing(object):
|
|
415
512
|
:returns: result of incrementing
|
416
513
|
:raises MemcacheConnectionError:
|
417
514
|
"""
|
418
|
-
|
419
|
-
|
420
|
-
if delta < 0:
|
421
|
-
command = b'decr'
|
422
|
-
delta = str(abs(int(delta))).encode('ascii')
|
515
|
+
cmd = MemcacheCommand('incr' if delta >= 0 else 'decr', key)
|
516
|
+
delta_val = str(abs(int(delta))).encode('ascii')
|
423
517
|
timeout = sanitize_timeout(time)
|
424
|
-
for (server, fp, sock) in self._get_conns(
|
518
|
+
for (server, fp, sock) in self._get_conns(cmd):
|
519
|
+
conn_start_time = tm.time()
|
425
520
|
try:
|
426
521
|
with Timeout(self._io_timeout):
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
if line[0].upper() == b'NOT_STORED':
|
442
|
-
sock.sendall(b' '.join([
|
443
|
-
command, key, delta]) + b'\r\n')
|
444
|
-
line = fp.readline().strip().split()
|
445
|
-
ret = int(line[0].strip())
|
446
|
-
else:
|
447
|
-
ret = int(add_val)
|
448
|
-
else:
|
449
|
-
ret = int(line[0].strip())
|
522
|
+
new_val = self._incr_or_decr(fp, sock, cmd, delta_val)
|
523
|
+
if new_val is None:
|
524
|
+
add_val = b'0' if cmd.method == 'decr' else delta_val
|
525
|
+
new_val = self._add(fp, sock, cmd, add_val, timeout)
|
526
|
+
if new_val is None:
|
527
|
+
new_val = self._incr_or_decr(
|
528
|
+
fp, sock, cmd, delta_val)
|
529
|
+
if new_val is None:
|
530
|
+
# This can happen if this thread takes more
|
531
|
+
# than the TTL to get from the first failed
|
532
|
+
# incr to the second incr, during which time
|
533
|
+
# the key was concurrently added and expired.
|
534
|
+
raise MemcacheIncrNotFoundError(
|
535
|
+
'expired ttl=%s' % time)
|
450
536
|
self._return_conn(server, fp, sock)
|
451
|
-
return
|
537
|
+
return new_val
|
452
538
|
except (Exception, Timeout) as e:
|
453
|
-
self._exception_occurred(server, e,
|
454
|
-
|
539
|
+
self._exception_occurred(server, e, cmd, conn_start_time,
|
540
|
+
sock=sock, fp=fp)
|
541
|
+
raise MemcacheConnectionError("No memcached connections succeeded.")
|
455
542
|
|
456
543
|
@memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_LOW)
|
457
544
|
def decr(self, key, delta=1, time=0):
|
@@ -478,18 +565,21 @@ class MemcacheRing(object):
|
|
478
565
|
:param server_key: key to use in determining which server in the ring
|
479
566
|
is used
|
480
567
|
"""
|
481
|
-
|
482
|
-
|
483
|
-
|
568
|
+
cmd = server_cmd = MemcacheCommand('delete', key)
|
569
|
+
if server_key:
|
570
|
+
server_cmd = MemcacheCommand('delete', server_key)
|
571
|
+
for (server, fp, sock) in self._get_conns(server_cmd):
|
572
|
+
conn_start_time = tm.time()
|
484
573
|
try:
|
485
574
|
with Timeout(self._io_timeout):
|
486
|
-
sock.sendall(b'delete ' +
|
575
|
+
sock.sendall(b'delete ' + cmd.hash_key + b'\r\n')
|
487
576
|
# Wait for the delete to complete
|
488
577
|
fp.readline()
|
489
578
|
self._return_conn(server, fp, sock)
|
490
579
|
return
|
491
580
|
except (Exception, Timeout) as e:
|
492
|
-
self._exception_occurred(server, e,
|
581
|
+
self._exception_occurred(server, e, cmd, conn_start_time,
|
582
|
+
sock=sock, fp=fp)
|
493
583
|
|
494
584
|
@memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_HIGH)
|
495
585
|
def set_multi(self, mapping, server_key, serialize=True, time=0,
|
@@ -508,7 +598,7 @@ class MemcacheRing(object):
|
|
508
598
|
python-memcached interface. This implementation
|
509
599
|
ignores it
|
510
600
|
"""
|
511
|
-
|
601
|
+
cmd = MemcacheCommand('set_multi', server_key)
|
512
602
|
timeout = sanitize_timeout(time)
|
513
603
|
msg = []
|
514
604
|
for key, value in mapping.items():
|
@@ -520,7 +610,8 @@ class MemcacheRing(object):
|
|
520
610
|
value = json.dumps(value).encode('ascii')
|
521
611
|
flags |= JSON_FLAG
|
522
612
|
msg.append(set_msg(key, flags, timeout, value))
|
523
|
-
for (server, fp, sock) in self._get_conns(
|
613
|
+
for (server, fp, sock) in self._get_conns(cmd):
|
614
|
+
conn_start_time = tm.time()
|
524
615
|
try:
|
525
616
|
with Timeout(self._io_timeout):
|
526
617
|
sock.sendall(b''.join(msg))
|
@@ -530,7 +621,8 @@ class MemcacheRing(object):
|
|
530
621
|
self._return_conn(server, fp, sock)
|
531
622
|
return
|
532
623
|
except (Exception, Timeout) as e:
|
533
|
-
self._exception_occurred(server, e,
|
624
|
+
self._exception_occurred(server, e, cmd, conn_start_time,
|
625
|
+
sock=sock, fp=fp)
|
534
626
|
|
535
627
|
@memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_HIGH)
|
536
628
|
def get_multi(self, keys, server_key):
|
@@ -542,12 +634,13 @@ class MemcacheRing(object):
|
|
542
634
|
is used
|
543
635
|
:returns: list of values
|
544
636
|
"""
|
545
|
-
|
546
|
-
|
547
|
-
for (server, fp, sock) in self._get_conns(
|
637
|
+
cmd = MemcacheCommand('get_multi', server_key)
|
638
|
+
hash_keys = [md5hash(key) for key in keys]
|
639
|
+
for (server, fp, sock) in self._get_conns(cmd):
|
640
|
+
conn_start_time = tm.time()
|
548
641
|
try:
|
549
642
|
with Timeout(self._io_timeout):
|
550
|
-
sock.sendall(b'get ' + b' '.join(
|
643
|
+
sock.sendall(b'get ' + b' '.join(hash_keys) + b'\r\n')
|
551
644
|
line = fp.readline().strip().split()
|
552
645
|
responses = {}
|
553
646
|
while True:
|
@@ -566,7 +659,7 @@ class MemcacheRing(object):
|
|
566
659
|
fp.readline()
|
567
660
|
line = fp.readline().strip().split()
|
568
661
|
values = []
|
569
|
-
for key in
|
662
|
+
for key in hash_keys:
|
570
663
|
if key in responses:
|
571
664
|
values.append(responses[key])
|
572
665
|
else:
|
@@ -574,7 +667,8 @@ class MemcacheRing(object):
|
|
574
667
|
self._return_conn(server, fp, sock)
|
575
668
|
return values
|
576
669
|
except (Exception, Timeout) as e:
|
577
|
-
self._exception_occurred(server, e,
|
670
|
+
self._exception_occurred(server, e, cmd, conn_start_time,
|
671
|
+
sock=sock, fp=fp)
|
578
672
|
|
579
673
|
|
580
674
|
def load_memcache(conf, logger):
|
@@ -19,9 +19,19 @@ given account quota (in bytes) is exceeded while DELETE requests are still
|
|
19
19
|
allowed.
|
20
20
|
|
21
21
|
``account_quotas`` uses the ``x-account-meta-quota-bytes`` metadata entry to
|
22
|
-
store the quota. Write requests to this metadata entry are
|
23
|
-
resellers. There is no quota limit if
|
24
|
-
set.
|
22
|
+
store the overall account quota. Write requests to this metadata entry are
|
23
|
+
only permitted for resellers. There is no overall account quota limit if
|
24
|
+
``x-account-meta-quota-bytes`` is not set.
|
25
|
+
|
26
|
+
Additionally, account quotas may be set for each storage policy, using metadata
|
27
|
+
of the form ``x-account-quota-bytes-policy-<policy name>``. Again, only
|
28
|
+
resellers may update these metadata, and there will be no limit for a
|
29
|
+
particular policy if the corresponding metadata is not set.
|
30
|
+
|
31
|
+
.. note::
|
32
|
+
Per-policy quotas need not sum to the overall account quota, and the sum of
|
33
|
+
all :ref:`container_quotas` for a given policy need not sum to the account's
|
34
|
+
policy quota.
|
25
35
|
|
26
36
|
The ``account_quotas`` middleware should be added to the pipeline in your
|
27
37
|
``/etc/swift/proxy-server.conf`` file just after any auth middleware.
|
@@ -55,7 +65,8 @@ account size has been updated.
|
|
55
65
|
from swift.common.swob import HTTPForbidden, HTTPBadRequest, \
|
56
66
|
HTTPRequestEntityTooLarge, wsgify
|
57
67
|
from swift.common.registry import register_swift_info
|
58
|
-
from swift.
|
68
|
+
from swift.common.storage_policy import POLICIES
|
69
|
+
from swift.proxy.controllers.base import get_account_info, get_container_info
|
59
70
|
|
60
71
|
|
61
72
|
class AccountQuotaMiddleware(object):
|
@@ -68,29 +79,49 @@ class AccountQuotaMiddleware(object):
|
|
68
79
|
self.app = app
|
69
80
|
|
70
81
|
def handle_account(self, request):
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
'X-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
82
|
+
if request.method in ("POST", "PUT"):
|
83
|
+
# account request, so we pay attention to the quotas
|
84
|
+
new_quotas = {}
|
85
|
+
new_quotas[None] = request.headers.get(
|
86
|
+
'X-Account-Meta-Quota-Bytes')
|
87
|
+
if request.headers.get(
|
88
|
+
'X-Remove-Account-Meta-Quota-Bytes'):
|
89
|
+
new_quotas[None] = 0 # X-Remove dominates if both are present
|
90
|
+
|
91
|
+
for policy in POLICIES:
|
92
|
+
tail = 'Account-Quota-Bytes-Policy-%s' % policy.name
|
93
|
+
if request.headers.get('X-Remove-' + tail):
|
94
|
+
new_quotas[policy.idx] = 0
|
95
|
+
else:
|
96
|
+
quota = request.headers.pop('X-' + tail, None)
|
97
|
+
new_quotas[policy.idx] = quota
|
98
|
+
|
99
|
+
if request.environ.get('reseller_request') is True:
|
100
|
+
if any(quota and not quota.isdigit()
|
101
|
+
for quota in new_quotas.values()):
|
102
|
+
return HTTPBadRequest()
|
103
|
+
for idx, quota in new_quotas.items():
|
104
|
+
if idx is None:
|
105
|
+
continue # For legacy reasons, it's in user meta
|
106
|
+
hdr = 'X-Account-Sysmeta-Quota-Bytes-Policy-%d' % idx
|
107
|
+
request.headers[hdr] = quota
|
108
|
+
elif any(quota is not None for quota in new_quotas.values()):
|
109
|
+
# deny quota set for non-reseller
|
110
|
+
return HTTPForbidden()
|
111
|
+
|
112
|
+
resp = request.get_response(self.app)
|
113
|
+
# Non-resellers can't update quotas, but they *can* see them
|
114
|
+
for policy in POLICIES:
|
115
|
+
infix = 'Quota-Bytes-Policy'
|
116
|
+
value = resp.headers.get('X-Account-Sysmeta-%s-%d' % (
|
117
|
+
infix, policy.idx))
|
118
|
+
if value:
|
119
|
+
resp.headers['X-Account-%s-%s' % (infix, policy.name)] = value
|
120
|
+
return resp
|
87
121
|
|
88
122
|
@wsgify
|
89
123
|
def __call__(self, request):
|
90
124
|
|
91
|
-
if request.method not in ("POST", "PUT"):
|
92
|
-
return self.app
|
93
|
-
|
94
125
|
try:
|
95
126
|
ver, account, container, obj = request.split_path(
|
96
127
|
2, 4, rest_with_last=True)
|
@@ -102,7 +133,7 @@ class AccountQuotaMiddleware(object):
|
|
102
133
|
# container or object request; even if the quota headers are set
|
103
134
|
# in the request, they're meaningless
|
104
135
|
|
105
|
-
if request.method == "
|
136
|
+
if not (request.method == "PUT" and obj):
|
106
137
|
return self.app
|
107
138
|
# OK, object PUT
|
108
139
|
|
@@ -110,6 +141,7 @@ class AccountQuotaMiddleware(object):
|
|
110
141
|
# but resellers aren't constrained by quotas :-)
|
111
142
|
return self.app
|
112
143
|
|
144
|
+
# Object PUT request
|
113
145
|
content_length = (request.content_length or 0)
|
114
146
|
|
115
147
|
account_info = get_account_info(request.environ, self.app,
|
@@ -119,24 +151,50 @@ class AccountQuotaMiddleware(object):
|
|
119
151
|
try:
|
120
152
|
quota = int(account_info['meta'].get('quota-bytes', -1))
|
121
153
|
except ValueError:
|
122
|
-
|
123
|
-
if quota
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
154
|
+
quota = -1
|
155
|
+
if quota >= 0:
|
156
|
+
new_size = int(account_info['bytes']) + content_length
|
157
|
+
if quota < new_size:
|
158
|
+
resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
|
159
|
+
if 'swift.authorize' in request.environ:
|
160
|
+
orig_authorize = request.environ['swift.authorize']
|
161
|
+
|
162
|
+
def reject_authorize(*args, **kwargs):
|
163
|
+
aresp = orig_authorize(*args, **kwargs)
|
164
|
+
if aresp:
|
165
|
+
return aresp
|
166
|
+
return resp
|
167
|
+
request.environ['swift.authorize'] = reject_authorize
|
168
|
+
else:
|
169
|
+
return resp
|
131
170
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
171
|
+
container_info = get_container_info(request.environ, self.app,
|
172
|
+
swift_source='AQ')
|
173
|
+
if not container_info:
|
174
|
+
return self.app
|
175
|
+
policy_idx = container_info['storage_policy']
|
176
|
+
sysmeta_key = 'quota-bytes-policy-%s' % policy_idx
|
177
|
+
try:
|
178
|
+
policy_quota = int(account_info['sysmeta'].get(sysmeta_key, -1))
|
179
|
+
except ValueError:
|
180
|
+
policy_quota = -1
|
181
|
+
if policy_quota >= 0:
|
182
|
+
policy_stats = account_info['storage_policies'].get(policy_idx, {})
|
183
|
+
new_size = int(policy_stats.get('bytes', 0)) + content_length
|
184
|
+
if policy_quota < new_size:
|
185
|
+
resp = HTTPRequestEntityTooLarge(
|
186
|
+
body='Upload exceeds policy quota.')
|
187
|
+
if 'swift.authorize' in request.environ:
|
188
|
+
orig_authorize = request.environ['swift.authorize']
|
189
|
+
|
190
|
+
def reject_authorize(*args, **kwargs):
|
191
|
+
aresp = orig_authorize(*args, **kwargs)
|
192
|
+
if aresp:
|
193
|
+
return aresp
|
194
|
+
return resp
|
195
|
+
request.environ['swift.authorize'] = reject_authorize
|
196
|
+
else:
|
136
197
|
return resp
|
137
|
-
request.environ['swift.authorize'] = reject_authorize
|
138
|
-
else:
|
139
|
-
return resp
|
140
198
|
|
141
199
|
return self.app
|
142
200
|
|
@@ -17,7 +17,8 @@ import time
|
|
17
17
|
from collections import defaultdict
|
18
18
|
|
19
19
|
from swift.common.request_helpers import split_and_validate_path
|
20
|
-
from swift.common.swob import Request, HTTPTooManyBackendRequests
|
20
|
+
from swift.common.swob import Request, HTTPTooManyBackendRequests, \
|
21
|
+
HTTPException
|
21
22
|
from swift.common.utils import get_logger, non_negative_float, \
|
22
23
|
EventletRateLimiter
|
23
24
|
|
@@ -66,13 +67,14 @@ class BackendRateLimitMiddleware(object):
|
|
66
67
|
try:
|
67
68
|
device, partition, _ = split_and_validate_path(req, 1, 3, True)
|
68
69
|
int(partition) # check it's a valid partition
|
70
|
+
except (ValueError, HTTPException):
|
71
|
+
# request may not have device/partition e.g. a healthcheck req
|
72
|
+
pass
|
73
|
+
else:
|
69
74
|
rate_limiter = self.rate_limiters[device]
|
70
75
|
if not rate_limiter.is_allowed():
|
71
76
|
self.logger.increment('backend.ratelimit')
|
72
77
|
handler = HTTPTooManyBackendRequests()
|
73
|
-
except Exception: # noqa
|
74
|
-
# request may not have device/partition e.g. a healthcheck req
|
75
|
-
pass
|
76
78
|
return handler(env, start_response)
|
77
79
|
|
78
80
|
|