Skip to content

Commit 67c1d02

Browse files
author
alexandre menezes
committed
Merge branch 'hf-reconnect'
2 parents 501c405 + 656856a commit 67c1d02

File tree

5 files changed

+138
-71
lines changed

5 files changed

+138
-71
lines changed

discovery/aioclient.py

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,6 @@ def __create_service(self, service_name, service_port, healthcheck_path):
4646

4747
logging.debug(f'Service data: {self.__service}')
4848

49-
def __format_id(self, id):
50-
"""Retrieve consul ID from Consul API: /health/status/<service>.
51-
52-
docs: https://www.consul.io/api/health.html#list-nodes-for-service
53-
"""
54-
return id[Filter.PAYLOAD.value][Filter.FIRST_ITEM.value]['Node']['ID']
55-
5649
def __format_catalog_service(self, services):
5750
servicesfmt = [{"node": svc['Node'],
5851
"address": svc['Address'],
@@ -65,17 +58,34 @@ def __format_catalog_service(self, services):
6558
async def _reconnect(self):
6659
"""Service re-registration steps."""
6760
await self.__discovery.agent.service.deregister(self.__service['id'])
68-
await self.__discovery.agent.service.register(name=self.__service['name'],
69-
service_id=self.__service['id'],
70-
check=self.__service['healthcheck'],
71-
address=self.__service['application_ip'],
72-
port=self.__service['port'])
73-
current_id = await self.__discovery.health.service('consul')
74-
self.__id = self.__format_id(current_id)
75-
76-
logging.debug(f"Consul ID: {self.__format_id(current_id)}")
61+
await self.__discovery.agent.service.register(
62+
name=self.__service['name'],
63+
service_id=self.__service['id'],
64+
check=self.__service['healthcheck'],
65+
address=self.__service['application_ip'],
66+
port=self.__service['port']
67+
)
68+
69+
self.__id = await self.get_leader_current_id()
70+
71+
logging.debug(f"Consul ID: {self.__id}")
7772
logging.info('Service successfully re-registered')
7873

74+
async def get_leader_current_id(self):
75+
"""Retrieve current ID from consul leader."""
76+
consul_leader = await self.__discovery.status.leader()
77+
consul_instances = await self.__discovery.health.service('consul')
78+
consul_instances = consul_instances[Filter.PAYLOAD.value]
79+
80+
current_id = [instance['Node']['ID']
81+
for instance in consul_instances
82+
if instance['Node']['Address'] == consul_leader.split(':')[0]]
83+
84+
# if len(current_id) > 0:
85+
# current_id = current_id[Filter.FIRST_ITEM.value]
86+
87+
return current_id[Filter.FIRST_ITEM.value]
88+
7989
async def consul_is_healthy(self):
8090
"""Start a loop to monitor consul healthy.
8191
@@ -87,18 +97,22 @@ async def consul_is_healthy(self):
8797
current_id = await self.__discovery.health.service('consul')
8898

8999
logging.debug('Checking consul health status')
90-
logging.debug(f"Consul ID: {self.__format_id(current_id)}")
100+
logging.debug(f"Consul ID: {current_id}")
91101

92-
if self.__format_id(current_id) != self.__id:
102+
if current_id != self.__id:
93103
await self._reconnect()
94104

95105
except aiohttp.ClientConnectorError:
96106
logging.error('failed to connect to discovery service...')
97-
logging.error(f"reconnect will occur in {self.DEFAULT_TIMEOUT} seconds.")
107+
logging.error(
108+
f"reconnect will occur in {self.DEFAULT_TIMEOUT} seconds."
109+
)
98110
await self.consul_is_healthy()
99111

100112
except aiohttp.ServerDisconnectedError:
101-
logging.error('temporary loss of communication with the discovery server.')
113+
logging.error(
114+
'temporary loss of communication with the discovery server.'
115+
)
102116
asyncio.sleep(self.DEFAULT_TIMEOUT)
103117
await self.consul_is_healthy()
104118

@@ -131,7 +145,10 @@ async def deregister(self):
131145

132146
logging.info('successfully unregistered application!')
133147

134-
async def register(self, service_name, service_port, healthcheck_path="/manage/health"):
148+
async def register(self,
149+
service_name,
150+
service_port,
151+
healthcheck_path="/manage/health"):
135152
"""Register a new service.
136153
137154
Default values are:
@@ -141,19 +158,23 @@ async def register(self, service_name, service_port, healthcheck_path="/manage/h
141158
timeout: 5s
142159
"""
143160
try:
144-
self.__create_service(service_name,
145-
service_port,
146-
healthcheck_path)
147-
await self.__discovery.agent.service.register(name=self.__service['name'],
148-
service_id=self.__service['id'],
149-
check=self.__service['healthcheck'],
150-
address=self.__service['application_ip'],
151-
port=self.__service['port'])
152-
current_id = await self.__discovery.health.service('consul')
153-
self.__id = self.__format_id(current_id)
161+
self.__create_service(
162+
service_name,
163+
service_port,
164+
healthcheck_path
165+
)
166+
await self.__discovery.agent.service.register(
167+
name=self.__service['name'],
168+
service_id=self.__service['id'],
169+
check=self.__service['healthcheck'],
170+
address=self.__service['application_ip'],
171+
port=self.__service['port']
172+
)
173+
174+
self.__id = await self.get_leader_current_id()
154175

155176
logging.info('service successfully registered!')
156-
logging.debug(f"Consul ID: {self.__format_id(current_id)}")
177+
logging.debug(f"Consul ID: {self.__id}")
157178

158179
except aiohttp.ClientConnectorError:
159180
logging.error("failed to connect to discovery...")

discovery/client.py

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,6 @@ def __create_service(self, service_name, service_port, healthcheck_path):
4343

4444
logging.debug(f'Service data: {self.__service}')
4545

46-
def __format_id(self, service_id):
47-
"""Retrieve consul ID from Consul API: /health/status/<service>.
48-
49-
docs: https://www.consul.io/api/health.html#list-nodes-for-service
50-
"""
51-
return service_id[Filter.PAYLOAD.value][Filter.FIRST_ITEM.value]['Node']['ID']
52-
5346
def __format_catalog_service(self, services):
5447
servicesfmt = [{"node": svc['Node'],
5548
"address": svc['Address'],
@@ -64,16 +57,31 @@ def __reconnect(self):
6457
logging.debug('Service reconnect fallback')
6558

6659
self.__discovery.agent.service.deregister(self.__service['id'])
67-
self.__discovery.agent.service.register(name=self.__service['name'],
68-
service_id=self.__service['id'],
69-
check=self.__service['healthcheck'],
70-
address=self.__service['application_ip'],
71-
port=self.__service['port'])
72-
current_id = self.__discovery.health.service('consul')
73-
self.__id = self.__format_id(current_id)
60+
self.__discovery.agent.service.register(
61+
name=self.__service['name'],
62+
service_id=self.__service['id'],
63+
check=self.__service['healthcheck'],
64+
address=self.__service['application_ip'],
65+
port=self.__service['port']
66+
)
67+
68+
self.__id = self.get_leader_current_id()
7469

7570
logging.info('Service successfully re-registered')
7671

72+
def get_leader_current_id(self):
73+
"""Retrieve current ID from consul leader."""
74+
consul_leader = self.__discovery.status.leader()
75+
consul_instances = self.__discovery.health.service('consul')[Filter.PAYLOAD.value]
76+
current_id = [instance['Node']['ID']
77+
for instance in consul_instances
78+
if instance['Node']['Address'] == consul_leader.split(':')[0]]
79+
80+
if len(current_id) > 0:
81+
current_id = current_id[Filter.FIRST_ITEM.value]
82+
83+
return current_id
84+
7785
def consul_is_healthy(self):
7886
"""Start a loop to monitor consul healthy.
7987
@@ -82,17 +90,18 @@ def consul_is_healthy(self):
8290
while True:
8391
try:
8492
time.sleep(self.DEFAULT_TIMEOUT)
85-
current_id = self.__discovery.health.service('consul')
8693

87-
logging.debug('Checking consul health status')
88-
logging.debug(f"Consul ID: {self.__format_id(current_id)}")
94+
current_id = self.get_leader_current_id()
95+
logging.debug(f"Consul ID: {current_id}")
8996

90-
if self.__format_id(current_id) != self.__id:
97+
if current_id != self.__id:
9198
self.__reconnect()
9299

93100
except requests.exceptions.ConnectionError:
94101
logging.error("Failed to connect to discovery service...")
95-
logging.error(f'Reconnect will occur in {self.DEFAULT_TIMEOUT} seconds.')
102+
logging.error(
103+
f'Reconnect will occur in {self.DEFAULT_TIMEOUT} seconds.'
104+
)
96105

97106
def find_service(self, service_name, method='rr'):
98107
"""Search for a service in the consul's catalog.
@@ -118,12 +127,17 @@ def find_services(self, service_name):
118127

119128
def deregister(self):
120129
"""Deregister a service registered."""
121-
logging.debug(f"Unregistering service id: {self.__service['id']}")
130+
logging.debug(
131+
f"Unregistering service id: {self.__service['id']}"
132+
)
122133
logging.info('Successfully unregistered application!')
123134

124135
self.__discovery.agent.service.deregister(self.__service['id'])
125136

126-
def register(self, service_name, service_port, healthcheck_path="/manage/health"):
137+
def register(self,
138+
service_name,
139+
service_port,
140+
healthcheck_path="/manage/health"):
127141
"""Register a new service.
128142
129143
Default values are:
@@ -136,13 +150,15 @@ def register(self, service_name, service_port, healthcheck_path="/manage/health"
136150
self.__create_service(service_name,
137151
service_port,
138152
healthcheck_path)
139-
self.__discovery.agent.service.register(name=self.__service['name'],
140-
service_id=self.__service['id'],
141-
check=self.__service['healthcheck'],
142-
address=self.__service['application_ip'],
143-
port=self.__service['port'])
144-
current_id = self.__discovery.health.service('consul')
145-
self.__id = self.__format_id(current_id)
153+
self.__discovery.agent.service.register(
154+
name=self.__service['name'],
155+
service_id=self.__service['id'],
156+
check=self.__service['healthcheck'],
157+
address=self.__service['application_ip'],
158+
port=self.__service['port']
159+
)
160+
161+
self.__id = self.get_leader_current_id()
146162

147163
logging.info('Service successfully registered!')
148164
logging.debug(f'Consul ID: {self.__id}')

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setuptools.setup(
1010
name="discovery-client",
11-
version="0.2.1",
11+
version="0.2.2",
1212
author="alexandre menezes",
1313
author_email="alexandre.fmenezes@gmail.com",
1414
description="discovery service client",

tests/unit/test_aioclient.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import os
44
import unittest
55

6+
# from aiohttp import ClientConnectorError
7+
68
import asynctest
79
from asynctest import CoroutineMock, patch
810

@@ -22,7 +24,8 @@ def setUp(self):
2224
self.loop = asyncio.get_event_loop()
2325
self.consul_health_response = (
2426
0, [{'Node': {
25-
'ID': '123456'}}])
27+
'ID': '123456',
28+
'Address': '127.0.0.1'}}])
2629
self.consul_raw_response = (
2730
0, [{'Node': 'localhost',
2831
'Address': '127.0.0.1',
@@ -130,18 +133,46 @@ async def async_test_find_services(loop):
130133
async_test_find_services(self.loop)
131134
)
132135

136+
@patch('discovery.aioclient.consul.aio.Consul')
137+
def test_get_leader_current_id(self, MockAioClient):
138+
"""Test retrieve the ID from Consul leader."""
139+
async def async_test_get_leader_current_id(loop):
140+
consul_client = MockAioClient(consul.aio.Consul)
141+
consul_client.status.leader = CoroutineMock(
142+
return_value='127.0.0.1:8300'
143+
)
144+
consul_client.health.service = CoroutineMock(
145+
return_value=self.consul_health_response
146+
)
147+
148+
dc = aioclient.Consul('localhost', 8500, app=loop)
149+
current_id = await dc.get_leader_current_id()
150+
151+
self.assertIsNotNone(current_id)
152+
self.assertEqual(
153+
current_id,
154+
self.consul_health_response[1][0]['Node']['ID']
155+
)
156+
157+
self.loop.run_until_complete(
158+
async_test_get_leader_current_id(self.loop)
159+
)
160+
133161
@patch('discovery.aioclient.consul.aio.Consul')
134162
def test_register(self, MockAioConsul):
135163
"""Test registration of a service in the consul's catalog."""
136164
async def async_test_register(loop):
137165
consul_client = MockAioConsul(consul.aio.Consul)
138166
consul_client.agent.service.register = CoroutineMock()
139-
consul_client.catalog.service = CoroutineMock(
140-
return_value=self.myapp_raw_response
167+
consul_client.status.leader = CoroutineMock(
168+
return_value='127.0.0.1:8300'
141169
)
142170
consul_client.health.service = CoroutineMock(
143171
return_value=self.consul_health_response
144172
)
173+
consul_client.catalog.service = CoroutineMock(
174+
return_value=self.myapp_raw_response
175+
)
145176

146177
dc = aioclient.Consul('localhost', 8500, app=loop)
147178
await dc.register('myapp', 5000)
@@ -164,6 +195,9 @@ async def async_test_deregister(loop):
164195
consul_client.catalog.service = CoroutineMock(
165196
return_value=self.myapp_raw_response
166197
)
198+
consul_client.status.leader = CoroutineMock(
199+
return_value='127.0.0.1:8300'
200+
)
167201
consul_client.health.service = CoroutineMock(
168202
return_value=self.consul_health_response
169203
)
@@ -177,7 +211,9 @@ async def async_test_deregister(loop):
177211

178212
await dc.deregister()
179213

180-
consul_client.catalog.service = CoroutineMock(return_value=(0, []))
214+
consul_client.catalog.service = CoroutineMock(
215+
return_value=(0, [])
216+
)
181217

182218
with self.assertRaises(IndexError):
183219
await dc.find_service('myapp')

tests/unit/test_client.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,6 @@ def test_register(self, MockConsul):
148148
consul_client.catalog.service = MagicMock(
149149
return_value=self.myapp_raw_response
150150
)
151-
consul_client.health.service = MagicMock(
152-
return_value=self.consul_health_response
153-
)
154151

155152
dc = client.Consul('localhost', 8500)
156153
dc.register('myapp', 5000)
@@ -186,9 +183,6 @@ def test_deregister(self, MockConsul):
186183
consul_client.catalog.service = MagicMock(
187184
return_value=self.myapp_raw_response
188185
)
189-
consul_client.health.service = MagicMock(
190-
return_value=self.consul_health_response
191-
)
192186

193187
dc = client.Consul('localhost', 8500)
194188
dc.register('myapp', 5000)

0 commit comments

Comments
 (0)