Skip to content

Commit 9e21370

Browse files
committed
token-map: rebuild on zero-token transitions
1 parent 5bfa9e1 commit 9e21370

3 files changed

Lines changed: 88 additions & 6 deletions

File tree

cassandra/cluster.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3919,8 +3919,10 @@ def _refresh_node_list_and_token_map(self, connection, preloaded_results=None,
39193919
host.dse_workloads = row.get("workloads")
39203920

39213921
tokens = row.get("tokens", None)
3922-
if partitioner and tokens and self._token_meta_enabled:
3923-
token_map[host] = tokens
3922+
if partitioner and self._token_meta_enabled:
3923+
should_rebuild_token_map |= self._tokens_changed(host, tokens)
3924+
if tokens:
3925+
token_map[host] = tokens
39243926
self._cluster.metadata.update_host(host, old_endpoint=endpoint)
39253927

39263928
for old_host_id, old_host in self._cluster.metadata.all_hosts_items():
@@ -3965,12 +3967,41 @@ def _is_valid_peer(row):
39653967

39663968
if "tokens" in row and not row.get("tokens"):
39673969
log.debug(
3968-
"Found a zero-token node - tokens is None (broadcast_rpc: %s, host_id: %s). Ignoring host." %
3970+
"Found a zero-token node (broadcast_rpc: %s, host_id: %s). "
3971+
"Keeping host for load balancing, but omitting it from the token map." %
39693972
(broadcast_rpc, host_id))
3970-
return False
39713973

39723974
return True
39733975

3976+
def _tokens_changed(self, host, tokens):
3977+
current_token_map = self._cluster.metadata.token_map
3978+
if current_token_map is None:
3979+
return False
3980+
3981+
if isinstance(current_token_map, dict):
3982+
current_tokens = current_token_map.get(host)
3983+
return set(current_tokens or ()) != set(tokens or ())
3984+
3985+
token_to_host_owner = getattr(current_token_map, 'token_to_host_owner', None)
3986+
token_class = getattr(current_token_map, 'token_class', None)
3987+
if token_to_host_owner is None or token_class is None:
3988+
return False
3989+
3990+
current_tokens = set(
3991+
token for token, owner in token_to_host_owner.items()
3992+
if owner == host)
3993+
if not tokens:
3994+
return bool(current_tokens)
3995+
3996+
try:
3997+
refreshed_tokens = set(token_class.from_string(token) for token in tokens)
3998+
except Exception:
3999+
log.debug("[control connection] Unable to compare refreshed tokens for %s",
4000+
host, exc_info=True)
4001+
return True
4002+
4003+
return current_tokens != refreshed_tokens
4004+
39744005
def _update_location_info(self, host, datacenter, rack):
39754006
if host.datacenter == datacenter and host.rack == rack:
39764007
return False

cassandra/policies.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,11 @@ class TokenAwarePolicy(LoadBalancingPolicy):
464464
465465
If no :attr:`~.Statement.routing_key` is set on the query, the child
466466
policy's query plan will be used as is.
467+
468+
Token awareness only applies to token-owning nodes. Zero-token CQL
469+
proxy or front-layer nodes are not replicas. When those nodes are the
470+
intended query front layer, configure a round-robin policy such as
471+
:class:`.RoundRobinPolicy` or :class:`.DCAwareRoundRobinPolicy` directly.
467472
"""
468473

469474
_child_policy = None

tests/unit/test_control_connection.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,6 @@ def refresh_and_validate_added_hosts():
406406
[None, None, "a", "dc1", "rack1", ["1", "101", "201"], 'uuid1'],
407407
["192.168.1.7", "10.0.0.1", "a", None, "rack1", ["1", "101", "201"], 'uuid2'],
408408
["192.168.1.6", "10.0.0.1", "a", "dc1", None, ["1", "101", "201"], 'uuid3'],
409-
["192.168.1.5", "10.0.0.1", "a", "dc1", "rack1", None, 'uuid4'],
410409
["192.168.1.4", "10.0.0.1", "a", "dc1", "rack1", ["1", "101", "201"], None]]])
411410
refresh_and_validate_added_hosts()
412411

@@ -420,10 +419,57 @@ def refresh_and_validate_added_hosts():
420419
[None, 9042, None, 7040, "a", "dc1", "rack1", ["2", "102", "202"], "uuid2"],
421420
["192.168.1.5", 9042, "10.0.0.2", 7040, "a", None, "rack1", ["2", "102", "202"], "uuid2"],
422421
["192.168.1.5", 9042, "10.0.0.2", 7040, "a", "dc1", None, ["2", "102", "202"], "uuid2"],
423-
["192.168.1.5", 9042, "10.0.0.2", 7040, "a", "dc1", "rack1", None, "uuid2"],
424422
["192.168.1.5", 9042, "10.0.0.2", 7040, "a", "dc1", "rack1", ["2", "102", "202"], None]]])
425423
refresh_and_validate_added_hosts()
426424

425+
def test_refresh_nodes_and_tokens_keeps_zero_token_peer_for_load_balancing(self):
426+
self.connection.peer_results[1].append(
427+
["192.168.1.3", "10.0.0.3", "a", "dc1", "rack1", None, "uuid4"]
428+
)
429+
self.cluster.scheduler.schedule = lambda delay, f, *args, **kwargs: f(*args, **kwargs)
430+
431+
self.control_connection.refresh_node_list_and_token_map()
432+
433+
zero_token_host = self.cluster.metadata.get_host_by_host_id("uuid4")
434+
assert zero_token_host is not None
435+
assert zero_token_host in self.cluster.metadata.all_hosts()
436+
assert zero_token_host not in self.cluster.metadata.token_map
437+
438+
def test_refresh_nodes_and_tokens_keeps_zero_token_local_host_for_load_balancing(self):
439+
self.connection.local_results[1][0][7] = None
440+
441+
self.control_connection.refresh_node_list_and_token_map()
442+
443+
local_host = self.cluster.metadata.get_host_by_host_id("uuid1")
444+
assert local_host is not None
445+
assert local_host in self.cluster.metadata.all_hosts()
446+
assert local_host not in self.cluster.metadata.token_map
447+
448+
def test_refresh_nodes_and_tokens_rebuilds_token_map_when_existing_host_loses_tokens(self):
449+
self.control_connection.refresh_node_list_and_token_map()
450+
451+
zero_token_host = self.cluster.metadata.get_host_by_host_id("uuid2")
452+
assert zero_token_host in self.cluster.metadata.token_map
453+
454+
self.connection.peer_results[1][0][5] = None
455+
self.control_connection.refresh_node_list_and_token_map()
456+
457+
assert zero_token_host in self.cluster.metadata.all_hosts()
458+
assert zero_token_host not in self.cluster.metadata.token_map
459+
460+
def test_refresh_nodes_and_tokens_rebuilds_token_map_when_existing_host_gains_tokens(self):
461+
self.connection.peer_results[1][0][5] = None
462+
self.control_connection.refresh_node_list_and_token_map()
463+
464+
token_host = self.cluster.metadata.get_host_by_host_id("uuid2")
465+
assert token_host in self.cluster.metadata.all_hosts()
466+
assert token_host not in self.cluster.metadata.token_map
467+
468+
self.connection.peer_results[1][0][5] = ["1", "101", "201"]
469+
self.control_connection.refresh_node_list_and_token_map()
470+
471+
assert token_host in self.cluster.metadata.token_map
472+
427473
def test_change_ip(self):
428474
"""
429475
Tests node IPs are updated while the nodes themselves are not

0 commit comments

Comments
 (0)