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
swift/common/memcached.py CHANGED
@@ -44,19 +44,23 @@ version is at:
44
44
  http://github.com/memcached/memcached/blob/1.4.2/doc/protocol.txt
45
45
  """
46
46
 
47
- import six
48
- import six.moves.cPickle as pickle
47
+ import os
49
48
  import json
50
49
  import logging
51
- import time
50
+ # the name of 'time' module is changed to 'tm', to avoid changing the
51
+ # signatures of member functions in this file.
52
+ import time as tm
52
53
  from bisect import bisect
53
- from hashlib import md5
54
54
 
55
- from eventlet.green import socket
55
+ from eventlet.green import socket, ssl
56
56
  from eventlet.pools import Pool
57
57
  from eventlet import Timeout
58
- from six.moves import range
58
+ from configparser import ConfigParser, NoSectionError, NoOptionError
59
59
  from swift.common import utils
60
+ from swift.common.exceptions import MemcacheConnectionError, \
61
+ MemcacheIncrNotFoundError, MemcachePoolTimeout
62
+ from swift.common.utils import md5, human_readable, config_true_value, \
63
+ memcached_timing_stats
60
64
 
61
65
  DEFAULT_MEMCACHED_PORT = 11211
62
66
 
@@ -66,23 +70,27 @@ IO_TIMEOUT = 2.0
66
70
  PICKLE_FLAG = 1
67
71
  JSON_FLAG = 2
68
72
  NODE_WEIGHT = 50
69
- PICKLE_PROTOCOL = 2
70
73
  TRY_COUNT = 3
71
74
 
72
75
  # if ERROR_LIMIT_COUNT errors occur in ERROR_LIMIT_TIME seconds, the server
73
76
  # will be considered failed for ERROR_LIMIT_DURATION seconds.
74
77
  ERROR_LIMIT_COUNT = 10
75
- ERROR_LIMIT_TIME = 60
76
- ERROR_LIMIT_DURATION = 60
78
+ ERROR_LIMIT_TIME = ERROR_LIMIT_DURATION = 60
79
+ DEFAULT_ITEM_SIZE_WARNING_THRESHOLD = -1
80
+
81
+ # Different sample rates for emitting Memcached timing stats.
82
+ TIMING_SAMPLE_RATE_HIGH = 0.1
83
+ TIMING_SAMPLE_RATE_MEDIUM = 0.01
84
+ TIMING_SAMPLE_RATE_LOW = 0.001
85
+
86
+ # The max value of a delta expiration time.
87
+ EXPTIME_MAXDELTA = 30 * 24 * 60 * 60
77
88
 
78
89
 
79
90
  def md5hash(key):
80
91
  if not isinstance(key, bytes):
81
- if six.PY2:
82
- key = key.encode('utf-8')
83
- else:
84
- key = key.encode('utf-8', errors='surrogateescape')
85
- return md5(key).hexdigest().encode('ascii')
92
+ key = key.encode('utf-8', errors='surrogateescape')
93
+ return md5(key, usedforsecurity=False).hexdigest().encode('ascii')
86
94
 
87
95
 
88
96
  def sanitize_timeout(timeout):
@@ -92,8 +100,8 @@ def sanitize_timeout(timeout):
92
100
  translates negative values to mean a delta of 30 days in seconds (and 1
93
101
  additional second), client beware.
94
102
  """
95
- if timeout > (30 * 24 * 60 * 60):
96
- timeout += time.time()
103
+ if timeout > EXPTIME_MAXDELTA:
104
+ timeout += tm.time()
97
105
  return int(timeout)
98
106
 
99
107
 
@@ -111,14 +119,6 @@ def set_msg(key, flags, timeout, value):
111
119
  ]) + (b'\r\n' + value + b'\r\n')
112
120
 
113
121
 
114
- class MemcacheConnectionError(Exception):
115
- pass
116
-
117
-
118
- class MemcachePoolTimeout(Timeout):
119
- pass
120
-
121
-
122
122
  class MemcacheConnPool(Pool):
