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.
Files changed (104) hide show
  1. swift/cli/info.py +9 -2
  2. swift/cli/ringbuilder.py +5 -1
  3. swift/common/container_sync_realms.py +6 -7
  4. swift/common/daemon.py +7 -3
  5. swift/common/db.py +22 -7
  6. swift/common/db_replicator.py +19 -20
  7. swift/common/direct_client.py +63 -14
  8. swift/common/internal_client.py +24 -3
  9. swift/common/manager.py +43 -44
  10. swift/common/memcached.py +168 -74
  11. swift/common/middleware/__init__.py +4 -0
  12. swift/common/middleware/account_quotas.py +98 -40
  13. swift/common/middleware/backend_ratelimit.py +6 -4
  14. swift/common/middleware/crossdomain.py +21 -8
  15. swift/common/middleware/listing_formats.py +26 -38
  16. swift/common/middleware/proxy_logging.py +12 -9
  17. swift/common/middleware/s3api/controllers/bucket.py +8 -2
  18. swift/common/middleware/s3api/s3api.py +9 -4
  19. swift/common/middleware/s3api/s3request.py +32 -24
  20. swift/common/middleware/s3api/s3response.py +10 -1
  21. swift/common/middleware/tempauth.py +9 -10
  22. swift/common/middleware/versioned_writes/__init__.py +0 -3
  23. swift/common/middleware/versioned_writes/object_versioning.py +22 -5
  24. swift/common/middleware/x_profile/html_viewer.py +1 -1
  25. swift/common/middleware/xprofile.py +5 -0
  26. swift/common/request_helpers.py +1 -2
  27. swift/common/ring/ring.py +22 -19
  28. swift/common/swob.py +2 -1
  29. swift/common/{utils.py → utils/__init__.py} +610 -1146
  30. swift/common/utils/ipaddrs.py +256 -0
  31. swift/common/utils/libc.py +345 -0
  32. swift/common/utils/timestamp.py +399 -0
  33. swift/common/wsgi.py +70 -39
  34. swift/container/backend.py +106 -38
  35. swift/container/server.py +11 -2
  36. swift/container/sharder.py +34 -15
  37. swift/locale/de/LC_MESSAGES/swift.po +1 -320
  38. swift/locale/en_GB/LC_MESSAGES/swift.po +1 -347
  39. swift/locale/es/LC_MESSAGES/swift.po +1 -279
  40. swift/locale/fr/LC_MESSAGES/swift.po +1 -209
  41. swift/locale/it/LC_MESSAGES/swift.po +1 -207
  42. swift/locale/ja/LC_MESSAGES/swift.po +2 -278
  43. swift/locale/ko_KR/LC_MESSAGES/swift.po +3 -303
  44. swift/locale/pt_BR/LC_MESSAGES/swift.po +1 -204
  45. swift/locale/ru/LC_MESSAGES/swift.po +1 -203
  46. swift/locale/tr_TR/LC_MESSAGES/swift.po +1 -192
  47. swift/locale/zh_CN/LC_MESSAGES/swift.po +1 -192
  48. swift/locale/zh_TW/LC_MESSAGES/swift.po +1 -193
  49. swift/obj/diskfile.py +19 -6
  50. swift/obj/server.py +20 -6
  51. swift/obj/ssync_receiver.py +19 -9
  52. swift/obj/ssync_sender.py +10 -10
  53. swift/proxy/controllers/account.py +7 -7
  54. swift/proxy/controllers/base.py +374 -366
  55. swift/proxy/controllers/container.py +112 -53
  56. swift/proxy/controllers/obj.py +254 -390
  57. swift/proxy/server.py +3 -8
  58. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-server +1 -1
  59. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-server +1 -1
  60. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-drive-audit +45 -14
  61. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-server +1 -1
  62. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-proxy-server +1 -1
  63. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/AUTHORS +4 -0
  64. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/METADATA +32 -35
  65. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/RECORD +103 -100
  66. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/WHEEL +1 -1
  67. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/entry_points.txt +0 -1
  68. swift-2.32.1.dist-info/pbr.json +1 -0
  69. swift-2.31.1.dist-info/pbr.json +0 -1
  70. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-audit +0 -0
  71. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-auditor +0 -0
  72. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-info +0 -0
  73. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-reaper +0 -0
  74. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-replicator +0 -0
  75. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-config +0 -0
  76. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-auditor +0 -0
  77. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-info +0 -0
  78. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-reconciler +0 -0
  79. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-replicator +0 -0
  80. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-sharder +0 -0
  81. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-sync +0 -0
  82. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-updater +0 -0
  83. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-dispersion-populate +0 -0
  84. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-dispersion-report +0 -0
  85. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-form-signature +0 -0
  86. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-get-nodes +0 -0
  87. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-init +0 -0
  88. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-auditor +0 -0
  89. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-expirer +0 -0
  90. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-info +0 -0
  91. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-reconstructor +0 -0
  92. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-relinker +0 -0
  93. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-replicator +0 -0
  94. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-updater +0 -0
  95. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-oldies +0 -0
  96. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-orphans +0 -0
  97. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-recon +0 -0
  98. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-recon-cron +0 -0
  99. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-reconciler-enqueue +0 -0
  100. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-builder +0 -0
  101. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-builder-analyzer +0 -0
  102. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-composer +0 -0
  103. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/LICENSE +0 -0
  104. {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
- import time
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 += time.time()
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
- def _exception_occurred(self, server, e, action='talking',
220
- sock=None, fp=None, got_connection=True):
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("Timeout %(action)s to memcached: %(server)s",
223
- {'action': action, 'server': server})
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: %(err)s",
227
- {'action': action, 'server': server, 'err': e})
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("Error %(action)s to memcached: %(server)s",
230
- {'action': action, 'server': server})
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 = time.time()
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, key):
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, key)
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
- if self._error_limited[server] > time.time():
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
- server, e, action='getting a connection',
287
- got_connection=False)
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
- server, e, action='connecting', sock=sock)
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
- key = md5hash(key)
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(key):
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(key, flags, timeout, value))
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, sock=sock, fp=fp)
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
- key = md5hash(key)
448
+ cmd = MemcacheCommand('get', key)
372
449
  value = None
