test_client_hash.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. from library.pymemcache.client.hash import HashClient
  2. from library.pymemcache.client.base import Client, PooledClient
  3. from library.pymemcache.exceptions import MemcacheError, MemcacheUnknownError
  4. from library.pymemcache import pool
  5. from .test_client import ClientTestMixin, MockSocket
  6. import unittest
  7. import os
  8. import pytest
  9. import mock
  10. import socket
  11. class TestHashClient(ClientTestMixin, unittest.TestCase):
  12. def make_client_pool(self, hostname, mock_socket_values,
  13. serializer=None, **kwargs):
  14. mock_client = Client(hostname, serializer=serializer, **kwargs)
  15. mock_client.sock = MockSocket(mock_socket_values)
  16. client = PooledClient(hostname, serializer=serializer)
  17. client.client_pool = pool.ObjectPool(lambda: mock_client)
  18. return mock_client
  19. def make_client(self, *mock_socket_values, **kwargs):
  20. current_port = 11012
  21. client = HashClient([], **kwargs)
  22. ip = '127.0.0.1'
  23. for vals in mock_socket_values:
  24. s = '%s:%s' % (ip, current_port)
  25. c = self.make_client_pool(
  26. (ip, current_port),
  27. vals,
  28. **kwargs
  29. )
  30. client.clients[s] = c
  31. client.hasher.add_node(s)
  32. current_port += 1
  33. return client
  34. def make_unix_client(self, sockets, *mock_socket_values, **kwargs):
  35. client = HashClient([], **kwargs)
  36. for socket_, vals in zip(sockets, mock_socket_values):
  37. c = self.make_client_pool(
  38. socket_,
  39. vals,
  40. **kwargs
  41. )
  42. client.clients[socket_] = c
  43. client.hasher.add_node(socket_)
  44. return client
  45. def test_setup_client_without_pooling(self):
  46. client_class = 'pymemcache.client.hash.HashClient.client_class'
  47. with mock.patch(client_class) as internal_client:
  48. client = HashClient([], timeout=999, key_prefix='foo_bar_baz')
  49. client.add_server(('127.0.0.1', '11211'))
  50. assert internal_client.call_args[0][0] == ('127.0.0.1', '11211')
  51. kwargs = internal_client.call_args[1]
  52. assert kwargs['timeout'] == 999
  53. assert kwargs['key_prefix'] == 'foo_bar_baz'
  54. def test_get_many_unix(self):
  55. pid = os.getpid()
  56. sockets = [
  57. '/tmp/pymemcache.1.%d' % pid,
  58. '/tmp/pymemcache.2.%d' % pid,
  59. ]
  60. client = self.make_unix_client(sockets, *[
  61. [b'STORED\r\n', b'VALUE key3 0 6\r\nvalue2\r\nEND\r\n', ],
  62. [b'STORED\r\n', b'VALUE key1 0 6\r\nvalue1\r\nEND\r\n', ],
  63. ])
  64. def get_clients(key):
  65. if key == b'key3':
  66. return client.clients['/tmp/pymemcache.1.%d' % pid]
  67. else:
  68. return client.clients['/tmp/pymemcache.2.%d' % pid]
  69. client._get_client = get_clients
  70. result = client.set(b'key1', b'value1', noreply=False)
  71. result = client.set(b'key3', b'value2', noreply=False)
  72. result = client.get_many([b'key1', b'key3'])
  73. assert result == {b'key1': b'value1', b'key3': b'value2'}
  74. def test_get_many_all_found(self):
  75. client = self.make_client(*[
  76. [b'STORED\r\n', b'VALUE key3 0 6\r\nvalue2\r\nEND\r\n', ],
  77. [b'STORED\r\n', b'VALUE key1 0 6\r\nvalue1\r\nEND\r\n', ],
  78. ])
  79. def get_clients(key):
  80. if key == b'key3':
  81. return client.clients['127.0.0.1:11012']
  82. else:
  83. return client.clients['127.0.0.1:11013']
  84. client._get_client = get_clients
  85. result = client.set(b'key1', b'value1', noreply=False)
  86. result = client.set(b'key3', b'value2', noreply=False)
  87. result = client.get_many([b'key1', b'key3'])
  88. assert result == {b'key1': b'value1', b'key3': b'value2'}
  89. def test_get_many_some_found(self):
  90. client = self.make_client(*[
  91. [b'END\r\n', ],
  92. [b'STORED\r\n', b'VALUE key1 0 6\r\nvalue1\r\nEND\r\n', ],
  93. ])
  94. def get_clients(key):
  95. if key == b'key3':
  96. return client.clients['127.0.0.1:11012']
  97. else:
  98. return client.clients['127.0.0.1:11013']
  99. client._get_client = get_clients
  100. result = client.set(b'key1', b'value1', noreply=False)
  101. result = client.get_many([b'key1', b'key3'])
  102. assert result == {b'key1': b'value1'}
  103. def test_get_many_bad_server_data(self):
  104. client = self.make_client(*[
  105. [b'STORED\r\n', b'VAXLUE key3 0 6\r\nvalue2\r\nEND\r\n', ],
  106. [b'STORED\r\n', b'VAXLUE key1 0 6\r\nvalue1\r\nEND\r\n', ],
  107. ])
  108. def get_clients(key):
  109. if key == b'key3':
  110. return client.clients['127.0.0.1:11012']
  111. else:
  112. return client.clients['127.0.0.1:11013']
  113. client._get_client = get_clients
  114. with pytest.raises(MemcacheUnknownError):
  115. client.set(b'key1', b'value1', noreply=False)
  116. client.set(b'key3', b'value2', noreply=False)
  117. client.get_many([b'key1', b'key3'])
  118. def test_get_many_bad_server_data_ignore(self):
  119. client = self.make_client(*[
  120. [b'STORED\r\n', b'VAXLUE key3 0 6\r\nvalue2\r\nEND\r\n', ],
  121. [b'STORED\r\n', b'VAXLUE key1 0 6\r\nvalue1\r\nEND\r\n', ],
  122. ], ignore_exc=True)
  123. def get_clients(key):
  124. if key == b'key3':
  125. return client.clients['127.0.0.1:11012']
  126. else:
  127. return client.clients['127.0.0.1:11013']
  128. client._get_client = get_clients
  129. client.set(b'key1', b'value1', noreply=False)
  130. client.set(b'key3', b'value2', noreply=False)
  131. result = client.get_many([b'key1', b'key3'])
  132. assert result == {}
  133. def test_gets_many(self):
  134. client = self.make_client(*[
  135. [b'STORED\r\n', b'VALUE key3 0 6 1\r\nvalue2\r\nEND\r\n', ],
  136. [b'STORED\r\n', b'VALUE key1 0 6 1\r\nvalue1\r\nEND\r\n', ],
  137. ])
  138. def get_clients(key):
  139. if key == b'key3':
  140. return client.clients['127.0.0.1:11012']
  141. else:
  142. return client.clients['127.0.0.1:11013']
  143. client._get_client = get_clients
  144. assert client.set(b'key1', b'value1', noreply=False) is True
  145. assert client.set(b'key3', b'value2', noreply=False) is True
  146. result = client.gets_many([b'key1', b'key3'])
  147. assert (result ==
  148. {b'key1': (b'value1', b'1'), b'key3': (b'value2', b'1')})
  149. def test_touch_not_found(self):
  150. client = self.make_client([b'NOT_FOUND\r\n'])
  151. result = client.touch(b'key', noreply=False)
  152. assert result is False
  153. def test_touch_no_expiry_found(self):
  154. client = self.make_client([b'TOUCHED\r\n'])
  155. result = client.touch(b'key', noreply=False)
  156. assert result is True
  157. def test_touch_with_expiry_found(self):
  158. client = self.make_client([b'TOUCHED\r\n'])
  159. result = client.touch(b'key', 1, noreply=False)
  160. assert result is True
  161. def test_close(self):
  162. client = self.make_client([])
  163. assert all(c.sock is not None for c in client.clients.values())
  164. result = client.close()
  165. assert result is None
  166. assert all(c.sock is None for c in client.clients.values())
  167. def test_quit(self):
  168. client = self.make_client([])
  169. assert all(c.sock is not None for c in client.clients.values())
  170. result = client.quit()
  171. assert result is None
  172. assert all(c.sock is None for c in client.clients.values())
  173. def test_no_servers_left(self):
  174. from library.pymemcache.client.hash import HashClient
  175. client = HashClient(
  176. [], use_pooling=True,
  177. ignore_exc=True,
  178. timeout=1, connect_timeout=1
  179. )
  180. hashed_client = client._get_client('foo')
  181. assert hashed_client is None
  182. def test_no_servers_left_raise_exception(self):
  183. from library.pymemcache.client.hash import HashClient
  184. client = HashClient(
  185. [], use_pooling=True,
  186. ignore_exc=False,
  187. timeout=1, connect_timeout=1
  188. )
  189. with pytest.raises(MemcacheError) as e:
  190. client._get_client('foo')
  191. assert str(e.value) == 'All servers seem to be down right now'
  192. def test_unavailable_servers_zero_retry_raise_exception(self):
  193. from library.pymemcache.client.hash import HashClient
  194. client = HashClient(
  195. [('example.com', 11211)], use_pooling=True,
  196. ignore_exc=False,
  197. retry_attempts=0, timeout=1, connect_timeout=1
  198. )
  199. with pytest.raises(socket.error):
  200. client.get('foo')
  201. def test_no_servers_left_with_commands_return_default_value(self):
  202. from library.pymemcache.client.hash import HashClient
  203. client = HashClient(
  204. [], use_pooling=True,
  205. ignore_exc=True,
  206. timeout=1, connect_timeout=1
  207. )
  208. result = client.get('foo')
  209. assert result is None
  210. result = client.set('foo', 'bar')
  211. assert result is False
  212. def test_no_servers_left_with_set_many(self):
  213. from library.pymemcache.client.hash import HashClient
  214. client = HashClient(
  215. [], use_pooling=True,
  216. ignore_exc=True,
  217. timeout=1, connect_timeout=1
  218. )
  219. result = client.set_many({'foo': 'bar'})
  220. assert result == ['foo']
  221. def test_no_servers_left_with_get_many(self):
  222. from library.pymemcache.client.hash import HashClient
  223. client = HashClient(
  224. [], use_pooling=True,
  225. ignore_exc=True,
  226. timeout=1, connect_timeout=1
  227. )
  228. result = client.get_many(['foo', 'bar'])
  229. assert result == {}
  230. def test_ignore_exec_set_many(self):
  231. values = {
  232. 'key1': 'value1',
  233. 'key2': 'value2',
  234. 'key3': 'value3'
  235. }
  236. with pytest.raises(MemcacheUnknownError):
  237. client = self.make_client(*[
  238. [b'STORED\r\n', b'UNKNOWN\r\n', b'STORED\r\n'],
  239. [b'STORED\r\n', b'UNKNOWN\r\n', b'STORED\r\n'],
  240. ])
  241. client.set_many(values, noreply=False)
  242. client = self.make_client(*[
  243. [b'STORED\r\n', b'UNKNOWN\r\n', b'STORED\r\n'],
  244. ], ignore_exc=True)
  245. result = client.set_many(values, noreply=False)
  246. assert len(result) == 0
  247. def test_noreply_set_many(self):
  248. values = {
  249. 'key1': 'value1',
  250. 'key2': 'value2',
  251. 'key3': 'value3'
  252. }
  253. client = self.make_client(*[
  254. [b'STORED\r\n', b'NOT_STORED\r\n', b'STORED\r\n'],
  255. ])
  256. result = client.set_many(values, noreply=False)
  257. assert len(result) == 1
  258. client = self.make_client(*[
  259. [b'STORED\r\n', b'NOT_STORED\r\n', b'STORED\r\n'],
  260. ])
  261. result = client.set_many(values, noreply=True)
  262. assert result == []
  263. def test_set_many_unix(self):
  264. values = {
  265. 'key1': 'value1',
  266. 'key2': 'value2',
  267. 'key3': 'value3'
  268. }
  269. pid = os.getpid()
  270. sockets = ['/tmp/pymemcache.%d' % pid]
  271. client = self.make_unix_client(sockets, *[
  272. [b'STORED\r\n', b'NOT_STORED\r\n', b'STORED\r\n'],
  273. ])
  274. result = client.set_many(values, noreply=False)
  275. assert len(result) == 1
  276. def test_server_encoding_pooled(self):
  277. """
  278. test passed encoding from hash client to pooled clients
  279. """
  280. encoding = 'utf8'
  281. from library.pymemcache.client.hash import HashClient
  282. hash_client = HashClient(
  283. [('example.com', 11211)], use_pooling=True,
  284. encoding=encoding
  285. )
  286. for client in hash_client.clients.values():
  287. assert client.encoding == encoding
  288. def test_server_encoding_client(self):
  289. """
  290. test passed encoding from hash client to clients
  291. """
  292. encoding = 'utf8'
  293. from library.pymemcache.client.hash import HashClient
  294. hash_client = HashClient(
  295. [('example.com', 11211)], encoding=encoding
  296. )
  297. for client in hash_client.clients.values():
  298. assert client.encoding == encoding
  299. @mock.patch("pymemcache.client.hash.HashClient.client_class")
  300. def test_dead_server_comes_back(self, client_patch):
  301. client = HashClient([], dead_timeout=0, retry_attempts=0)
  302. client.add_server(("127.0.0.1", 11211))
  303. test_client = client_patch.return_value
  304. test_client.server = ("127.0.0.1", 11211)
  305. test_client.get.side_effect = socket.timeout()
  306. with pytest.raises(socket.timeout):
  307. client.get(b"key", noreply=False)
  308. # Client gets removed because of socket timeout
  309. assert ("127.0.0.1", 11211) in client._dead_clients
  310. test_client.get.side_effect = lambda *_: "Some value"
  311. # Client should be retried and brought back
  312. assert client.get(b"key") == "Some value"
  313. assert ("127.0.0.1", 11211) not in client._dead_clients
  314. @mock.patch("pymemcache.client.hash.HashClient.client_class")
  315. def test_failed_is_retried(self, client_patch):
  316. client = HashClient([], retry_attempts=1, retry_timeout=0)
  317. client.add_server(("127.0.0.1", 11211))
  318. assert client_patch.call_count == 1
  319. test_client = client_patch.return_value
  320. test_client.server = ("127.0.0.1", 11211)
  321. test_client.get.side_effect = socket.timeout()
  322. with pytest.raises(socket.timeout):
  323. client.get(b"key", noreply=False)
  324. test_client.get.side_effect = lambda *_: "Some value"
  325. assert client.get(b"key") == "Some value"
  326. assert client_patch.call_count == 1
  327. def test_custom_client(self):
  328. class MyClient(Client):
  329. pass
  330. client = HashClient([])
  331. client.client_class = MyClient
  332. client.add_server(('host', 11211))
  333. assert isinstance(client.clients['host:11211'], MyClient)
  334. def test_custom_client_with_pooling(self):
  335. class MyClient(Client):
  336. pass
  337. client = HashClient([], use_pooling=True)
  338. client.client_class = MyClient
  339. client.add_server(('host', 11211))
  340. assert isinstance(client.clients['host:11211'], PooledClient)
  341. pool = client.clients['host:11211'].client_pool
  342. with pool.get_and_release(destroy_on_fail=True) as c:
  343. assert isinstance(c, MyClient)
  344. def test_mixed_inet_and_unix_sockets(self):
  345. expected = {
  346. '/tmp/pymemcache.{pid}'.format(pid=os.getpid()),
  347. ('127.0.0.1', 11211),
  348. ('::1', 11211),
  349. }
  350. client = HashClient([
  351. '/tmp/pymemcache.{pid}'.format(pid=os.getpid()),
  352. '127.0.0.1',
  353. '127.0.0.1:11211',
  354. '[::1]',
  355. '[::1]:11211',
  356. ('127.0.0.1', 11211),
  357. ('::1', 11211),
  358. ])
  359. assert expected == {c.server for c in client.clients.values()}
  360. def test_legacy_add_remove_server_signature(self):
  361. server = ('127.0.0.1', 11211)
  362. client = HashClient([])
  363. assert client.clients == {}
  364. client.add_server(*server) # Unpack (host, port) tuple.
  365. assert ('%s:%s' % server) in client.clients
  366. client._mark_failed_server(server)
  367. assert server in client._failed_clients
  368. client.remove_server(*server) # Unpack (host, port) tuple.
  369. assert server in client._dead_clients
  370. assert server not in client._failed_clients
  371. # Ensure that server is a string if passing port argument:
  372. with pytest.raises(TypeError):
  373. client.add_server(server, server[-1])
  374. with pytest.raises(TypeError):
  375. client.remove_server(server, server[-1])
  376. # TODO: Test failover logic