swift 2.23.2__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 (208) 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.2.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.2.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.2.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.2.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.2.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +183 -29
  17. swift/cli/manage_shard_ranges.py +708 -37
  18. swift-2.23.2.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.2.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.2.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.2.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 +198 -127
  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 +396 -147
  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 -81
  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 +52 -19
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +192 -174
  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.2.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} +2191 -2762
  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 +555 -536
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +552 -227
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +397 -176
  135. swift/container/sharder.py +1580 -639
  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 +213 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +653 -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 +452 -86
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1009 -490
  156. swift/proxy/server.py +185 -112
  157. swift-2.35.0.dist-info/AUTHORS +501 -0
  158. swift-2.35.0.dist-info/LICENSE +202 -0
  159. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/METADATA +52 -61
  160. swift-2.35.0.dist-info/RECORD +201 -0
  161. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  162. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  163. swift-2.35.0.dist-info/pbr.json +1 -0
  164. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  165. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  166. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  167. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  168. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  169. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  170. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  171. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  172. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  173. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  174. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  175. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  176. swift-2.23.2.data/scripts/swift-account-auditor +0 -23
  177. swift-2.23.2.data/scripts/swift-account-info +0 -51
  178. swift-2.23.2.data/scripts/swift-account-reaper +0 -23
  179. swift-2.23.2.data/scripts/swift-account-replicator +0 -34
  180. swift-2.23.2.data/scripts/swift-account-server +0 -23
  181. swift-2.23.2.data/scripts/swift-container-auditor +0 -23
  182. swift-2.23.2.data/scripts/swift-container-info +0 -51
  183. swift-2.23.2.data/scripts/swift-container-reconciler +0 -21
  184. swift-2.23.2.data/scripts/swift-container-replicator +0 -34
  185. swift-2.23.2.data/scripts/swift-container-sharder +0 -33
  186. swift-2.23.2.data/scripts/swift-container-sync +0 -23
  187. swift-2.23.2.data/scripts/swift-container-updater +0 -23
  188. swift-2.23.2.data/scripts/swift-dispersion-report +0 -24
  189. swift-2.23.2.data/scripts/swift-form-signature +0 -20
  190. swift-2.23.2.data/scripts/swift-init +0 -119
  191. swift-2.23.2.data/scripts/swift-object-auditor +0 -29
  192. swift-2.23.2.data/scripts/swift-object-expirer +0 -33
  193. swift-2.23.2.data/scripts/swift-object-info +0 -60
  194. swift-2.23.2.data/scripts/swift-object-reconstructor +0 -33
  195. swift-2.23.2.data/scripts/swift-object-relinker +0 -41
  196. swift-2.23.2.data/scripts/swift-object-replicator +0 -37
  197. swift-2.23.2.data/scripts/swift-object-server +0 -27
  198. swift-2.23.2.data/scripts/swift-object-updater +0 -23
  199. swift-2.23.2.data/scripts/swift-proxy-server +0 -23
  200. swift-2.23.2.data/scripts/swift-recon +0 -24
  201. swift-2.23.2.data/scripts/swift-ring-builder +0 -24
  202. swift-2.23.2.data/scripts/swift-ring-builder-analyzer +0 -22
  203. swift-2.23.2.data/scripts/swift-ring-composer +0 -22
  204. swift-2.23.2.dist-info/DESCRIPTION.rst +0 -166
  205. swift-2.23.2.dist-info/RECORD +0 -220
  206. swift-2.23.2.dist-info/metadata.json +0 -1
  207. swift-2.23.2.dist-info/pbr.json +0 -1
  208. {swift-2.23.2.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
@@ -128,11 +128,12 @@ class MemcacheConnPool(Pool):
128
128
  :func:`swift.common.utils.parse_socket_string` for details.
129
129
  """
130
130
 
131
- def __init__(self, server, size, connect_timeout):
131
+ def __init__(self, server, size, connect_timeout, tls_context=None):
132
132
  Pool.__init__(self, max_size=size)
133
133
  self.host, self.port = utils.parse_socket_string(
134
134
  server, DEFAULT_MEMCACHED_PORT)
135
135
  self._connect_timeout = connect_timeout
136
+ self._tls_context = tls_context
136
137
 
137
138
  def create(self):
138
139
  addrs = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC,
@@ -140,16 +141,54 @@ class MemcacheConnPool(Pool):
140
141
  family, socktype, proto, canonname, sockaddr = addrs[0]
141
142
  sock = socket.socket(family, socket.SOCK_STREAM)
142
143
  sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
143
- with Timeout(self._connect_timeout):
144
- sock.connect(sockaddr)
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
145
153
  return (sock.makefile('rwb'), sock)
146
154
 
147
155
  def get(self):
148
156
  fp, sock = super(MemcacheConnPool, self).get()
149
- if fp is None:
150
- # An error happened previously, so we need a new connection
151
- fp, sock = self.create()
152
- 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]
153
192
 
154
193
 
155
194
  class MemcacheRing(object):
@@ -157,39 +196,100 @@ class MemcacheRing(object):
157
196
  Simple, consistent-hashed memcache client.
158
197
  """
159
198
 
160
- def __init__(self, servers, connect_timeout=CONN_TIMEOUT,
161
- io_timeout=IO_TIMEOUT, pool_timeout=POOL_TIMEOUT,
162
- tries=TRY_COUNT, allow_pickle=False, allow_unpickle=False,
163
- max_conns=2):
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):
164
208
  self._ring = {}