373
- for (server, fp, sock) in self._get_conns(key):
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 ' + key + b'\r\n')
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 line[1] == key:
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, sock=sock, fp=fp)
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
- key = md5hash(key)
419
- command = b'incr'
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(key):
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
- sock.sendall(b' '.join([
428
- command, key, delta]) + b'\r\n')
429
- line = fp.readline().strip().split()
430
- if not line:
431
- raise MemcacheConnectionError('incomplete read')
432
- if line[0].upper() == b'NOT_FOUND':
433
- add_val = delta
434
- if command == b'decr':
435
- add_val = b'0'
436
- sock.sendall(b' '.join([
437
- b'add', key, b'0', str(timeout).encode('ascii'),
438
- str(len(add_val)).encode('ascii')
439
- ]) + b'\r\n' + add_val + b'\r\n')
440
- line = fp.readline().strip().split()
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 ret
537
+ return new_val
452
538
  except (Exception, Timeout) as e:
453
- self._exception_occurred(server, e, sock=sock, fp=fp)
454
- raise MemcacheConnectionError("No Memcached connections succeeded.")
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
- key = md5hash(key)
482
- server_key = md5hash(server_key) if server_key else key
483
- for (server, fp, sock) in self._get_conns(server_key):
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 ' + key + b'\r\n')
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, sock=sock, fp=fp)
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
- server_key = md5hash(server_key)
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(server_key):
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, sock=sock, fp=fp)
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
- server_key = md5hash(server_key)
546
- keys = [md5hash(key) for key in keys]
547
- for (server, fp, sock) in self._get_conns(server_key):
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(keys) + b'\r\n')
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 keys:
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, sock=sock, fp=fp)
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):
@@ -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
 
@@ -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 only permitted for
23
- resellers. There is no quota limit if ``x-account-meta-quota-bytes`` is not
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.proxy.controllers.base import get_account_info
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
- # account request, so we pay attention to the quotas
72
- new_quota = request.headers.get(
73
- 'X-Account-Meta-Quota-Bytes')
74
- if request.headers.get(
75
- 'X-Remove-Account-Meta-Quota-Bytes'):
76
- new_quota = 0 # X-Remove dominates if both are present
77
-
78
- if request.environ.get('reseller_request') is True:
79
- if new_quota and not new_quota.isdigit():
80
- return HTTPBadRequest()
81
- return self.app
82
-
83
- # deny quota set for non-reseller
84
- if new_quota is not None:
85
- return HTTPForbidden()
86
- return self.app
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 == "POST" or not obj:
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
- return self.app
123
- if quota < 0:
124
- return self.app
125
-
126
- new_size = int(account_info['bytes']) + content_length
127
- if quota < new_size:
128
- resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
129
- if 'swift.authorize' in request.environ:
130
- orig_authorize = request.environ['swift.authorize']
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
- def reject_authorize(*args, **kwargs):
133
- aresp = orig_authorize(*args, **kwargs)
134
- if aresp:
135
- return aresp
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