1+ import time
2+
3+ import pytest
4+
15from cassandra .cluster import Cluster
26from cassandra .policies import ConstantReconnectionPolicy , RoundRobinPolicy , TokenAwarePolicy
37
48from tests .integration import PROTOCOL_VERSION , use_cluster
59from tests .unit .test_host_connection_pool import LOGGER
610
11+ CCM_CLUSTER = None
12+
713def setup_module ():
8- use_cluster ('tablets' , [3 ], start = True )
14+ global CCM_CLUSTER
15+
16+ CCM_CLUSTER = use_cluster ('tablets' , [3 ], start = True )
17+
918
1019class TestTabletsIntegration :
1120 @classmethod
@@ -14,22 +23,22 @@ def setup_class(cls):
1423 load_balancing_policy = TokenAwarePolicy (RoundRobinPolicy ()),
1524 reconnection_policy = ConstantReconnectionPolicy (1 ))
1625 cls .session = cls .cluster .connect ()
17- cls .create_ks_and_cf (cls )
26+ cls .create_ks_and_cf (cls . session )
1827 cls .create_data (cls .session )
1928
2029 @classmethod
2130 def teardown_class (cls ):
2231 cls .cluster .shutdown ()
2332
24- def verify_same_host_in_tracing (self , results ):
33+ def verify_hosts_in_tracing (self , results , expected ):
2534 traces = results .get_query_trace ()
2635 events = traces .events
2736 host_set = set ()
2837 for event in events :
2938 LOGGER .info ("TRACE EVENT: %s %s %s" , event .source , event .thread_name , event .description )
3039 host_set .add (event .source )
3140
32- assert len (host_set ) == 1
41+ assert len (host_set ) == expected
3342 assert 'locally' in "\n " .join ([event .description for event in events ])
3443
3544 trace_id = results .response_future .get_query_trace_ids ()[0 ]
@@ -40,9 +49,13 @@ def verify_same_host_in_tracing(self, results):
4049 LOGGER .info ("TRACE EVENT: %s %s" , event .source , event .activity )
4150 host_set .add (event .source )
4251
43- assert len (host_set ) == 1
52+ assert len (host_set ) == expected
4453 assert 'locally' in "\n " .join ([event .activity for event in events ])
4554
55+ def get_tablet_record (self , query ):
56+ metadata = self .session .cluster .metadata
57+ return metadata ._tablets .get_tablet_for_key (query .keyspace , query .table , metadata .token_map .token_class .from_key (query .routing_key ))
58+
4659 def verify_same_shard_in_tracing (self , results ):
4760 traces = results .get_query_trace ()
4861 events = traces .events
@@ -65,24 +78,25 @@ def verify_same_shard_in_tracing(self, results):
6578 assert len (shard_set ) == 1
6679 assert 'locally' in "\n " .join ([event .activity for event in events ])
6780
68- def create_ks_and_cf (self ):
69- self .session .execute (
81+ @classmethod
82+ def create_ks_and_cf (cls , session ):
83+ session .execute (
7084 """
7185 DROP KEYSPACE IF EXISTS test1
7286 """
7387 )
74- self . session .execute (
88+ session .execute (
7589 """
7690 CREATE KEYSPACE test1
7791 WITH replication = {
7892 'class': 'NetworkTopologyStrategy',
79- 'replication_factor': 1
93+ 'replication_factor': 2
8094 } AND tablets = {
8195 'initial': 8
8296 }
8397 """ )
8498
85- self . session .execute (
99+ session .execute (
86100 """
87101 CREATE TABLE test1.table1 (pk int, ck int, v int, PRIMARY KEY (pk, ck));
88102 """ )
@@ -120,7 +134,7 @@ def query_data_host_select(self, session, verify_in_tracing=True):
120134 results = session .execute (bound , trace = True )
121135 assert results == [(2 , 2 , 0 )]
122136 if verify_in_tracing :
123- self .verify_same_host_in_tracing (results )
137+ self .verify_hosts_in_tracing (results , 1 )
124138
125139 def query_data_shard_insert (self , session , verify_in_tracing = True ):
126140 prepared = session .prepare (
@@ -142,7 +156,7 @@ def query_data_host_insert(self, session, verify_in_tracing=True):
142156 bound = prepared .bind ([(52 ), (1 ), (2 )])
143157 results = session .execute (bound , trace = True )
144158 if verify_in_tracing :
145- self .verify_same_host_in_tracing (results )
159+ self .verify_hosts_in_tracing (results , 2 )
146160
147161 def test_tablets (self ):
148162 self .query_data_host_select (self .session )
@@ -151,3 +165,70 @@ def test_tablets(self):
151165 def test_tablets_shard_awareness (self ):
152166 self .query_data_shard_select (self .session )
153167 self .query_data_shard_insert (self .session )
168+
169+ def test_tablets_invalidation_drop_ks_while_reconnecting (self ):
170+ def recreate_while_reconnecting (_ ):
171+ # Kill control connection
172+ conn = self .session .cluster .control_connection ._connection
173+ self .session .cluster .control_connection ._connection = None
174+ conn .close ()
175+
176+ # Drop and recreate ks and table to trigger tablets invalidation
177+ self .create_ks_and_cf (self .cluster .connect ())
178+
179+ # Start control connection
180+ self .session .cluster .control_connection ._reconnect ()
181+
182+ self .run_tablets_invalidation_test (recreate_while_reconnecting )
183+
184+ def test_tablets_invalidation_drop_ks (self ):
185+ def drop_ks (_ ):
186+ # Drop and recreate ks and table to trigger tablets invalidation
187+ self .create_ks_and_cf (self .cluster .connect ())
188+ time .sleep (3 )
189+
190+ self .run_tablets_invalidation_test (drop_ks )
191+
192+ @pytest .mark .last
193+ def test_tablets_invalidation_decommission_non_cc_node (self ):
194+ def decommission_non_cc_node (rec ):
195+ # Drop and recreate ks and table to trigger tablets invalidation
196+ for node in CCM_CLUSTER .nodes .values ():
197+ if self .cluster .control_connection ._connection .endpoint .address == node .network_interfaces ["storage" ][0 ]:
198+ # Ignore node that control connection is connected to
199+ continue
200+ for replica in rec .replicas :
201+ if str (replica [0 ]) == str (node .node_hostid ):
202+ node .decommission ()
203+ break
204+ else :
205+ continue
206+ break
207+ else :
208+ assert False , "failed to find node to decommission"
209+ time .sleep (10 )
210+
211+ self .run_tablets_invalidation_test (decommission_non_cc_node )
212+
213+
214+ def run_tablets_invalidation_test (self , invalidate ):
215+ # Make sure driver holds tablet info
216+ # By landing query to the host that is not in replica set
217+ bound = self .session .prepare (
218+ """
219+ SELECT pk, ck, v FROM test1.table1 WHERE pk = ?
220+ """ ).bind ([(2 )])
221+
222+ rec = None
223+ for host in self .cluster .metadata .all_hosts ():
224+ self .session .execute (bound , host = host )
225+ rec = self .get_tablet_record (bound )
226+ if rec is not None :
227+ break
228+
229+ assert rec is not None , "failed to find tablet record"
230+
231+ invalidate (rec )
232+
233+ # Check if tablets information was purged
234+ assert self .get_tablet_record (bound ) is None , "tablet was not deleted, invalidation did not work"
0 commit comments