165
209
  self._errors = dict(((serv, []) for serv in servers))
166
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
167
214
  for server in sorted(servers):
168
215
  for i in range(NODE_WEIGHT):
169
216
  self._ring[md5hash('%s-%s' % (server, i))] = server
170
217
  self._tries = tries if tries <= len(servers) else len(servers)
171
218
  self._sorted = sorted(self._ring)
172
- self._client_cache = dict(((server,
173
- MemcacheConnPool(server, max_conns,
174
- connect_timeout))
175
- for server in servers))
219
+ self._client_cache = dict((
220
+ (server, MemcacheConnPool(server, max_conns, connect_timeout,
221
+ tls_context=tls_context))
222
+ for server in servers))
176
223
  self._connect_timeout = connect_timeout
177
224
  self._io_timeout = io_timeout
178
225
  self._pool_timeout = pool_timeout
179
- self._allow_pickle = allow_pickle
180
- 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
181
231
 
182
- def _exception_occurred(self, server, e, action='talking',
183
- 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):
184
259
  if isinstance(e, Timeout):
185
- logging.error("Timeout %(action)s to memcached: %(server)s",
186
- {'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)
187
271
  elif isinstance(e, (socket.error, MemcacheConnectionError)):
188
- logging.error("Error %(action)s to memcached: %(server)s: %(err)s",
189
- {'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)
190
282
  else:
191
- logging.exception("Error %(action)s to memcached: %(server)s",
192
- {'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
+
193
293
  try:
194
294
  if fp:
195
295
  fp.close()
@@ -206,73 +306,93 @@ class MemcacheRing(object):
206
306
  # We need to return something to the pool
207
307
  # A new connection will be created the next time it is retrieved
208
308
  self._return_conn(server, None, None)
209
- now = time.time()
210
- self._errors[server].append(time.time())
211
- 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:
212
322
  self._errors[server] = [err for err in self._errors[server]
213
- if err > now - ERROR_LIMIT_TIME]
214
- if len(self._errors[server]) > ERROR_LIMIT_COUNT:
215
- self._error_limited[server] = now + ERROR_LIMIT_DURATION
216
- 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)
217
327
 
218
- def _get_conns(self, key):
328
+ def _get_conns(self, cmd):
219
329
  """
220
330
  Retrieves a server conn from the pool, or connects a new one.
221
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
222
335
  """
223
- pos = bisect(self._sorted, key)
336
+ pos = bisect(self._sorted, cmd.hash_key)
224
337
  served = []
338
+ any_yielded = False
225
339
  while len(served) < self._tries:
226
340
  pos = (pos + 1) % len(self._sorted)
227
341
  server = self._ring[self._sorted[pos]]
228
342
  if server in served:
229
343
  continue
230
344
  served.append(server)
231
- if self._error_limited[server] > time.time():
345
+ pool_start_time = tm.time()
346
+ if self._error_limited[server] > pool_start_time:
232
347
  continue
233
348
  sock = None
234
349
  try:
235
350
  with MemcachePoolTimeout(self._pool_timeout):
236
351
  fp, sock = self._client_cache[server].get()
352
+ any_yielded = True
237
353
  yield server, fp, sock
238
354
  except MemcachePoolTimeout as e:
239
- self._exception_occurred(
240
- server, e, action='getting a connection',
241
- got_connection=False)
355
+ self._exception_occurred(server, e, cmd, pool_start_time,
356
+ action='getting a connection',
357
+ got_connection=False)
242
358
  except (Exception, Timeout) as e:
243
359
  # Typically a Timeout exception caught here is the one raised
244
360
  # by the create() method of this server's MemcacheConnPool
245
361
  # object.
246
- self._exception_occurred(
247
- 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')
248
367
 
249
368
  def _return_conn(self, server, fp, sock):
250
369
  """Returns a server connection to the pool."""
251
370
  self._client_cache[server].put((fp, sock))
252
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)
253
375
  def set(self, key, value, serialize=True, time=0,
254
- min_compress_len=0):
376
+ min_compress_len=0, raise_on_error=False):
255
377
  """
256
378
  Set a key/value pair in memcache
257
379
 
258
380
  :param key: key
259
381
  :param value: value
260
382
  :param serialize: if True, value is serialized with JSON before sending
261
- to memcache, or with pickle if configured to use
262
- pickle instead of JSON (to avoid cache poisoning)
383
+ to memcache
263
384
  :param time: the time to live
264
385
  :param min_compress_len: minimum compress length, this parameter was
265
386
  added to keep the signature compatible with
266
387
  python-memcached interface. This
267
388
  implementation ignores it.
389
+ :param raise_on_error: if True, propagate Timeouts and other errors.
390
+ By default, errors are ignored.
268
391
  """
269
- key = md5hash(key)
392
+ cmd = MemcacheCommand('set', key)
270
393
  timeout = sanitize_timeout(time)
271
394
  flags = 0
272
- if serialize and self._allow_pickle:
273
- value = pickle.dumps(value, PICKLE_PROTOCOL)
274
- flags |= PICKLE_FLAG
275
- elif serialize:
395
+ if serialize:
276
396
  if isinstance(value, bytes):
277
397
  value = value.decode('utf8')
278
398
  value = json.dumps(value).encode('ascii')
@@ -280,55 +400,94 @@ class MemcacheRing(object):
280
400
  elif not isinstance(value, bytes):
281
401
  value = str(value).encode('utf-8')
282
402
 
283
- 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()
284
413
  try:
285
414
  with Timeout(self._io_timeout):
286
- sock.sendall(set_msg(key, flags, timeout, value))
415
+ sock.sendall(set_msg(cmd.hash_key, flags, timeout, value))
287
416
  # Wait for the set to complete
288
- fp.readline()
417
+ msg = fp.readline().strip()
418
+ if msg != b'STORED':
419
+ msg = msg.decode('ascii')
420
+ raise MemcacheConnectionError('failed set: %s' % msg)
289
421
  self._return_conn(server, fp, sock)
290
422
  return
291
423
  except (Exception, Timeout) as e:
292
- self._exception_occurred(server, e, sock=sock, fp=fp)
293
-
294
- 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):
295
432
  """
296
433
  Gets the object specified by key. It will also unserialize the object
297
- before returning if it is serialized in memcache with JSON, or if it
298
- is pickled and unpickling is allowed.
434
+ before returning if it is serialized in memcache with JSON.
299
435
 
300
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.
301
439
  :returns: value of the key in memcache
302
440
  """
303
- key = md5hash(key)
441
+ cmd = MemcacheCommand('get', key)
304
442
  value = None
305
- 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()
306
445
  try:
307
446
  with Timeout(self._io_timeout):
308
- sock.sendall(b'get ' + key + b'\r\n')
447
+ sock.sendall(b'get ' + cmd.hash_key + b'\r\n')
309
448
  line = fp.readline().strip().split()
310
449
  while True:
311
450
  if not line:
312
451
  raise MemcacheConnectionError('incomplete read')
313
452
  if line[0].upper() == b'END':
314
453
  break
315
- if line[0].upper() == b'VALUE' and line[1] == key:
454
+ if (line[0].upper() == b'VALUE' and
455
+ line[1] == cmd.hash_key):
316
456
  size = int(line[3])
317
457
  value = fp.read(size)
318
458
  if int(line[2]) & PICKLE_FLAG:
319
- if self._allow_unpickle:
320
- value = pickle.loads(value)
321
- else:
322
- value = None
323
- elif int(line[2]) & JSON_FLAG:
459
+ value = None
460
+ if int(line[2]) & JSON_FLAG:
324
461
  value = json.loads(value)
325
462
  fp.readline()
326
463
  line = fp.readline().strip().split()
327
464
  self._return_conn(server, fp, sock)
328
465
  return value
329
466
  except (Exception, Timeout) as e:
330
- self._exception_occurred(server, e, sock=sock, fp=fp)
331
-
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)
332
491
  def incr(self, key, delta=1, time=0):
333
492
  """
334
493
  Increments a key which has a numeric value by delta.
@@ -346,44 +505,35 @@ class MemcacheRing(object):
346
505
  :returns: result of incrementing
347
506
  :raises MemcacheConnectionError:
348
507
  """
349
- key = md5hash(key)
350
- command = b'incr'
351
- if delta < 0:
352
- command = b'decr'
353
- 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')
354
510
  timeout = sanitize_timeout(time)
355
- 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()
356
513
  try:
357
514
  with Timeout(self._io_timeout):
358
- sock.sendall(b' '.join([
359
- command, key, delta]) + b'\r\n')
360
- line = fp.readline().strip().split()
361
- if not line:
362
- raise MemcacheConnectionError('incomplete read')
363
- if line[0].upper() == b'NOT_FOUND':
364
- add_val = delta
365
- if command == b'decr':
366
- add_val = b'0'
367
- sock.sendall(b' '.join([
368
- b'add', key, b'0', str(timeout).encode('ascii'),
369
- str(len(add_val)).encode('ascii')
370
- ]) + b'\r\n' + add_val + b'\r\n')
371
- line = fp.readline().strip().split()
372
- if line[0].upper() == b'NOT_STORED':
373
- sock.sendall(b' '.join([
374
- command, key, delta]) + b'\r\n')
375
- line = fp.readline().strip().split()
376
- ret = int(line[0].strip())
377
- else:
378
- ret = int(add_val)
379
- else:
380
- 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)
381
529
  self._return_conn(server, fp, sock)
382
- return ret
530
+ return new_val
383
531
  except (Exception, Timeout) as e:
384
- self._exception_occurred(server, e, sock=sock, fp=fp)
385
- 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.")
386
535
 
536
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_LOW)
387
537
  def decr(self, key, delta=1, time=0):
