Skip to content

Commit e8a8bfe

Browse files
authored
Switch to py-consul (patroni#3191)
python-consul is unmaintained for a long time and py-consul is an official replacement. However, we still keep backward compatibility with python-consul. Close: patroni#3189
1 parent 72be036 commit e8a8bfe

File tree

9 files changed

+62
-54
lines changed

9 files changed

+62
-54
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ where dependencies can be either empty, or consist of one or more of the followi
9191
etcd or etcd3
9292
`python-etcd` module in order to use Etcd as DCS
9393
consul
94-
`python-consul` module in order to use Consul as DCS
94+
`py-consul` module in order to use Consul as DCS
9595
zookeeper
9696
`kazoo` module in order to use Zookeeper as DCS
9797
exhibitor

docs/installation.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ where ``dependencies`` can be either empty, or consist of one or more of the fol
4949
etcd or etcd3
5050
`python-etcd` module in order to use Etcd as Distributed Configuration Store (DCS)
5151
consul
52-
`python-consul` module in order to use Consul as DCS
52+
`py-consul` module in order to use Consul as DCS
5353
zookeeper
5454
`kazoo` module in order to use Zookeeper as DCS
5555
exhibitor

patroni/dcs/consul.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import urllib3
1717

18-
from consul import base, ConsulException, NotFound
18+
from consul import base, Check, ConsulException, NotFound
1919
from urllib3.exceptions import HTTPError
2020

2121
from ..exceptions import DCSError
@@ -181,7 +181,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
181181
self.token = kwargs.get('token')
182182
super(ConsulClient, self).__init__(*args, **kwargs)
183183

184-
def http_connect(self, *args: Any, **kwargs: Any) -> HTTPClient:
184+
def connect(self, *args: Any, **kwargs: Any) -> HTTPClient:
185185
kwargs.update(dict(zip(['host', 'port', 'scheme', 'verify'], args)))
186186
if self._cert:
187187
kwargs['cert'] = self._cert
@@ -191,8 +191,8 @@ def http_connect(self, *args: Any, **kwargs: Any) -> HTTPClient:
191191
kwargs['token'] = self.token
192192
return HTTPClient(**kwargs)
193193

194-
def connect(self, *args: Any, **kwargs: Any) -> HTTPClient:
195-
return self.http_connect(*args, **kwargs)
194+
def http_connect(self, *args: Any, **kwargs: Any) -> HTTPClient:
195+
return self.connect(*args, **kwargs) # pragma: no cover
196196

197197
def reload_config(self, config: Dict[str, Any]) -> None:
198198
self.http.token = self.token = config.get('token')
@@ -526,8 +526,8 @@ def _update_service(self, data: Dict[str, Any]) -> Optional[bool]:
526526
api_parts = api_parts._replace(path='/{0}'.format(role))
527527
conn_url: str = data['conn_url']
528528
conn_parts = urlparse(conn_url)
529-
check = base.Check.http(api_parts.geturl(), self._service_check_interval,
530-
deregister='{0}s'.format(self._client.http.ttl * 10))
529+
check = Check.http(api_parts.geturl(), self._service_check_interval,
530+
deregister='{0}s'.format(self._client.http.ttl * 10))
531531
if self._service_check_tls_server_name is not None:
532532
check['TLSServerName'] = self._service_check_tls_server_name
533533
tags = self._service_tags[:]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ boto3
33
PyYAML
44
kazoo>=1.3.1
55
python-etcd>=0.4.3,<0.5
6-
python-consul>=0.7.1
6+
py-consul>=1.1.1
77
click>=4.1
88
prettytable>=0.7
99
python-dateutil

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
' zookeeper exhibitor consul streaming replication kubernetes k8s'
2626

2727
EXTRAS_REQUIRE = {'aws': ['boto3'], 'etcd': ['python-etcd'], 'etcd3': ['python-etcd'],
28-
'consul': ['python-consul'], 'exhibitor': ['kazoo'], 'zookeeper': ['kazoo'],
28+
'consul': ['py-consul'], 'exhibitor': ['kazoo'], 'zookeeper': ['kazoo'],
2929
'kubernetes': [], 'raft': ['pysyncobj', 'cryptography'], 'jsonlogger': ['python-json-logger']}
3030

3131
# Add here all kinds of additional classifiers as defined under

tests/test_consul.py

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,18 @@ def test_put(self):
8989
self.client.put(Mock(), '/v1/session/create', params=[], data='{"foo": "bar"}')
9090

9191

92-
@patch.object(consul.Consul.KV, 'get', kv_get)
92+
KV = consul.Consul.KV if hasattr(consul.Consul, 'KV') else consul.api.kv.KV
93+
Session = consul.Consul.Session if hasattr(consul.Consul, 'Session') else consul.api.session.Session
94+
Agent = consul.Consul.Agent if hasattr(consul.Consul, 'Agent') else consul.api.agent.Agent
95+
96+
97+
@patch.object(KV, 'get', kv_get)
9398
class TestConsul(unittest.TestCase):
9499

95-
@patch.object(consul.Consul.Session, 'create', Mock(return_value='fd4f44fe-2cac-bba5-a60b-304b51ff39b7'))
96-
@patch.object(consul.Consul.Session, 'renew', Mock(side_effect=NotFound))
97-
@patch.object(consul.Consul.KV, 'get', kv_get)
98-
@patch.object(consul.Consul.KV, 'delete', Mock())
100+
@patch.object(Session, 'create', Mock(return_value='fd4f44fe-2cac-bba5-a60b-304b51ff39b7'))
101+
@patch.object(Session, 'renew', Mock(side_effect=NotFound))
102+
@patch.object(KV, 'get', kv_get)
103+
@patch.object(KV, 'delete', Mock())
99104
def setUp(self):
100105
self.assertIsInstance(get_dcs({'ttl': 30, 'scope': 't', 'name': 'p', 'retry_timeout': 10,
101106
'consul': {'url': 'https://l:1', 'verify': 'on',
@@ -112,22 +117,22 @@ def setUp(self):
112117
self.c.get_cluster()
113118

114119
@patch('time.sleep', Mock(side_effect=SleepException))
115-
@patch.object(consul.Consul.Session, 'create', Mock(side_effect=ConsulException))
120+
@patch.object(Session, 'create', Mock(side_effect=ConsulException))
116121
def test_create_session(self):
117122
self.c._session = None
118123
self.assertRaises(SleepException, self.c.create_session)
119124

120-
@patch.object(consul.Consul.Session, 'renew', Mock(side_effect=NotFound))
121-
@patch.object(consul.Consul.Session, 'create', Mock(side_effect=[InvalidSessionTTL, ConsulException]))
122-
@patch.object(consul.Consul.Agent, 'self', Mock(return_value={'Config': {'SessionTTLMin': 0}}))
125+
@patch.object(Session, 'renew', Mock(side_effect=NotFound))
126+
@patch.object(Session, 'create', Mock(side_effect=[InvalidSessionTTL, ConsulException]))
127+
@patch.object(Agent, 'self', Mock(return_value={'Config': {'SessionTTLMin': 0}}))
123128
@patch.object(HTTPClient, 'set_ttl', Mock(side_effect=ValueError))
124129
def test_referesh_session(self):
125130
self.c._session = '1'
126131
self.assertFalse(self.c.refresh_session())
127132
self.c._last_session_refresh = 0
128133
self.assertRaises(ConsulError, self.c.refresh_session)
129134

130-
@patch.object(consul.Consul.KV, 'delete', Mock())
135+
@patch.object(KV, 'delete', Mock())
131136
def test_get_cluster(self):
132137
self.c._base_path = 'service/test'
133138
self.assertIsInstance(self.c.get_cluster(), Cluster)
@@ -145,8 +150,8 @@ def test__get_citus_cluster(self):
145150
self.assertIsInstance(cluster, Cluster)
146151
self.assertIsInstance(cluster.workers[1], Cluster)
147152

148-
@patch.object(consul.Consul.KV, 'delete', Mock(side_effect=[ConsulException, True, True, True]))
149-
@patch.object(consul.Consul.KV, 'put', Mock(side_effect=[True, ConsulException, InvalidSession]))
153+
@patch.object(KV, 'delete', Mock(side_effect=[ConsulException, True, True, True]))
154+
@patch.object(KV, 'put', Mock(side_effect=[True, ConsulException, InvalidSession]))
150155
def test_touch_member(self):
151156
self.c.refresh_session = Mock(return_value=False)
152157
with patch.object(Consul, 'update_service', Mock(side_effect=Exception)):
@@ -159,66 +164,66 @@ def test_touch_member(self):
159164
self.c.refresh_session = Mock(side_effect=ConsulError('foo'))
160165
self.assertFalse(self.c.touch_member({'balbla': 'blabla'}))
161166

162-
@patch.object(consul.Consul.KV, 'put', Mock(side_effect=[InvalidSession, False, InvalidSession]))
167+
@patch.object(KV, 'put', Mock(side_effect=[InvalidSession, False, InvalidSession]))
163168
def test_take_leader(self):
164169
self.c.set_ttl(20)
165170
self.c._do_refresh_session = Mock()
166171
self.assertFalse(self.c.take_leader())
167172
with patch('time.time', Mock(side_effect=[0, 0, 0, 100, 100, 100])):
168173
self.assertFalse(self.c.take_leader())
169174

170-
@patch.object(consul.Consul.KV, 'put', Mock(return_value=True))
175+
@patch.object(KV, 'put', Mock(return_value=True))
171176
def test_set_failover_value(self):
172177
self.c.set_failover_value('')
173178

174-
@patch.object(consul.Consul.KV, 'put', Mock(return_value=True))
179+
@patch.object(KV, 'put', Mock(return_value=True))
175180
def test_set_config_value(self):
176181
self.c.set_config_value('')
177182

178183
@patch.object(Cluster, 'min_version', PropertyMock(return_value=(2, 0)))
179-
@patch.object(consul.Consul.KV, 'put', Mock(side_effect=ConsulException))
184+
@patch.object(KV, 'put', Mock(side_effect=ConsulException))
180185
def test_write_leader_optime(self):
181186
self.c.get_cluster()
182187
self.c.write_leader_optime('1')
183188

184-
@patch.object(consul.Consul.Session, 'renew')
185-
@patch.object(consul.Consul.KV, 'put', Mock(side_effect=ConsulException))
189+
@patch.object(Session, 'renew')
190+
@patch.object(KV, 'put', Mock(side_effect=ConsulException))
186191
def test_update_leader(self, mock_renew):
187192
cluster = self.c.get_cluster()
188193
self.c._session = 'fd4f44fe-2cac-bba5-a60b-304b51ff39b8'
189-
with patch.object(consul.Consul.KV, 'delete', Mock(return_value=True)):
190-
with patch.object(consul.Consul.KV, 'put', Mock(return_value=True)):
194+
with patch.object(KV, 'delete', Mock(return_value=True)):
195+
with patch.object(KV, 'put', Mock(return_value=True)):
191196
self.assertTrue(self.c.update_leader(cluster, 12345, failsafe={'foo': 'bar'}))
192-
with patch.object(consul.Consul.KV, 'put', Mock(side_effect=ConsulException)):
197+
with patch.object(KV, 'put', Mock(side_effect=ConsulException)):
193198
self.assertFalse(self.c.update_leader(cluster, 12345))
194199
with patch('time.time', Mock(side_effect=[0, 0, 0, 0, 100, 200, 300])):
195200
self.assertRaises(ConsulError, self.c.update_leader, cluster, 12345)
196201
with patch('time.time', Mock(side_effect=[0, 100, 200, 300])):
197202
self.assertRaises(ConsulError, self.c.update_leader, cluster, 12345)
198-
with patch.object(consul.Consul.KV, 'delete', Mock(side_effect=ConsulException)):
203+
with patch.object(KV, 'delete', Mock(side_effect=ConsulException)):
199204
self.assertFalse(self.c.update_leader(cluster, 12347))
200205
mock_renew.side_effect = RetryFailedError('')
201206
self.c._last_session_refresh = 0
202207
self.assertRaises(ConsulError, self.c.update_leader, cluster, 12346)
203208
mock_renew.side_effect = ConsulException
204209
self.assertFalse(self.c.update_leader(cluster, 12347))
205210

206-
@patch.object(consul.Consul.KV, 'delete', Mock(return_value=True))
211+
@patch.object(KV, 'delete', Mock(return_value=True))
207212
def test_delete_leader(self):
208213
leader = self.c.get_cluster().leader
209214
self.c.delete_leader(leader)
210215
self.c._name = 'other'
211216
self.c.delete_leader(leader)
212217

213-
@patch.object(consul.Consul.KV, 'put', Mock(return_value=True))
218+
@patch.object(KV, 'put', Mock(return_value=True))
214219
def test_initialize(self):
215220
self.c.initialize()
216221

217-
@patch.object(consul.Consul.KV, 'delete', Mock(return_value=True))
222+
@patch.object(KV, 'delete', Mock(return_value=True))
218223
def test_cancel_initialization(self):
219224
self.c.cancel_initialization()
220225

221-
@patch.object(consul.Consul.KV, 'delete', Mock(return_value=True))
226+
@patch.object(KV, 'delete', Mock(return_value=True))
222227
def test_delete_cluster(self):
223228
self.c.delete_cluster()
224229

@@ -227,28 +232,28 @@ def test_watch(self):
227232
self.c.watch(None, 1)
228233
self.c._name = ''
229234
self.c.watch(6429, 1)
230-
with patch.object(consul.Consul.KV, 'get', Mock(side_effect=ConsulException)):
235+
with patch.object(KV, 'get', Mock(side_effect=ConsulException)):
231236
self.c.watch(6429, 1)
232237

233238
def test_set_retry_timeout(self):
234239
self.c.set_retry_timeout(10)
235240

236-
@patch.object(consul.Consul.KV, 'delete', Mock(return_value=True))
237-
@patch.object(consul.Consul.KV, 'put', Mock(return_value=True))
241+
@patch.object(KV, 'delete', Mock(return_value=True))
242+
@patch.object(KV, 'put', Mock(return_value=True))
238243
def test_sync_state(self):
239244
self.assertEqual(self.c.set_sync_state_value('{}'), 1)
240245
with patch('time.time', Mock(side_effect=[1, 100, 1000])):
241246
self.assertFalse(self.c.set_sync_state_value('{}'))
242-
with patch.object(consul.Consul.KV, 'put', Mock(return_value=False)):
247+
with patch.object(KV, 'put', Mock(return_value=False)):
243248
self.assertFalse(self.c.set_sync_state_value('{}'))
244249
self.assertTrue(self.c.delete_sync_state())
245250

246-
@patch.object(consul.Consul.KV, 'put', Mock(return_value=True))
251+
@patch.object(KV, 'put', Mock(return_value=True))
247252
def test_set_history_value(self):
248253
self.assertTrue(self.c.set_history_value('{}'))
249254

250-
@patch.object(consul.Consul.Agent.Service, 'register', Mock(side_effect=(False, True, True, True)))
251-
@patch.object(consul.Consul.Agent.Service, 'deregister', Mock(return_value=True))
255+
@patch.object(Agent.Service, 'register', Mock(side_effect=(False, True, True, True)))
256+
@patch.object(Agent.Service, 'deregister', Mock(return_value=True))
252257
def test_update_service(self):
253258
d = {'role': 'replica', 'api_url': 'http://a/t', 'conn_url': 'pg://c:1', 'state': 'running'}
254259
self.assertIsNone(self.c.update_service({}, {}))
@@ -265,7 +270,7 @@ def test_update_service(self):
265270
d['role'] = 'primary'
266271
self.assertTrue(self.c.update_service({}, d))
267272

268-
@patch.object(consul.Consul.KV, 'put', Mock(side_effect=ConsulException))
273+
@patch.object(KV, 'put', Mock(side_effect=ConsulException))
269274
def test_reload_config(self):
270275
self.assertEqual([], self.c._service_tags)
271276
self.c.reload_config({'consul': {'token': 'foo', 'register_service': True, 'service_tags': ['foo']},
@@ -278,39 +283,39 @@ def test_reload_config(self):
278283

279284
# Changing register_service from True to False calls deregister()
280285
self.c.reload_config({'consul': {'register_service': False}, 'loop_wait': 10, 'ttl': 30, 'retry_timeout': 10})
281-
with patch('consul.Consul.Agent.Service.deregister') as mock_deregister:
286+
with patch.object(Agent.Service, 'deregister') as mock_deregister:
282287
self.c.touch_member(d)
283288
mock_deregister.assert_called_once()
284289

285290
self.assertEqual([], self.c._service_tags)
286291

287292
# register_service staying False between reloads does not call deregister()
288293
self.c.reload_config({'consul': {'register_service': False}, 'loop_wait': 10, 'ttl': 30, 'retry_timeout': 10})
289-
with patch('consul.Consul.Agent.Service.deregister') as mock_deregister:
294+
with patch.object(Agent.Service, 'deregister') as mock_deregister:
290295
self.c.touch_member(d)
291296
self.assertFalse(mock_deregister.called)
292297

293298
# Changing register_service from False to True calls register()
294299
self.c.reload_config({'consul': {'register_service': True}, 'loop_wait': 10, 'ttl': 30, 'retry_timeout': 10})
295-
with patch('consul.Consul.Agent.Service.register') as mock_register:
300+
with patch.object(Agent.Service, 'register') as mock_register:
296301
self.c.touch_member(d)
297302
mock_register.assert_called_once()
298303

299304
# register_service staying True between reloads does not call register()
300305
self.c.reload_config({'consul': {'register_service': True}, 'loop_wait': 10, 'ttl': 30, 'retry_timeout': 10})
301-
with patch('consul.Consul.Agent.Service.register') as mock_register:
306+
with patch.object(Agent.Service, 'register') as mock_register:
302307
self.c.touch_member(d)
303308
self.assertFalse(mock_deregister.called)
304309

305310
# register_service staying True between reloads does calls register() if other service data has changed
306311
self.c.reload_config({'consul': {'register_service': True}, 'loop_wait': 10, 'ttl': 30, 'retry_timeout': 10})
307-
with patch('consul.Consul.Agent.Service.register') as mock_register:
312+
with patch.object(Agent.Service, 'register') as mock_register:
308313
self.c.touch_member(d)
309314
mock_register.assert_called_once()
310315

311316
# register_service staying True between reloads does calls register() if service_tags have changed
312317
self.c.reload_config({'consul': {'register_service': True, 'service_tags': ['foo']}, 'loop_wait': 10,
313318
'ttl': 30, 'retry_timeout': 10})
314-
with patch('consul.Consul.Agent.Service.register') as mock_register:
319+
with patch.object(Agent.Service, 'register') as mock_register:
315320
self.c.touch_member(d)
316321
mock_register.assert_called_once()

typings/consul/__init__.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
from consul.check import Check
12
from consul.base import ConsulException, NotFound
2-
__all__ = ['ConsulException', 'Consul', 'NotFound']
3+
__all__ = ['Check', 'ConsulException', 'Consul', 'NotFound']

typings/consul/base.pyi

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
from typing import Any, Dict, List, Optional, Tuple
22
class ConsulException(Exception): ...
33
class NotFound(ConsulException): ...
4-
class Check:
5-
@classmethod
6-
def http(klass, url: str, interval: str, timeout: Optional[str] = None, deregister: Optional[str] = None) -> Dict[str, str]: ...
74
class Consul:
85
http: Any
96
agent: 'Consul.Agent'

typings/consul/check.pyi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from typing import Dict, Optional
2+
class Check:
3+
@classmethod
4+
def http(klass, url: str, interval: str, timeout: Optional[str] = None, deregister: Optional[str] = None) -> Dict[str, str]: ...
5+

0 commit comments

Comments
 (0)