11
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
+ import uuid
14
15
15
16
try :
16
17
import unittest2 as unittest
17
18
except ImportError :
18
19
import unittest # noqa
19
20
21
+ import time
20
22
import logging
21
23
from mock import MagicMock
22
24
from concurrent .futures import ThreadPoolExecutor
23
25
24
- from cassandra .cluster import ShardAwareOptions
26
+ from cassandra .cluster import ShardAwareOptions , _Scheduler
27
+ from cassandra .policies import ConstantReconnectionPolicy , \
28
+ NoDelayShardReconnectionPolicy , NoConcurrentShardReconnectionPolicy , ShardReconnectionPolicyScope
25
29
from cassandra .pool import HostConnection , HostDistance
26
30
from cassandra .connection import ShardingInfo , DefaultEndPoint
27
31
from cassandra .metadata import Murmur3Token
@@ -53,7 +57,15 @@ class OptionsHolder(object):
53
57
self .assertEqual (shard_info .shard_id_from_token (Murmur3Token .from_key (b"e" ).value ), 4 )
54
58
self .assertEqual (shard_info .shard_id_from_token (Murmur3Token .from_key (b"100000" ).value ), 2 )
55
59
56
- def test_advanced_shard_aware_port (self ):
60
+ def test_shard_aware_reconnection_policy_no_delay (self ):
61
+ # with NoDelayReconnectionPolicy all the connections should be created right away
62
+ self ._test_shard_aware_reconnection_policy (4 , NoDelayShardReconnectionPolicy (), 4 , 4 )
63
+
64
+ def test_shard_aware_reconnection_policy_delay (self ):
65
+ # with ConstantReconnectionPolicy first connection is created right away, others are delayed
66
+ self ._test_shard_aware_reconnection_policy (4 , NoConcurrentShardReconnectionPolicy (ShardReconnectionPolicyScope .Cluster , ConstantReconnectionPolicy (1 )), 1 , 4 )
67
+
68
+ def _test_shard_aware_reconnection_policy (self , shard_count , shard_reconnection_policy , expected_count , expected_after ):
57
69
"""
58
70
Test that on given a `shard_aware_port` on the OPTIONS message (ShardInfo class)
59
71
the next connections would be open using this port
@@ -71,17 +83,25 @@ def __init__(self, is_ssl=False, *args, **kwargs):
71
83
self .cluster .ssl_options = None
72
84
self .cluster .shard_aware_options = ShardAwareOptions ()
73
85
self .cluster .executor = ThreadPoolExecutor (max_workers = 2 )
86
+ self ._executor_submit_original = self .cluster .executor .submit
87
+ self .cluster .executor .submit = self ._executor_submit
88
+ self .cluster .scheduler = _Scheduler (self .cluster .executor )
74
89
self .cluster .signal_connection_failure = lambda * args , ** kwargs : False
75
90
self .cluster .connection_factory = self .mock_connection_factory
76
91
self .connection_counter = 0
92
+ self .shard_reconnection_scheduler = shard_reconnection_policy .new_scheduler (self )
77
93
self .futures = []
78
94
79
95
def submit (self , fn , * args , ** kwargs ):
96
+ if self .is_shutdown :
97
+ return None
98
+ return self .cluster .executor .submit (fn , * args , ** kwargs )
99
+
100
+ def _executor_submit (self , fn , * args , ** kwargs ):
80
101
logging .info ("Scheduling %s with args: %s, kwargs: %s" , fn , args , kwargs )
81
- if not self .is_shutdown :
82
- f = self .cluster .executor .submit (fn , * args , ** kwargs )
83
- self .futures += [f ]
84
- return f
102
+ f = self ._executor_submit_original (fn , * args , ** kwargs )
103
+ self .futures += [f ]
104
+ return f
85
105
86
106
def mock_connection_factory (self , * args , ** kwargs ):
87
107
connection = MagicMock ()
@@ -90,26 +110,50 @@ def mock_connection_factory(self, *args, **kwargs):
90
110
connection .is_closed = False
91
111
connection .orphaned_threshold_reached = False
92
112
connection .endpoint = args [0 ]
93
- sharding_info = ShardingInfo (shard_id = 1 , shards_count = 4 , partitioner = "" , sharding_algorithm = "" , sharding_ignore_msb = 0 , shard_aware_port = 19042 , shard_aware_port_ssl = 19045 )
113
+ sharding_info = ShardingInfo (shard_id = 1 , shards_count = shard_count , partitioner = "" , sharding_algorithm = "" , sharding_ignore_msb = 0 , shard_aware_port = 19042 , shard_aware_port_ssl = 19045 )
94
114
connection .features = ProtocolFeatures (shard_id = kwargs .get ('shard_id' , self .connection_counter ), sharding_info = sharding_info )
95
115
self .connection_counter += 1
96
116
97
117
return connection
98
118
99
119
host = MagicMock ()
120
+ host .host_id = uuid .uuid4 ()
100
121
host .endpoint = DefaultEndPoint ("1.2.3.4" )
122
+ session = None
123
+ reconnection_policy = None
124
+ if isinstance (shard_reconnection_policy , NoConcurrentShardReconnectionPolicy ):
125
+ reconnection_policy = shard_reconnection_policy .reconnection_policy
126
+ try :
127
+ for port , is_ssl in [(19042 , False ), (19045 , True )]:
128
+ session = MockSession (is_ssl = is_ssl )
129
+ pool = HostConnection (host = host , host_distance = HostDistance .REMOTE , session = session )
130
+ for f in session .futures :
131
+ f .result ()
132
+ assert len (pool ._connections ) == expected_count
133
+ for shard_id , connection in pool ._connections .items ():
134
+ assert connection .features .shard_id == shard_id
135
+ if shard_id == 0 :
136
+ assert connection .endpoint == DefaultEndPoint ("1.2.3.4" )
137
+ else :
138
+ assert connection .endpoint == DefaultEndPoint ("1.2.3.4" , port = port )
101
139
102
- for port , is_ssl in [(19042 , False ), (19045 , True )]:
103
- session = MockSession (is_ssl = is_ssl )
104
- pool = HostConnection (host = host , host_distance = HostDistance .REMOTE , session = session )
105
- for f in session .futures :
106
- f .result ()
107
- assert len (pool ._connections ) == 4
108
- for shard_id , connection in pool ._connections .items ():
109
- assert connection .features .shard_id == shard_id
110
- if shard_id == 0 :
111
- assert connection .endpoint == DefaultEndPoint ("1.2.3.4" )
112
- else :
113
- assert connection .endpoint == DefaultEndPoint ("1.2.3.4" , port = port )
140
+ sleep_time = 0
141
+ if reconnection_policy :
142
+ # Check that connections to shards are being established according to the policy
143
+ # Calculate total time it will need to establish all connections
144
+ # Sleep half of the time and check that connections are not there yet
145
+ # Sleep rest of the time + 1 second and check that all connections has been established
146
+ schedule = reconnection_policy .new_schedule ()
147
+ for _ in range (shard_count ):
148
+ sleep_time += next (schedule )
149
+ if sleep_time > 0 :
150
+ time .sleep (sleep_time / 2 )
151
+ # Check that connection are not being established quicker than expected
152
+ assert len (pool ._connections ) < expected_after
153
+ time .sleep (sleep_time / 2 + 1 )
114
154
115
- session .cluster .executor .shutdown (wait = True )
155
+ assert len (pool ._connections ) == expected_after
156
+ finally :
157
+ if session :
158
+ session .cluster .scheduler .shutdown ()
159
+ session .cluster .executor .shutdown (wait = True )
0 commit comments