Skip to content

Commit b8a61d9

Browse files
committed
Fix etcd.Client.machines
In our old version of the code, etcd.Client.api_execute called Client.machines in case of failure, which called api_execute in return. In the scenario of a client with a list of possible servers, and the first of the list being down, we throw an exception right now, because of this. Using directly urllib3 and skipping api_execute in Client.machines allows us to connect to the first available server. Closes jplana#51
1 parent 00a0084 commit b8a61d9

3 files changed

Lines changed: 29 additions & 26 deletions

File tree

src/etcd/client.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,11 @@ def uri(protocol, host, port):
121121
# we need the set of servers in the cluster in order to try
122122
# reconnecting upon error.
123123
self._machines_cache = self.machines
124-
self._machines_cache.remove(self._base_uri)
124+
if self._base_uri in self._machines_cache:
125+
self._machines_cache.remove(self._base_uri)
126+
else:
127+
# This happens if the code includes the hostname and not the ip, or vice-versa
128+
self._base_uri = self._machines_cache.pop(0)
125129
else:
126130
self._machines_cache = []
127131

@@ -166,11 +170,27 @@ def machines(self):
166170
>>> print client.machines
167171
['http://127.0.0.1:4001', 'http://127.0.0.1:4002']
168172
"""
169-
return [
170-
node.strip() for node in self.api_execute(
171-
self.version_prefix + '/machines',
172-
self._MGET).data.decode('utf-8').split(',')
173-
]
173+
# We can't use api_execute here, or it causes a logical loop
174+
try:
175+
uri = self._base_uri + self.version_prefix + '/machines'
176+
response = self.http.request(
177+
self._MGET,
178+
uri,
179+
timeout=self.read_timeout,
180+
redirect=self.allow_redirect)
181+
182+
return [
183+
node.strip() for node in
184+
self._handle_server_response(response).data.decode('utf-8').split(',')
185+
]
186+
except:
187+
# We can't get the list of machines, if one server is in the machines cache, try on it
188+
if self._machines_cache:
189+
self._base_uri = self._machines_cache.pop(0)
190+
# Call myself
191+
return self.machines
192+
else:
193+
raise etcd.EtcdException("Could not get the list of servers, maybe you provided the wrong host(s) to connect to?")
174194

175195
@property
176196
def leader(self):

src/etcd/tests/unit/test_old_request.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,6 @@ def getheaders(self):
1919

2020
class TestClientRequest(unittest.TestCase):
2121

22-
def test_machines(self):
23-
""" Can request machines """
24-
client = etcd.Client()
25-
client.api_execute = mock.Mock(
26-
return_value=FakeHTTPResponse(200, data=
27-
"http://127.0.0.1:4002,"
28-
" http://127.0.0.1:4001,"
29-
" http://127.0.0.1:4003,"
30-
" http://127.0.0.1:4001")
31-
)
32-
33-
assert client.machines == [
34-
'http://127.0.0.1:4002',
35-
'http://127.0.0.1:4001',
36-
'http://127.0.0.1:4003',
37-
'http://127.0.0.1:4001'
38-
]
3922

4023
def test_leader(self):
4124
""" Can request the leader """

src/etcd/tests/unit/test_request.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ class TestClientApiInterface(TestClientApiBase):
105105
106106
If a test should be run only in this class, please override the method there.
107107
"""
108-
109-
def test_machines(self):
108+
@mock.patch('urllib3.request.RequestMethods.request')
109+
def test_machines(self, mocker):
110110
""" Can request machines """
111111
data = ['http://127.0.0.1:4001',
112112
'http://127.0.0.1:4002', 'http://127.0.0.1:4003']
113113
d = ','.join(data)
114-
self._mock_api(200, d)
114+
mocker.return_value = self._prepare_response(200, d)
115115
self.assertEquals(data, self.client.machines)
116116

117117
def test_leader(self):

0 commit comments

Comments
 (0)