Skip to content

Commit 8300726

Browse files
committed
policies: hide in-flight hosts from query plans
1 parent 882c276 commit 8300726

2 files changed

Lines changed: 67 additions & 13 deletions

File tree

cassandra/policies.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ def _is_ignored_zero_token_host(self, host):
143143
def _filter_zero_token_hosts(self, hosts):
144144
return tuple(h for h in hosts if not self._is_ignored_zero_token_host(h))
145145

146+
def _is_in_flight_host(self, host):
147+
return ((getattr(host, '_currently_handling_node_up', False) is True or
148+
getattr(host, '_currently_handling_node_addition', False) is True) and
149+
getattr(host, 'is_up', None) is not True)
150+
151+
def _is_ignored_query_plan_host(self, host):
152+
return self._is_ignored_zero_token_host(host) or self._is_in_flight_host(host)
153+
154+
def _filter_query_plan_hosts(self, hosts):
155+
return tuple(h for h in hosts if not self._is_ignored_query_plan_host(h))
156+
146157
def distance(self, host):
147158
"""
148159
Returns a measure of how remote a :class:`~.pool.Host` is in
@@ -211,7 +222,7 @@ def make_query_plan(self, working_keyspace=None, query=None):
211222
pos = self._position
212223
self._position += 1
213224

214-
hosts = self._filter_zero_token_hosts(self._live_hosts)
225+
hosts = self._filter_query_plan_hosts(self._live_hosts)
215226
length = len(hosts)
216227
if length:
217228
pos %= length
@@ -303,15 +314,15 @@ def make_query_plan(self, working_keyspace=None, query=None):
303314
pos = self._position
304315
self._position += 1
305316

306-
local_live = self._filter_zero_token_hosts(self._dc_live_hosts.get(self.local_dc, ()))
317+
local_live = self._filter_query_plan_hosts(self._dc_live_hosts.get(self.local_dc, ()))
307318
pos = (pos % len(local_live)) if local_live else 0
308319
for host in islice(cycle(local_live), pos, pos + len(local_live)):
309320
yield host
310321

311322
# the dict can change, so get candidate DCs iterating over keys of a copy
312323
other_dcs = [dc for dc in self._dc_live_hosts.copy().keys() if dc != self.local_dc]
313324
for dc in other_dcs:
314-
remote_live = self._filter_zero_token_hosts(self._dc_live_hosts.get(dc, ()))
325+
remote_live = self._filter_query_plan_hosts(self._dc_live_hosts.get(dc, ()))
315326
for host in remote_live[:self.used_hosts_per_remote_dc]:
316327
yield host
317328

@@ -422,14 +433,14 @@ def make_query_plan(self, working_keyspace=None, query=None):
422433
pos = self._position
423434
self._position += 1
424435

425-
local_rack_live = self._filter_zero_token_hosts(self._live_hosts.get((self.local_dc, self.local_rack), ()))
436+
local_rack_live = self._filter_query_plan_hosts(self._live_hosts.get((self.local_dc, self.local_rack), ()))
426437
pos = (pos % len(local_rack_live)) if local_rack_live else 0
427438
# Slice the cyclic iterator to start from pos and include the next len(local_live) elements
428439
# This ensures we get exactly one full cycle starting from pos
429440
for host in islice(cycle(local_rack_live), pos, pos + len(local_rack_live)):
430441
yield host
431442

432-
local_live = [host for host in self._filter_zero_token_hosts(self._dc_live_hosts.get(self.local_dc, ()))
443+
local_live = [host for host in self._filter_query_plan_hosts(self._dc_live_hosts.get(self.local_dc, ()))
433444
if host.rack != self.local_rack]
434445
pos = (pos % len(local_live)) if local_live else 0
435446
for host in islice(cycle(local_live), pos, pos + len(local_live)):
@@ -438,7 +449,7 @@ def make_query_plan(self, working_keyspace=None, query=None):
438449
# the dict can change, so get candidate DCs iterating over keys of a copy
439450
for dc, remote_live in self._dc_live_hosts.copy().items():
440451
if dc != self.local_dc:
441-
remote_live = self._filter_zero_token_hosts(remote_live)
452+
remote_live = self._filter_query_plan_hosts(remote_live)
442453
for host in remote_live[:self.used_hosts_per_remote_dc]:
443454
yield host
444455

@@ -531,7 +542,7 @@ def make_query_plan(self, working_keyspace=None, query=None):
531542
child = self._child_policy
532543
if query is None or query.routing_key is None or keyspace is None:
533544
for host in child.make_query_plan(keyspace, query):
534-
if not self._is_ignored_zero_token_host(host):
545+
if not self._is_ignored_query_plan_host(host):
535546
yield host
536547
return
537548

@@ -553,15 +564,15 @@ def make_query_plan(self, working_keyspace=None, query=None):
553564
def yield_in_order(hosts):
554565
for distance in [HostDistance.LOCAL_RACK, HostDistance.LOCAL, HostDistance.REMOTE]:
555566
for replica in hosts:
556-
if (not self._is_ignored_zero_token_host(replica) and
567+
if (not self._is_ignored_query_plan_host(replica) and
557568
replica.is_up and child.distance(replica) == distance):
558569
yield replica
559570

560571
# yield replicas: local_rack, local, remote
561572
yield from yield_in_order(replicas)
562573
# yield rest of the cluster: local_rack, local, remote
563574
yield from yield_in_order([host for host in child.make_query_plan(keyspace, query)
564-
if host not in replicas and not self._is_ignored_zero_token_host(host)])
575+
if host not in replicas and not self._is_ignored_query_plan_host(host)])
565576

566577
def on_up(self, *args, **kwargs):
567578
return self._child_policy.on_up(*args, **kwargs)
@@ -739,7 +750,7 @@ def make_query_plan(self, working_keyspace=None, query=None):
739750
working_keyspace=working_keyspace, query=query
740751
)
741752
for host in child_qp:
742-
if not self._is_ignored_zero_token_host(host) and self.predicate(host):
753+
if not self._is_ignored_query_plan_host(host) and self.predicate(host):
743754
yield host
744755

745756
def check_supported(self):
@@ -1390,14 +1401,14 @@ def make_query_plan(self, working_keyspace=None, query=None):
13901401
target_host = self._cluster_metadata.get_host(addr)
13911402

13921403
child = self._child_policy
1393-
if target_host and target_host.is_up and not self._is_ignored_zero_token_host(target_host):
1404+
if target_host and target_host.is_up and not self._is_ignored_query_plan_host(target_host):
13941405
yield target_host
13951406
for h in child.make_query_plan(keyspace, query):
1396-
if h != target_host and not self._is_ignored_zero_token_host(h):
1407+
if h != target_host and not self._is_ignored_query_plan_host(h):
13971408
yield h
13981409
else:
13991410
for h in child.make_query_plan(keyspace, query):
1400-
if not self._is_ignored_zero_token_host(h):
1411+
if not self._is_ignored_query_plan_host(h):
14011412
yield h
14021413

14031414

tests/unit/test_cluster.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,27 @@ def test_on_add_waits_for_all_session_pool_futures_before_marking_host_up(self):
155155
finally:
156156
cluster.shutdown()
157157

158+
def test_on_add_excludes_host_from_query_plan_until_pool_futures_complete(self):
159+
cluster = Cluster(protocol_version=4)
160+
host = Host("127.0.0.1", SimpleConvictionPolicy, datacenter="dc1", rack="rack1", host_id=uuid.uuid4())
161+
pending_future = Future()
162+
session = Mock()
163+
session.add_or_renew_pool.return_value = pending_future
164+
session.update_created_pools.return_value = set()
165+
cluster.sessions = [session]
166+
167+
try:
168+
cluster.on_add(host, refresh_nodes=False)
169+
170+
load_balancer = cluster.profile_manager.default.load_balancing_policy
171+
assert host not in list(load_balancer.make_query_plan())
172+
173+
pending_future.set_result(True)
174+
175+
assert list(load_balancer.make_query_plan()) == [host]
176+
finally:
177+
cluster.shutdown()
178+
158179
def test_on_up_waits_for_all_session_pool_futures_before_marking_host_up(self):
159180
cluster = Cluster(protocol_version=4)
160181
host = Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())
@@ -186,6 +207,28 @@ def test_on_up_waits_for_all_session_pool_futures_before_marking_host_up(self):
186207
finally:
187208
cluster.shutdown()
188209

210+
def test_on_up_excludes_host_from_query_plan_until_pool_futures_complete(self):
211+
cluster = Cluster(protocol_version=4)
212+
host = Host("127.0.0.1", SimpleConvictionPolicy, datacenter="dc1", rack="rack1", host_id=uuid.uuid4())
213+
host.set_down()
214+
pending_future = Future()
215+
session = Mock()
216+
session.add_or_renew_pool.return_value = pending_future
217+
session.update_created_pools.return_value = set()
218+
cluster.sessions = [session]
219+
220+
try:
221+
cluster.on_up(host)
222+
223+
load_balancer = cluster.profile_manager.default.load_balancing_policy
224+
assert host not in list(load_balancer.make_query_plan())
225+
226+
pending_future.set_result(True)
227+
228+
assert list(load_balancer.make_query_plan()) == [host]
229+
finally:
230+
cluster.shutdown()
231+
189232
def test_invalid_contact_point_types(self):
190233
with pytest.raises(ValueError):
191234
Cluster(contact_points=[None], protocol_version=4, connect_timeout=1)

0 commit comments

Comments
 (0)