388
538
  """
389
539
  Decrements a key which has a numeric value by delta. Calls incr with
@@ -399,24 +549,32 @@ class MemcacheRing(object):
399
549
  """
400
550
  return self.incr(key, delta=-delta, time=time)
401
551
 
402
- def delete(self, key):
552
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_HIGH)
553
+ def delete(self, key, server_key=None):
403
554
  """
404
555
  Deletes a key/value pair from memcache.
405
556
 
406
557
  :param key: key to be deleted
558
+ :param server_key: key to use in determining which server in the ring
559
+ is used
407
560
  """
408
- key = md5hash(key)
409
- 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()
410
566
  try:
411
567
  with Timeout(self._io_timeout):
412
- sock.sendall(b'delete ' + key + b'\r\n')
568
+ sock.sendall(b'delete ' + cmd.hash_key + b'\r\n')
413
569
  # Wait for the delete to complete
414
570
  fp.readline()
415
571
  self._return_conn(server, fp, sock)
416
572
  return
417
573
  except (Exception, Timeout) as e:
418
- self._exception_occurred(server, e, sock=sock, fp=fp)
574
+ self._exception_occurred(server, e, cmd, conn_start_time,
575
+ sock=sock, fp=fp)
419
576
 
577
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_HIGH)
420
578
  def set_multi(self, mapping, server_key, serialize=True, time=0,
421
579
  min_compress_len=0):
