5
5
'''
6
6
7
7
import time
8
+ from itertools import cycle
9
+
8
10
from tarantool .connection import Connection
9
11
from tarantool .error import NetworkError
10
12
from tarantool .utils import ENCODING_DEFAULT
13
15
SOCKET_TIMEOUT ,
14
16
RECONNECT_MAX_ATTEMPTS ,
15
17
RECONNECT_DELAY ,
16
- DEFAULT_CLUSTER_DISCOVERY_DELAY_MILLIS ,
18
+ CLUSTER_DISCOVERY_DELAY ,
17
19
)
18
20
19
21
from tarantool .request import (
23
25
24
26
class RoundRobinStrategy (object ):
25
27
"""
26
- Simple roundrobin address rotation
28
+ Simple round-robin address rotation
27
29
"""
28
30
def __init__ (self , addrs ):
29
- self .addrs = addrs
30
- self .pos = 0
31
+ self .reload (addrs )
32
+
33
+ def reload (self , addrs ):
34
+ self .addrs = []
35
+ for i in addrs :
36
+ if i .get ("host" , None ) and i .get ("port" , None ) \
37
+ and isinstance (i ["port" ], int ):
38
+ if i in self .addrs :
39
+ continue
40
+ self .addrs .append (i )
41
+ self .__gen = cycle (self .addrs )
31
42
32
43
def getnext (self ):
33
- tmp = self .pos
34
- self .pos = (self .pos + 1 ) % len (self .addrs )
35
- return self .addrs [tmp ]
44
+ return next (self .__gen )
36
45
37
46
38
47
def parse_uri (uri_str ):
39
- if not uri_str or ':' not in uri_str :
40
- return None
41
- uri = uri_str .split (':' )
48
+ if not uri_str or uri_str . count ( ":" ) != 1 :
49
+ return
50
+ uri = uri_str .split (':' , 1 )
42
51
host = uri [0 ]
43
52
try :
44
53
port = int (uri [1 ])
45
54
except ValueError :
46
- return None
47
-
55
+ return
56
+
48
57
if host and port :
49
58
return {'host' : host , 'port' : port }
50
- else :
51
- return None
52
59
53
60
54
61
class MeshConnection (Connection ):
55
62
'''
56
- Represents a connection to a cluster of Tarantool servers.
63
+ Represents a connection to a cluster of Tarantool servers.
57
64
58
- This class uses Connection to connect to one of the nodes of the cluster.
59
- The initial list of nodes is passed to the constructor in 'addrs' parameter.
60
- The class set in 'strategy_class' parameter is used to select a node from
61
- the list and switch nodes in case of unavailability of the current node.
65
+ This class uses Connection to connect to one of the nodes of the cluster.
66
+ The initial list of nodes is passed to the constructor in 'addrs' parameter.
67
+ The class set in 'strategy_class' parameter is used to select a node from
68
+ the list and switch nodes in case of unavailability of the current node.
62
69
63
- 'get_nodes_function_name ' param of the constructor sets the name of a stored
64
- Lua function used to refresh the list of available nodes. The function takes
65
- no parameters and returns a list of strings in format 'host:port'. A generic
66
- function for getting the list of nodes looks like this:
70
+ 'cluster_discovery_function ' param of the constructor sets the name of a stored
71
+ Lua function used to refresh the list of available nodes. The function takes
72
+ no parameters and returns a list of strings in format 'host:port'. A generic
73
+ function for getting the list of nodes looks like this:
67
74
68
75
.. code-block:: lua
69
76
@@ -116,29 +123,27 @@ def __init__(self, host=None, port=None,
116
123
connection_timeout = CONNECTION_TIMEOUT ,
117
124
addrs = None ,
118
125
strategy_class = RoundRobinStrategy ,
119
- get_nodes_function_name = None ,
120
- nodes_refresh_interval = DEFAULT_CLUSTER_DISCOVERY_DELAY_MILLIS ):
126
+ cluster_discovery_function = None ,
127
+ cluster_discovery_delay = CLUSTER_DISCOVERY_DELAY ):
121
128
122
- addrs_list = []
129
+ if addrs is None :
130
+ addrs = []
123
131
124
132
if host and port :
125
- addrs_list .append ({'host' :host , 'port' :port })
126
-
127
- if addrs :
128
- for addr in addrs :
129
- if 'host' in addr and 'port' in addr :
130
- addrs_list .append ({'host' : addr ['host' ], 'port' : addr ['port' ]})
133
+ addrs .insert (0 , {'host' : host , 'port' : port })
131
134
132
135
self .strategy_class = strategy_class
133
- self .strategy = strategy_class (addrs_list )
134
-
135
- if not host and not port :
136
- addr = self .strategy .getnext ()
137
- host = addr ['host' ]
138
- port = addr ['port' ]
139
-
140
- self .get_nodes_function_name = get_nodes_function_name
141
- self .nodes_refresh_interval = nodes_refresh_interval
136
+ self .strategy = strategy_class (addrs )
137
+
138
+ if not self .strategy .addrs and connect_now :
139
+ raise ValueError ("Host/port or addrs list must be set" )
140
+
141
+ addr = self .strategy .getnext ()
142
+ host = addr ['host' ]
143
+ port = addr ['port' ]
144
+
145
+ self .cluster_discovery_function = cluster_discovery_function
146
+ self .cluster_discovery_delay = cluster_discovery_delay
142
147
self .last_nodes_refresh = time .time ()
143
148
super (MeshConnection , self ).__init__ (host = host ,
144
149
port = port ,
@@ -152,67 +157,75 @@ def __init__(self, host=None, port=None,
152
157
call_16 = call_16 ,
153
158
connection_timeout = connection_timeout )
154
159
160
+ def _opt_reconnect (self ):
161
+ '''
162
+ Use original opt_reconnect with address rotation
163
+ '''
164
+
165
+ for i in range (len (self .strategy .addrs )+ 1 ):
166
+ try :
167
+ super (MeshConnection , self )._opt_reconnect ()
168
+ except NetworkError :
169
+ addr = self .strategy .getnext ()
170
+ self .host = addr ["host" ]
171
+ self .port = addr ["port" ]
172
+ continue
173
+
174
+ if not self ._socket :
175
+ raise NetworkError
176
+
155
177
def _opt_refresh_instances (self ):
156
- """
178
+ '''
157
179
Refresh list of cluster instances.
158
180
If current connection not in server list will change connection.
159
- """
181
+ '''
160
182
now = time .time ()
161
183
162
- if self .connected and now - self .last_nodes_refresh > self .nodes_refresh_interval / 1000 :
163
- request = RequestCall (self , self .get_nodes_function_name , (), self .call_16 )
184
+ if not self .connected :
185
+ return
186
+
187
+ if now - self .last_nodes_refresh > self .cluster_discovery_delay :
188
+ request = RequestCall (self ,
189
+ self .cluster_discovery_function ,
190
+ (),
191
+ self .call_16 )
192
+
164
193
resp = self ._send_request_wo_reconnect (request )
165
194
166
195
# got data to refresh
167
196
if resp .data and resp .data [0 ]:
168
- addrs = list (parse_uri (i ) for i in resp .data [0 ])
169
- self .strategy = self .strategy_class (addrs )
170
- self .last_nodes_refresh = now
171
-
172
- if {'host' : self .host , 'port' : self .port } not in addrs :
173
- addr = self .strategy .getnext ()
174
- self .host = addr ['host' ]
175
- self .port = addr ['port' ]
176
- self .close ()
177
-
178
- if not self .connected :
179
-
180
- nattempts = (len (self .strategy .addrs ) * 2 ) + 1
181
-
182
- while nattempts >= 0 :
183
- try :
197
+ addrs = []
198
+ for i in resp .data [0 ]:
199
+ addr = parse_uri (i )
200
+ if addr :
201
+ addrs .append (addr )
202
+ if addrs :
203
+ self .strategy .reload (addrs )
204
+ self .last_nodes_refresh = now
205
+ else :
206
+ raise NetworkError ('No addresses to connect' )
207
+
208
+ # Check current addr in list
209
+ if {'host' : self .host , 'port' : self .port } not in addrs :
210
+ self .close ()
184
211
addr = self .strategy .getnext ()
185
- if addr ['host' ] != self .host or addr ['port' ] != self .port :
186
- self .host = addr ['host' ]
187
- self .port = addr ['port' ]
188
- self ._opt_reconnect ()
189
- break
190
- else :
191
- nattempts -= 1
192
- except NetworkError :
193
- continue
194
- else :
195
- raise NetworkError
212
+ self .host = addr ['host' ]
213
+ self .port = addr ['port' ]
214
+ self ._opt_reconnect ()
196
215
197
216
def _send_request (self , request ):
198
217
'''
199
218
Send the request to the server through the socket.
200
219
Return an instance of `Response` class.
201
220
202
- Update instances list from server `get_nodes_function_name ` function.
221
+ Update instances list from server `cluster_discovery_function ` function.
203
222
204
223
:param request: object representing a request
205
224
:type request: `Request` instance
206
225
207
226
:rtype: `Response` instance
208
227
'''
209
- if self .get_nodes_function_name :
228
+ if self .cluster_discovery_function :
210
229
self ._opt_refresh_instances ()
211
230
212
- try :
213
- return super (MeshConnection , self )._send_request (request )
214
- except NetworkError :
215
- self .connected = False
216
- self ._opt_refresh_instances ()
217
- finally :
218
- return super (MeshConnection , self )._send_request (request )
231
+ return super (MeshConnection , self )._send_request (request )
0 commit comments