Skip to content

Commit 4cd11ac

Browse files
committed
Add support for IGNORE_CLUSTER_ERRORS option
Conditionally ignore failures to `config get cluster` calls against the configured LOCATION endpoint with the introduction of a `IGNORE_CLUSTER_ERRORS` option (that defaults to `False`).
1 parent 4988289 commit 4cd11ac

File tree

5 files changed

+35
-9
lines changed

5 files changed

+35
-9
lines changed

README.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ Your cache backend should look something like this::
3636
'default': {
3737
'BACKEND': 'django_elasticache.memcached.ElastiCache',
3838
'LOCATION': 'cache-c.draaaf.cfg.use1.cache.amazonaws.com:11211',
39+
'OPTIONS' {
40+
'IGNORE_CLUSTER_ERRORS': [True,False],
41+
},
3942
}
4043
}
4144

42-
By the first call to cache it connects to cluster (using LOCATION param),
45+
By the first call to cache it connects to cluster (using ``LOCATION`` param),
4346
gets list of all nodes and setup pylibmc client using full
4447
list of nodes. As result your cache will work with all nodes in cluster and
4548
automatically detect new nodes in cluster. List of nodes are stored in class-level
@@ -48,6 +51,10 @@ But if you're using gunicorn or mod_wsgi you usually have max_request settings w
4851
restart process after some count of processed requests, so auto discovery will work
4952
fine.
5053

54+
The ``IGNORE_CLUSTER_ERRORS`` option is useful when ``LOCATION`` doesn't have support
55+
for ``config get cluster``. When set to ``True``, and ``config get cluster`` fails,
56+
it returns a list of a single node with the same endpoint supplied to ``LOCATION``.
57+
5158
Django-elasticache changes default pylibmc params to increase performance.
5259

5360
Another solutions

django_elasticache/cluster_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def __init__(self, cmd, response):
1717
'Unexpected response {} for command {}'.format(response, cmd))
1818

1919

20-
def get_cluster_info(host, port, timeout=3):
20+
def get_cluster_info(host, port, ignore_cluster_errors=False, timeout=3):
2121
"""
2222
return dict with info about nodes in cluster and current version
2323
{
@@ -46,7 +46,7 @@ def get_cluster_info(host, port, timeout=3):
4646
], timeout)
4747
client.close()
4848

49-
if res == b'ERROR\r\n':
49+
if res == b'ERROR\r\n' and ignore_cluster_errors:
5050
return {
5151
'version': version,
5252
'nodes': [

django_elasticache/memcached.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ def __init__(self, server, params):
3838
raise InvalidCacheBackendError(
3939
'Server configuration should be in format IP:port')
4040

41+
self._ignore_cluster_errors = self._options.get(
42+
'IGNORE_CLUSTER_ERRORS', False)
43+
4144
def update_params(self, params):
4245
"""
4346
update connection params to maximize performance
@@ -51,7 +54,8 @@ def update_params(self, params):
5154
# set special 'behaviors' pylibmc attributes
5255
params['OPTIONS'] = {
5356
'tcp_nodelay': True,
54-
'ketama': True
57+
'ketama': True,
58+
'IGNORE_CLUSTER_ERRORS': False,
5559
}
5660

5761
def clear_cluster_nodes_cache(self):
@@ -67,7 +71,8 @@ def get_cluster_nodes(self):
6771
server, port = self._servers[0].split(':')
6872
try:
6973
self._cluster_nodes_cache = (
70-
get_cluster_info(server, port)['nodes'])
74+
get_cluster_info(server, port,
75+
self._ignore_cluster_errors)['nodes'])
7176
except (socket.gaierror, socket.timeout) as err:
7277
raise Exception('Cannot connect to cluster {} ({})'.format(
7378
self._servers[0], err

tests/test_backend.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def test_patch_params():
2020
eq_(params['BINARY'], True)
2121
eq_(params['OPTIONS']['tcp_nodelay'], True)
2222
eq_(params['OPTIONS']['ketama'], True)
23+
eq_(params['OPTIONS']['IGNORE_CLUSTER_ERRORS'], False)
2324

2425

2526
@raises(Exception)
@@ -47,7 +48,7 @@ def test_split_servers(get_cluster_info):
4748
}
4849
backend._lib.Client = Mock()
4950
assert backend._cache
50-
get_cluster_info.assert_called_once_with('h', '0')
51+
get_cluster_info.assert_called_once_with('h', '0', False)
5152
backend._lib.Client.assert_called_once_with(servers)
5253

5354

@@ -70,7 +71,7 @@ def test_node_info_cache(get_cluster_info):
7071
eq_(backend._cache.get.call_count, 2)
7172
eq_(backend._cache.set.call_count, 2)
7273

73-
get_cluster_info.assert_called_once_with('h', '0')
74+
get_cluster_info.assert_called_once_with('h', '0', False)
7475

7576

7677
@patch('django.conf.settings', global_settings)

tests/test_protocol.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,27 @@ def test_ubuntu_protocol(Telnet):
9999

100100

101101
@patch('django_elasticache.cluster_utils.Telnet')
102-
def test_no_configuration_protocol_support(Telnet):
102+
def test_no_configuration_protocol_support_with_errors_ignored(Telnet):
103103
client = Telnet.return_value
104104
client.read_until.side_effect = TEST_PROTOCOL_4_READ_UNTIL
105105
client.expect.side_effect = TEST_PROTOCOL_4_EXPECT
106-
info = get_cluster_info('test', 0)
106+
info = get_cluster_info('test', 0, ignore_cluster_errors=True)
107107
client.write.assert_has_calls([
108108
call(b'version\n'),
109109
call(b'config get cluster\n'),
110110
])
111111
eq_(info['version'], '1.4.34')
112112
eq_(info['nodes'], ['test:0'])
113+
114+
115+
@raises(WrongProtocolData)
116+
@patch('django_elasticache.cluster_utils.Telnet')
117+
def test_no_configuration_protocol_support_with_errors(Telnet):
118+
client = Telnet.return_value
119+
client.read_until.side_effect = TEST_PROTOCOL_4_READ_UNTIL
120+
client.expect.side_effect = TEST_PROTOCOL_4_EXPECT
121+
get_cluster_info('test', 0, ignore_cluster_errors=False)
122+
client.write.assert_has_calls([
123+
call(b'version\n'),
124+
call(b'config get cluster\n'),
125+
])

0 commit comments

Comments
 (0)