422
580
  """
@@ -426,30 +584,27 @@ class MemcacheRing(object):
426
584
  :param server_key: key to use in determining which server in the ring
427
585
  is used
428
586
  :param serialize: if True, value is serialized with JSON before sending
429
- to memcache, or with pickle if configured to use
430
- pickle instead of JSON (to avoid cache poisoning)
587
+ to memcache.
431
588
  :param time: the time to live
432
589
  :min_compress_len: minimum compress length, this parameter was added
433
590
  to keep the signature compatible with
434
591
  python-memcached interface. This implementation
435
592
  ignores it
436
593
  """
437
- server_key = md5hash(server_key)
594
+ cmd = MemcacheCommand('set_multi', server_key)
438
595
  timeout = sanitize_timeout(time)
439
596
  msg = []
440
597
  for key, value in mapping.items():
441
598
  key = md5hash(key)
442
599
  flags = 0
443
- if serialize and self._allow_pickle:
444
- value = pickle.dumps(value, PICKLE_PROTOCOL)
445
- flags |= PICKLE_FLAG
446
- elif serialize:
600
+ if serialize:
447
601
  if isinstance(value, bytes):
448
602
  value = value.decode('utf8')
449
603
  value = json.dumps(value).encode('ascii')
450
604
  flags |= JSON_FLAG