123
123
  """
124
124
  Connection pool for Memcache Connections
@@ -141,19 +141,54 @@ class MemcacheConnPool(Pool):
141
141
  family, socktype, proto, canonname, sockaddr = addrs[0]
142
142
  sock = socket.socket(family, socket.SOCK_STREAM)
143
143
  sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
144
- with Timeout(self._connect_timeout):
145
- sock.connect(sockaddr)
146
- if self._tls_context:
147
- sock = self._tls_context.wrap_socket(sock,
148
- server_hostname=self.host)
144
+ try:
145
+ with Timeout(self._connect_timeout):
146
+ sock.connect(sockaddr)
147
+ if self._tls_context:
148
+ sock = self._tls_context.wrap_socket(sock,
149
+ server_hostname=self.host)
150
+ except (Exception, Timeout):
151
+ sock.close()
152
+ raise
149
153
  return (sock.makefile('rwb'), sock)
150
154
 
151
155
  def get(self):
152
156
  fp, sock = super(MemcacheConnPool, self).get()
153
- if fp is None:
154
- # An error happened previously, so we need a new connection
155
- fp, sock = self.create()
156
- return fp, sock
157
+ try:
158
+ if fp is None:
159
+ # An error happened previously, so we need a new connection
160
+ fp, sock = self.create()
161
+ return fp, sock
162
+ except MemcachePoolTimeout:
163
+ # This is the only place that knows an item was successfully taken
164
+ # from the pool, so it has to be responsible for repopulating it.
165
+ # Any other errors should get handled in _get_conns(); see the
166
+ # comment about timeouts during create() there.
167
+ self.put((None, None))
168
+ raise
169
+
170
+
171
+ class MemcacheCommand(object):
172
+ """
173
+ Helper class that encapsulates common parameters of a command.
174
+
175
+ :param method: the name of the MemcacheRing method that was called.
176
+ :param key: the memcached key.
177
+ """
178
+ __slots__ = ('method', 'key', 'command', 'hash_key')
179
+
180
+ def __init__(self, method, key):
181
+ self.method = method
182
+ self.key = key
183
+ self.command = method.encode()
184
+ self.hash_key = md5hash(key)
185
+
186
+ @property
187
+ def key_prefix(self):
188
+ # get the prefix of a user provided memcache key by removing the
189
+ # content after the last '/', all current usages within swift are using
190
+ # prefix, such as "shard-updating-v2", "nvratelimit" and etc.
191
+ return self.key.rsplit('/', 1)[0]
157
192
 
158
193
 
159
194
  class MemcacheRing(object):
@@ -161,13 +196,21 @@ class MemcacheRing(object):
161
196
  Simple, consistent-hashed memcache client.
162
197
  """
163
198
 
164
- def __init__(self, servers, connect_timeout=CONN_TIMEOUT,
165
- io_timeout=IO_TIMEOUT, pool_timeout=POOL_TIMEOUT,
166
- tries=TRY_COUNT, allow_pickle=False, allow_unpickle=False,
167
- max_conns=2, tls_context=None):
199
+ def __init__(
200
+ self, servers, connect_timeout=CONN_TIMEOUT,
201
+ io_timeout=IO_TIMEOUT, pool_timeout=POOL_TIMEOUT,
202
+ tries=TRY_COUNT,
203
+ max_conns=2, tls_context=None, logger=None,
204
+ error_limit_count=ERROR_LIMIT_COUNT,
205
+ error_limit_time=ERROR_LIMIT_TIME,
206
+ error_limit_duration=ERROR_LIMIT_DURATION,
207
+ item_size_warning_threshold=DEFAULT_ITEM_SIZE_WARNING_THRESHOLD):
168
208
  self._ring = {}
169
209
  self._errors = dict(((serv, []) for serv in servers))
170
210
  self._error_limited = dict(((serv, 0) for serv in servers))
211
+ self._error_limit_count = error_limit_count
212
+ self._error_limit_time = error_limit_time
213
+ self._error_limit_duration = error_limit_duration
171
214
  for server in sorted(servers):
172
215
  for i in range(NODE_WEIGHT):
173
216
  self._ring[md5hash('%s-%s' % (server, i))] = server
@@ -180,20 +223,73 @@ class MemcacheRing(object):
180
223
  self._connect_timeout = connect_timeout
181
224
  self._io_timeout = io_timeout
182
225
  self._pool_timeout = pool_timeout
183
- self._allow_pickle = allow_pickle
184
- self._allow_unpickle = allow_unpickle or allow_pickle
226
+ if logger is None:
227
+ self.logger = logging.getLogger()
228
+ else:
229
+ self.logger = logger
230
+ self.item_size_warning_threshold = item_size_warning_threshold
185
231
 
