1717# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1818# See the License for the specific language governing permissions and
1919# limitations under the License.
20-
20+ from collections import OrderedDict
2121from unittest import TestCase
2222
2323from neo4j .bolt import ProtocolError
2424from neo4j .bolt .connection import connect
25- from neo4j .v1 .routing import OrderedSet , RoutingTable , RoutingConnectionPool
25+ from neo4j .v1 .routing import OrderedSet , RoutingTable , RoutingConnectionPool , LeastConnectedLoadBalancingStrategy , \
26+ RoundRobinLoadBalancingStrategy
2627from neo4j .v1 .security import basic_auth
27- from neo4j .v1 .api import Driver , READ_ACCESS , WRITE_ACCESS
28+ from neo4j .v1 .api import READ_ACCESS , WRITE_ACCESS
2829
2930
3031VALID_ROUTING_RECORD = {
@@ -56,7 +57,6 @@ def connector(address):
5657
5758
5859class RoundRobinSetTestCase (TestCase ):
59-
6060 def test_should_repr_as_set (self ):
6161 s = OrderedSet ([1 , 2 , 3 ])
6262 assert repr (s ) == "{1, 2, 3}"
@@ -135,15 +135,13 @@ def test_should_be_able_to_replace(self):
135135
136136
137137class RoutingTableConstructionTestCase (TestCase ):
138-
139138 def test_should_be_initially_stale (self ):
140139 table = RoutingTable ()
141140 assert not table .is_fresh (READ_ACCESS )
142141 assert not table .is_fresh (WRITE_ACCESS )
143142
144143
145144class RoutingTableParseRoutingInfoTestCase (TestCase ):
146-
147145 def test_should_return_routing_table_on_valid_record (self ):
148146 table = RoutingTable .parse_routing_info ([VALID_ROUTING_RECORD ])
149147 assert table .routers == {('127.0.0.1' , 9001 ), ('127.0.0.1' , 9002 ), ('127.0.0.1' , 9003 )}
@@ -172,7 +170,6 @@ def test_should_fail_on_multiple_records(self):
172170
173171
174172class RoutingTableFreshnessTestCase (TestCase ):
175-
176173 def test_should_be_fresh_after_update (self ):
177174 table = RoutingTable .parse_routing_info ([VALID_ROUTING_RECORD ])
178175 assert table .is_fresh (READ_ACCESS )
@@ -198,7 +195,6 @@ def test_should_become_stale_if_no_writers(self):
198195
199196
200197class RoutingTableUpdateTestCase (TestCase ):
201-
202198 def setUp (self ):
203199 self .table = RoutingTable (
204200 [("192.168.1.1" , 7687 ), ("192.168.1.2" , 7687 )], [("192.168.1.3" , 7687 )], [], 0 )
@@ -224,9 +220,119 @@ def test_update_should_replace_ttl(self):
224220
225221
226222class RoutingConnectionPoolConstructionTestCase (TestCase ):
227-
228223 def test_should_populate_initial_router (self ):
229224 initial_router = ("127.0.0.1" , 9001 )
230225 router = ("127.0.0.1" , 9002 )
231226 with RoutingConnectionPool (connector , initial_router , {}, router ) as pool :
232227 assert pool .routing_table .routers == {("127.0.0.1" , 9002 )}
228+
229+
230+ class FakeConnectionPool (object ):
231+
232+ def __init__ (self , addresses ):
233+ self ._addresses = addresses
234+
235+ def in_use_connection_count (self , address ):
236+ return self ._addresses .get (address , 0 )
237+
238+
239+ class RoundRobinLoadBalancingStrategyTestCase (TestCase ):
240+
241+ def test_simple_reader_selection (self ):
242+ strategy = RoundRobinLoadBalancingStrategy ()
243+ self .assertEqual (strategy .select_reader (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "0.0.0.0" )
244+ self .assertEqual (strategy .select_reader (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "1.1.1.1" )
245+ self .assertEqual (strategy .select_reader (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "2.2.2.2" )
246+ self .assertEqual (strategy .select_reader (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "0.0.0.0" )
247+
248+ def test_empty_reader_selection (self ):
249+ strategy = RoundRobinLoadBalancingStrategy ()
250+ self .assertIsNone (strategy .select_reader ([]))
251+
252+ def test_simple_writer_selection (self ):
253+ strategy = RoundRobinLoadBalancingStrategy ()
254+ self .assertEqual (strategy .select_writer (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "0.0.0.0" )
255+ self .assertEqual (strategy .select_writer (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "1.1.1.1" )
256+ self .assertEqual (strategy .select_writer (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "2.2.2.2" )
257+ self .assertEqual (strategy .select_writer (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "0.0.0.0" )
258+
259+ def test_empty_writer_selection (self ):
260+ strategy = RoundRobinLoadBalancingStrategy ()
261+ self .assertIsNone (strategy .select_writer ([]))
262+
263+
264+ class LeastConnectedLoadBalancingStrategyTestCase (TestCase ):
265+
266+ def test_simple_reader_selection (self ):
267+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
268+ ("0.0.0.0" , 2 ),
269+ ("1.1.1.1" , 1 ),
270+ ("2.2.2.2" , 0 ),
271+ ])))
272+ self .assertEqual (strategy .select_reader (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "2.2.2.2" )
273+
274+ def test_reader_selection_with_clash (self ):
275+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
276+ ("0.0.0.0" , 0 ),
277+ ("0.0.0.1" , 0 ),
278+ ("1.1.1.1" , 1 ),
279+ ])))
280+ self .assertEqual (strategy .select_reader (["0.0.0.0" , "0.0.0.1" , "1.1.1.1" ]), "0.0.0.0" )
281+ self .assertEqual (strategy .select_reader (["0.0.0.0" , "0.0.0.1" , "1.1.1.1" ]), "0.0.0.1" )
282+
283+ def test_empty_reader_selection (self ):
284+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
285+ ])))
286+ self .assertIsNone (strategy .select_reader ([]))
287+
288+ def test_not_in_pool_reader_selection (self ):
289+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
290+ ("1.1.1.1" , 1 ),
291+ ("2.2.2.2" , 2 ),
292+ ])))
293+ self .assertEqual (strategy .select_reader (["2.2.2.2" , "3.3.3.3" ]), "3.3.3.3" )
294+
295+ def test_partially_in_pool_reader_selection (self ):
296+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
297+ ("1.1.1.1" , 1 ),
298+ ("2.2.2.2" , 0 ),
299+ ])))
300+ self .assertEqual (strategy .select_reader (["2.2.2.2" , "3.3.3.3" ]), "2.2.2.2" )
301+ self .assertEqual (strategy .select_reader (["2.2.2.2" , "3.3.3.3" ]), "3.3.3.3" )
302+
303+ def test_simple_writer_selection (self ):
304+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
305+ ("0.0.0.0" , 2 ),
306+ ("1.1.1.1" , 1 ),
307+ ("2.2.2.2" , 0 ),
308+ ])))
309+ self .assertEqual (strategy .select_writer (["0.0.0.0" , "1.1.1.1" , "2.2.2.2" ]), "2.2.2.2" )
310+
311+ def test_writer_selection_with_clash (self ):
312+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
313+ ("0.0.0.0" , 0 ),
314+ ("0.0.0.1" , 0 ),
315+ ("1.1.1.1" , 1 ),
316+ ])))
317+ self .assertEqual (strategy .select_writer (["0.0.0.0" , "0.0.0.1" , "1.1.1.1" ]), "0.0.0.0" )
318+ self .assertEqual (strategy .select_writer (["0.0.0.0" , "0.0.0.1" , "1.1.1.1" ]), "0.0.0.1" )
319+
320+ def test_empty_writer_selection (self ):
321+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
322+ ])))
323+ self .assertIsNone (strategy .select_writer ([]))
324+
325+ def test_not_in_pool_writer_selection (self ):
326+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
327+ ("1.1.1.1" , 1 ),
328+ ("2.2.2.2" , 2 ),
329+ ])))
330+ self .assertEqual (strategy .select_writer (["2.2.2.2" , "3.3.3.3" ]), "3.3.3.3" )
331+
332+ def test_partially_in_pool_writer_selection (self ):
333+ strategy = LeastConnectedLoadBalancingStrategy (FakeConnectionPool (OrderedDict ([
334+ ("1.1.1.1" , 1 ),
335+ ("2.2.2.2" , 0 ),
336+ ])))
337+ self .assertEqual (strategy .select_writer (["2.2.2.2" , "3.3.3.3" ]), "2.2.2.2" )
338+ self .assertEqual (strategy .select_writer (["2.2.2.2" , "3.3.3.3" ]), "3.3.3.3" )
0 commit comments