451
605
  msg.append(set_msg(key, flags, timeout, value))
452
- 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()
453
608
  try:
454
609
  with Timeout(self._io_timeout):
455
610
  sock.sendall(b''.join(msg))
@@ -459,8 +614,10 @@ class MemcacheRing(object):
459
614
  self._return_conn(server, fp, sock)
460
615
  return
461
616
  except (Exception, Timeout) as e:
462
- self._exception_occurred(server, e, sock=sock, fp=fp)
617
+ self._exception_occurred(server, e, cmd, conn_start_time,
618
+ sock=sock, fp=fp)
463
619
 
620
+ @memcached_timing_stats(sample_rate=TIMING_SAMPLE_RATE_HIGH)
464
621
  def get_multi(self, keys, server_key):
465
622
  """
466
623
  Gets multiple values from memcache for the given keys.
@@ -470,12 +627,13 @@ class MemcacheRing(object):
470
627
  is used
471
628
  :returns: list of values
472
629
  """
473
- server_key = md5hash(server_key)
474
- keys = [md5hash(key) for key in keys]
475
- 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()
476
634
  try:
477
635
  with Timeout(self._io_timeout):
478
- sock.sendall(b'get ' + b' '.join(keys) + b'\r\n')
636
+ sock.sendall(b'get ' + b' '.join(hash_keys) + b'\r\n')
479
637
  line = fp.readline().strip().split()
480
638
  responses = {}
481
639
  while True:
@@ -487,17 +645,14 @@ class MemcacheRing(object):
487
645
  size = int(line[3])
488
646
  value = fp.read(size)
489
647
  if int(line[2]) & PICKLE_FLAG:
490
- if self._allow_unpickle:
491
- value = pickle.loads(value)
492
- else:
493
- value = None
648
+ value = None
494
649
  elif int(line[2]) & JSON_FLAG:
495
650
  value = json.loads(value)
496
651
  responses[line[1]] = value
497
652
  fp.readline()
498
653
  line = fp.readline().strip().split()
499
654
  values = []
500
- for key in keys:
655
+ for key in hash_keys:
501
656
  if key in responses:
502
657
  values.append(responses[key])
503
658
  else:
@@ -505,4 +660,98 @@ class MemcacheRing(object):
505
660
  self._return_conn(server, fp, sock)
506
661
  return values
507
662
  except (Exception, Timeout) as e:
508
- 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)