186
- def _exception_occurred(self, server, e, action='talking',
187
- sock=None, fp=None, got_connection=True):
232
+ @property
233
+ def memcache_servers(self):
234
+ return list(self._client_cache.keys())
235
+
236
+ def _log_error(self, server, cmd, action, msg):
237
+ self.logger.error(
238
+ "Error %(action)s to memcached: %(server)s"
239
+ ": with key_prefix %(key_prefix)s, method %(method)s: %(msg)s",
240
+ {'action': action, 'server': server, 'key_prefix': cmd.key_prefix,
241
+ 'method': cmd.method, 'msg': msg})
242
+
243
+ """
244
+ Handles exceptions.
245
+
246
+ :param server: a server.
247
+ :param e: an exception.
248
+ :param cmd: an instance of MemcacheCommand.
249
+ :param conn_start_time: the time at which the failed operation started.
250
+ :param action: a verb describing the operation.
251
+ :param sock: an optional socket that needs to be closed by this method.
252
+ :param fp: an optional file pointer that needs to be closed by this method.
253
+ :param got_connection: if ``True``, the server's connection will be reset
254
+ in the cached connection pool.
255
+ """
256
+ def _exception_occurred(self, server, e, cmd, conn_start_time,
257
+ action='talking', sock=None,
258
+ fp=None, got_connection=True):
188
259
  if isinstance(e, Timeout):
189
- logging.error("Timeout %(action)s to memcached: %(server)s",
190
- {'action': action, 'server': server})
260
+ self.logger.error(
261
+ "Timeout %(action)s to memcached: %(server)s"
262
+ ": with key_prefix %(key_prefix)s, method %(method)s, "
263
+ "config_timeout %(config_timeout)s, time_spent %(time_spent)s",
264
+ {'action': action, 'server': server,
265
+ 'key_prefix': cmd.key_prefix, 'method': cmd.method,
266
+ 'config_timeout': e.seconds,
267
+ 'time_spent': tm.time() - conn_start_time})
268
+ self.logger.timing_since(
269
+ 'memcached.' + cmd.method + '.timeout.timing',
270
+ conn_start_time)
191
271
  elif isinstance(e, (socket.error, MemcacheConnectionError)):
192
- logging.error("Error %(action)s to memcached: %(server)s: %(err)s",
193
- {'action': action, 'server': server, 'err': e})
272
+ self.logger.error(
273
+ "Error %(action)s to memcached: %(server)s: "
274
+ "with key_prefix %(key_prefix)s, method %(method)s, "
275
+ "time_spent %(time_spent)s, %(err)s",
276
+ {'action': action, 'server': server,
277
+ 'key_prefix': cmd.key_prefix, 'method': cmd.method,
278
+ 'time_spent': tm.time() - conn_start_time, 'err': e})
279
+ self.logger.timing_since(
280
+ 'memcached.' + cmd.method + '.conn_err.timing',
281
+ conn_start_time)
194
282
  else:
195
- logging.exception("Error %(action)s to memcached: %(server)s",
196
- {'action': action, 'server': server})
283
+ self.logger.exception(
284
+ "Error %(action)s to memcached: %(server)s"
285
+ ": with key_prefix %(key_prefix)s, method %(method)s, "
286
+ "time_spent %(time_spent)s",
287
+ {'action': action, 'server': server,
288
+ 'key_prefix': cmd.key_prefix, 'method': cmd.method,
289
+ 'time_spent': tm.time() - conn_start_time})
290
+ self.logger.timing_since(
291
+ 'memcached.' + cmd.method + '.errors.timing', conn_start_time)
292
+
197
293
  try:
198
294
  if fp:
199
295
  fp.close()
@@ -210,73 +306,93 @@ class MemcacheRing(object):
210
306
  # We need to return something to the pool
211
307
  # A new connection will be created the next time it is retrieved
212
308
  self._return_conn(server, None, None)
213
- now = time.time()
214
- self._errors[server].append(time.time())
215
- if len(self._errors[server]) > ERROR_LIMIT_COUNT:
309
+
310
+ if isinstance(e, MemcacheIncrNotFoundError):
311
+ # these errors can be caused by other greenthreads not yielding to
312
+ # the incr greenthread often enough, rather than a server problem,
313
+ # so don't error limit the server
314
+ return
315
+
316
+ if self._error_limit_time <= 0 or self._error_limit_duration <= 0:
317
+ return
318
+
319
+ now = tm.time()
320
+ self._errors[server].append(now)
321
+ if len(self._errors[server]) > self._error_limit_count:
216
322
  self._errors[server] = [err for err in self._errors[server]
217
- if err > now - ERROR_LIMIT_TIME]
218
- if len(self._errors[server]) > ERROR_LIMIT_COUNT:
219
- self._error_limited[server] = now + ERROR_LIMIT_DURATION
220
- logging.error('Error limiting server %s', server)
323
+ if err > now - self._error_limit_time]
324
+ if len(self._errors[server]) > self._error_limit_count:
325
+ self._error_limited[server] = now + self._error_limit_duration
326
+ self.logger.error('Error limiting server %s', server)
221
327
 
222
- def _get_conns(self, key):
328
+ def _get_conns(self, cmd):
223
329
  """
224
330
  Retrieves a server conn from the pool, or connects a new one.
225
331
  Chooses the server based on a consistent hash of "key".
332
+
333
+ :param cmd: an instance of MemcacheCommand.
334
+ :return: generator to serve memcached connection
226
335
  """
227
- pos = bisect(self._sorted, key)
336
+ pos = bisect(self._sorted, cmd.hash_key)
228
337
  served = []
338
+ any_yielded = False
229
339
  while len(served) < self._tries:
230
340
  pos = (pos + 1) % len(self._sorted)
231
341
  server = self._ring[self._sorted[pos]]
232
342
  if server in served:
233
343
  continue
234
344
  served.append(server)
235
- if self._error_limited[server] > time.time():
345
+ pool_start_time = tm.time()
346
+ if self._error_limited[server] > pool_start_time:
236
347
  continue
237
348
  sock = None
238
349
  try:
239
350
  with MemcachePoolTimeout(self._pool_timeout):
240
351
  fp, sock = self._client_cache[server].get()
352
+ any_yielded = True
241
353
  yield server, fp, sock
242
354
  except MemcachePoolTimeout as e:
243
- self._exception_occurred(
244
- server, e, action='getting a connection',
245
- got_connection=False)
355
+ self._exception_occurred(server, e, cmd, pool_start_time,
356
+ action='getting a connection',
357
+ got_connection=False)
246
358
  except (Exception, Timeout) as e:
247
359
  # Typically a Timeout exception caught here is the one raised
248
360
  # by the create() method of this server's MemcacheConnPool
249
361
  # object.
250
- self._exception_occurred(
251
- server, e, action='connecting', sock=sock)
362
+ self._exception_occurred(server, e, cmd, pool_start_time,
363
+ action='connecting', sock=sock)
364
+ if not any_yielded:
365
+ self._log_error('ALL', cmd, 'connecting',
366
+ 'No more memcached servers to try')
252
367
 
253
368
  def _return_conn(self, server, fp, sock):
254
369
  """Returns a server connection to the pool."""
255
370
  self._client_cache[server].put((fp, sock))
256
371
 
372
+ # Sample rates of different memcached operations are based on generic
373
+ # swift usage patterns.
374
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_HIGH)
257
375
  def set(self, key, value, serialize=True, time=0,
258
- min_compress_len=0):
376
+ min_compress_len=0, raise_on_error=False):
259
377
  """
260
378
  Set a key/value pair in memcache
261
379
 
262
380
  :param key: key
263
381
  :param value: value
264
382
  :param serialize: if True, value is serialized with JSON before sending
265
- to memcache, or with pickle if configured to use
266
- pickle instead of JSON (to avoid cache poisoning)
383
+ to memcache
267
384
  :param time: the time to live
268
385
  :param min_compress_len: minimum compress length, this parameter was
269
386
  added to keep the signature compatible with
270
387
  python-memcached interface. This
271
388
  implementation ignores it.
389
+ :param raise_on_error: if True, propagate Timeouts and other errors.
390
+ By default, errors are ignored.
272
391
  """
273
- key = md5hash(key)
392
+ cmd = MemcacheCommand('set', key)
274
393
  timeout = sanitize_timeout(time)
275
394
  flags = 0
276
- if serialize and self._allow_pickle:
277
- value = pickle.dumps(value, PICKLE_PROTOCOL)
278
- flags |= PICKLE_FLAG
279
- elif serialize:
395
+ if serialize:
280
396
  if isinstance(value, bytes):
281
397
  value = value.decode('utf8')
282
398
  value = json.dumps(value).encode('ascii')
@@ -284,55 +400,94 @@ class MemcacheRing(object):
284
400
  elif not isinstance(value, bytes):
285
401
  value = str(value).encode('utf-8')
286
402
 
287
- for (server, fp, sock) in self._get_conns(key):
403
+ if 0 <= self.item_size_warning_threshold <= len(value):
404
+ self.logger.warning(
405
+ "Item size larger than warning threshold: "
406
+ "%d (%s) >= %d (%s)", len(value),
407
+ human_readable(len(value)),
408
+ self.item_size_warning_threshold,
409
+ human_readable(self.item_size_warning_threshold))
410
+
411
+ for (server, fp, sock) in self._get_conns(cmd):
412
+ conn_start_time = tm.time()
288
413
  try:
289
414
  with Timeout(self._io_timeout):
290
- sock.sendall(set_msg(key, flags, timeout, value))
415
+ sock.sendall(set_msg(cmd.hash_key, flags, timeout, value))
291
416
  # Wait for the set to complete
292
- fp.readline()
417
+ msg = fp.readline().strip()
418
+ if msg != b'STORED':
419
+ msg = msg.decode('ascii')
420
+ raise MemcacheConnectionError('failed set: %s' % msg)
293
421
  self._return_conn(server, fp, sock)
294
422
  return
295
423
  except (Exception, Timeout) as e:
296
- self._exception_occurred(server, e, sock=sock, fp=fp)
297
-
298
- def get(self, key):
424
+ self._exception_occurred(server, e, cmd, conn_start_time,
425
+ sock=sock, fp=fp)
426
+ if raise_on_error:
427
+ raise MemcacheConnectionError(
428
+ "No memcached connections succeeded.")
429
+
430
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_MEDIUM)
431
+ def get(self, key, raise_on_error=False):
299
432
  """
300
433
  Gets the object specified by key. It will also unserialize the object
301
- before returning if it is serialized in memcache with JSON, or if it
302
- is pickled and unpickling is allowed.
434
+ before returning if it is serialized in memcache with JSON.
303
435
 
304
436
  :param key: key
437
+ :param raise_on_error: if True, propagate Timeouts and other errors.
438
+ By default, errors are treated as cache misses.
305
439
  :returns: value of the key in memcache
306
440
  """
307
- key = md5hash(key)
441
+ cmd = MemcacheCommand('get', key)
308
442
  value = None
309
- for (server, fp, sock) in self._get_conns(key):
443
+ for (server, fp, sock) in self._get_conns(cmd):
444
+ conn_start_time = tm.time()
310
445
  try:
311
446
  with Timeout(self._io_timeout):
312
- sock.sendall(b'get ' + key + b'\r\n')
447
+ sock.sendall(b'get ' + cmd.hash_key + b'\r\n')
313
448
  line = fp.readline().strip().split()
314
449
  while True:
315
450
  if not line:
316
451
  raise MemcacheConnectionError('incomplete read')
317
452
  if line[0].upper() == b'END':
318
453
  break
319
- if line[0].upper() == b'VALUE' and line[1] == key:
454
+ if (line[0].upper() == b'VALUE' and
455
+ line[1] == cmd.hash_key):
320
456
  size = int(line[3])
321
457
  value = fp.read(size)
322
458
  if int(line[2]) & PICKLE_FLAG:
323
- if self._allow_unpickle:
324
- value = pickle.loads(value)
325
- else:
326
- value = None
327
- elif int(line[2]) & JSON_FLAG:
459
+ value = None
460
+ if int(line[2]) & JSON_FLAG:
328
461
  value = json.loads(value)
329
462
  fp.readline()
330
463
  line = fp.readline().strip().split()
331
464
  self._return_conn(server, fp, sock)
332
465
  return value
333
466
  except (Exception, Timeout) as e:
334
- self._exception_occurred(server, e, sock=sock, fp=fp)
335
-
467
+ self._exception_occurred(server, e, cmd, conn_start_time,
468
+ sock=sock, fp=fp)
469
+ if raise_on_error:
470
+ raise MemcacheConnectionError(
471
+ "No memcached connections succeeded.")
472
+
473
+ def _incr_or_decr(self, fp, sock, cmd, delta):
474
+ sock.sendall(b' '.join([cmd.command, cmd.hash_key, delta]) + b'\r\n')
475
+ line = fp.readline().strip().split()
476
+ if not line:
477
+ raise MemcacheConnectionError('incomplete read')
478
+ if line[0].upper() == b'NOT_FOUND':
479
+ return None
480
+ return int(line[0].strip())
481
+
482
+ def _add(self, fp, sock, cmd, add_val, timeout):
483
+ sock.sendall(b' '.join([
484
+ b'add', cmd.hash_key, b'0', str(timeout).encode('ascii'),
485
+ str(len(add_val)).encode('ascii')
486
+ ]) + b'\r\n' + add_val + b'\r\n')
487
+ line = fp.readline().strip().split()
488
+ return None if line[0].upper() == b'NOT_STORED' else int(add_val)
489
+
490
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_LOW)
336
491
  def incr(self, key, delta=1, time=0):
337
492
  """
338
493
  Increments a key which has a numeric value by delta.
@@ -350,44 +505,35 @@ class MemcacheRing(object):
350
505
  :returns: result of incrementing
351
506
  :raises MemcacheConnectionError:
352
507
  """
353
- key = md5hash(key)
354
- command = b'incr'
355
- if delta < 0:
356
- command = b'decr'
357
- delta = str(abs(int(delta))).encode('ascii')
508
+ cmd = MemcacheCommand('incr' if delta >= 0 else 'decr', key)
509
+ delta_val = str(abs(int(delta))).encode('ascii')
358
510
  timeout = sanitize_timeout(time)
359
- for (server, fp, sock) in self._get_conns(key):
511
+ for (server, fp, sock) in self._get_conns(cmd):
512
+ conn_start_time = tm.time()
360
513
  try:
361
514
  with Timeout(self._io_timeout):
362
- sock.sendall(b' '.join([
363
- command, key, delta]) + b'\r\n')
364
- line = fp.readline().strip().split()
365
- if not line:
366
- raise MemcacheConnectionError('incomplete read')
367
- if line[0].upper() == b'NOT_FOUND':
368
- add_val = delta
369
- if command == b'decr':
370
- add_val = b'0'
371
- sock.sendall(b' '.join([
372
- b'add', key, b'0', str(timeout).encode('ascii'),
373
- str(len(add_val)).encode('ascii')
374
- ]) + b'\r\n' + add_val + b'\r\n')
375
- line = fp.readline().strip().split()
376
- if line[0].upper() == b'NOT_STORED':
377
- sock.sendall(b' '.join([
378
- command, key, delta]) + b'\r\n')
379
- line = fp.readline().strip().split()
380
- ret = int(line[0].strip())
381
- else:
382
- ret = int(add_val)
383
- else:
384
- ret = int(line[0].strip())
515
+ new_val = self._incr_or_decr(fp, sock, cmd, delta_val)
516
+ if new_val is None:
517
+ add_val = b'0' if cmd.method == 'decr' else delta_val
518
+ new_val = self._add(fp, sock, cmd, add_val, timeout)
519
+ if new_val is None:
520
+ new_val = self._incr_or_decr(
521
+ fp, sock, cmd, delta_val)
522
+ if new_val is None:
523
+ # This can happen if this thread takes more
524
+ # than the TTL to get from the first failed
525
+ # incr to the second incr, during which time
526
+ # the key was concurrently added and expired.
527
+ raise MemcacheIncrNotFoundError(
528
+ 'expired ttl=%s' % time)
385
529
  self._return_conn(server, fp, sock)
386
- return ret
530
+ return new_val
387
531
  except (Exception, Timeout) as e:
388
- self._exception_occurred(server, e, sock=sock, fp=fp)
389
- raise MemcacheConnectionError("No Memcached connections succeeded.")
532
+ self._exception_occurred(server, e, cmd, conn_start_time,
533
+ sock=sock, fp=fp)
534
+ raise MemcacheConnectionError("No memcached connections succeeded.")
390
535
 
536
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_LOW)
391
537
  def decr(self, key, delta=1, time=0):
392
538
  """
393
539
  Decrements a key which has a numeric value by delta. Calls incr with
@@ -403,24 +549,32 @@ class MemcacheRing(object):
403
549
  """
404
550
  return self.incr(key, delta=-delta, time=time)
405
551
 
406
- def delete(self, key):
552
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_HIGH)
553
+ def delete(self, key, server_key=None):
407
554
  """
408
555
  Deletes a key/value pair from memcache.
409
556
 
410
557
  :param key: key to be deleted
558
+ :param server_key: key to use in determining which server in the ring
559
+ is used
411
560
  """
412
- key = md5hash(key)
413
- for (server, fp, sock) in self._get_conns(key):
561
+ cmd = server_cmd = MemcacheCommand('delete', key)
562
+ if server_key:
563
+ server_cmd = MemcacheCommand('delete', server_key)
564
+ for (server, fp, sock) in self._get_conns(server_cmd):
565
+ conn_start_time = tm.time()
414
566
  try:
415
567
  with Timeout(self._io_timeout):
416
- sock.sendall(b'delete ' + key + b'\r\n')
568
+ sock.sendall(b'delete ' + cmd.hash_key + b'\r\n')
417
569
  # Wait for the delete to complete
418
570
  fp.readline()
419
571
  self._return_conn(server, fp, sock)
420
572
  return
421
573
  except (Exception, Timeout) as e:
422
- self._exception_occurred(server, e, sock=sock, fp=fp)
574
+ self._exception_occurred(server, e, cmd, conn_start_time,
575
+ sock=sock, fp=fp)
423
576
 
577
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_HIGH)
424
578
  def set_multi(self, mapping, server_key, serialize=True, time=0,
425
579
  min_compress_len=0):
426
580
  """
@@ -430,30 +584,27 @@ class MemcacheRing(object):
430
584
  :param server_key: key to use in determining which server in the ring
431
585
  is used
432
586
  :param serialize: if True, value is serialized with JSON before sending
433
- to memcache, or with pickle if configured to use
434
- pickle instead of JSON (to avoid cache poisoning)
587
+ to memcache.
435
588
  :param time: the time to live
436
589
  :min_compress_len: minimum compress length, this parameter was added
437
590
  to keep the signature compatible with
438
591
  python-memcached interface. This implementation
439
592
  ignores it
440
593
  """
441
- server_key = md5hash(server_key)
594
+ cmd = MemcacheCommand('set_multi', server_key)
442
595
  timeout = sanitize_timeout(time)
443
596
  msg = []
444
597
  for key, value in mapping.items():
445
598
  key = md5hash(key)
446
599
  flags = 0
447
- if serialize and self._allow_pickle:
448
- value = pickle.dumps(value, PICKLE_PROTOCOL)
449
- flags |= PICKLE_FLAG
450
- elif serialize:
600
+ if serialize:
451
601
  if isinstance(value, bytes):
452
602
  value = value.decode('utf8')
453
603
  value = json.dumps(value).encode('ascii')
454
604
  flags |= JSON_FLAG
455
605
  msg.append(set_msg(key, flags, timeout, value))
456
- for (server, fp, sock) in self._get_conns(server_key):
606
+ for (server, fp, sock) in self._get_conns(cmd):
607
+ conn_start_time = tm.time()
457
608
  try:
458
609
  with Timeout(self._io_timeout):
459
610
  sock.sendall(b''.join(msg))
@@ -463,8 +614,10 @@ class MemcacheRing(object):
463
614
  self._return_conn(server, fp, sock)
464
615
  return
465
616
  except (Exception, Timeout) as e:
466
- self._exception_occurred(server, e, sock=sock, fp=fp)
617
+ self._exception_occurred(server, e, cmd, conn_start_time,
618
+ sock=sock, fp=fp)
467
619
 
620
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_HIGH)
468
621
  def get_multi(self, keys, server_key):
469
622
  """
470
623
  Gets multiple values from memcache for the given keys.
@@ -474,12 +627,13 @@ class MemcacheRing(object):
474
627
  is used
475
628
  :returns: list of values
476
629
  """
477
- server_key = md5hash(server_key)
478
- keys = [md5hash(key) for key in keys]
479
- for (server, fp, sock) in self._get_conns(server_key):
630
+ cmd = MemcacheCommand('get_multi', server_key)
631
+ hash_keys = [md5hash(key) for key in keys]
632
+ for (server, fp, sock) in self._get_conns(cmd):
633
+ conn_start_time = tm.time()
480
634
  try:
481
635
  with Timeout(self._io_timeout):
482
- sock.sendall(b'get ' + b' '.join(keys) + b'\r\n')
636
+ sock.sendall(b'get ' + b' '.join(hash_keys) + b'\r\n')
483
637
  line = fp.readline().strip().split()
484
638
  responses = {}
485
639
  while True:
@@ -491,17 +645,14 @@ class MemcacheRing(object):
491
645
  size = int(line[3])
492
646
  value = fp.read(size)
493
647
  if int(line[2]) & PICKLE_FLAG:
494
- if self._allow_unpickle:
495
- value = pickle.loads(value)
496
- else:
497
- value = None
648
+ value = None
498
649
  elif int(line[2]) & JSON_FLAG:
499
650
  value = json.loads(value)
500
651
  responses[line[1]] = value
501
652
  fp.readline()
502
653
  line = fp.readline().strip().split()
503
654
  values = []
504
- for key in keys:
655
+ for key in hash_keys:
505
656
  if key in responses:
506
657
  values.append(responses[key])
507
658
  else:
@@ -509,4 +660,98 @@ class MemcacheRing(object):
509
660
  self._return_conn(server, fp, sock)
510
661
  return values
511
662
  except (Exception, Timeout) as e:
512
- self._exception_occurred(server, e, sock=sock, fp=fp)
663
+ self._exception_occurred(server, e, cmd, conn_start_time,
664
+ sock=sock, fp=fp)
665
+
666
+
667
+ def load_memcache(conf, logger):
668
+ """
669
+ Build a MemcacheRing object from the given config. It will also use the
670
+ passed in logger.
671
+
672
+ :param conf: a dict, the config options
673
+ :param logger: a logger
674
+ """
675
+ memcache_servers = conf.get('memcache_servers')
676
+ try:
677
+ # Originally, while we documented using memcache_max_connections
678
+ # we only accepted max_connections
679
+ max_conns = int(conf.get('memcache_max_connections',
680
+ conf.get('max_connections', 0)))
681
+ except ValueError:
682
+ max_conns = 0
683
+
684
+ memcache_options = {}
685
+ if (not memcache_servers
686
+ or max_conns <= 0):
687
+ path = os.path.join(conf.get('swift_dir', '/etc/swift'),
688
+ 'memcache.conf')
689
+ memcache_conf = ConfigParser()
690
+ if memcache_conf.read(path):
691
+ # if memcache.conf exists we'll start with those base options
692
+ try:
693
+ memcache_options = dict(memcache_conf.items('memcache'))
694
+ except NoSectionError:
695
+ pass
696
+
697
+ if not memcache_servers:
698
+ try:
699
+ memcache_servers = \
700
+ memcache_conf.get('memcache', 'memcache_servers')
701
+ except (NoSectionError, NoOptionError):
702
+ pass
703
+ if max_conns <= 0:
704
+ try:
705
+ new_max_conns = \
706
+ memcache_conf.get('memcache',
707
+ 'memcache_max_connections')
708
+ max_conns = int(new_max_conns)
709
+ except (NoSectionError, NoOptionError, ValueError):
710
+ pass
711
+
712
+ # while memcache.conf options are the base for the memcache
713
+ # middleware, if you set the same option also in the filter
714
+ # section of the proxy config it is more specific.
715
+ memcache_options.update(conf)
716
+ connect_timeout = float(memcache_options.get(
717
+ 'connect_timeout', CONN_TIMEOUT))
718
+ pool_timeout = float(memcache_options.get(
719
+ 'pool_timeout', POOL_TIMEOUT))
720
+ tries = int(memcache_options.get('tries', TRY_COUNT))
721
+ io_timeout = float(memcache_options.get('io_timeout', IO_TIMEOUT))
722
+ if config_true_value(memcache_options.get('tls_enabled', 'false')):
723
+ tls_cafile = memcache_options.get('tls_cafile')
724
+ tls_certfile = memcache_options.get('tls_certfile')
725
+ tls_keyfile = memcache_options.get('tls_keyfile')
726
+ tls_context = ssl.create_default_context(
727
+ cafile=tls_cafile)
728
+ if tls_certfile:
729
+ tls_context.load_cert_chain(tls_certfile, tls_keyfile)
730
+ else:
731
+ tls_context = None
732
+ error_suppression_interval = float(memcache_options.get(
733
+ 'error_suppression_interval', ERROR_LIMIT_TIME))
734
+ error_suppression_limit = float(memcache_options.get(
735
+ 'error_suppression_limit', ERROR_LIMIT_COUNT))
736
+ item_size_warning_threshold = int(memcache_options.get(
737
+ 'item_size_warning_threshold', DEFAULT_ITEM_SIZE_WARNING_THRESHOLD))
738
+
739
+ if not memcache_servers:
740
+ memcache_servers = '127.0.0.1:11211'
741
+ if max_conns <= 0:
742
+ max_conns = 2
743
+
744
+ return MemcacheRing(
745
+ [s.strip() for s in memcache_servers.split(',')
746
+ if s.strip()],
747
+ connect_timeout=connect_timeout,
748
+ pool_timeout=pool_timeout,
749
+ tries=tries,
750
+ io_timeout=io_timeout,
751
+ max_conns=max_conns,
752
+ tls_context=tls_context,
753
+ logger=logger,
754
+ error_limit_count=error_suppression_limit,
755
+ error_limit_time=error_suppression_interval,
756
+ error_limit_duration=error_suppression_interval,
757
+ item_size_warning_threshold=item_size_warning_threshold)