Skip to content

Commit 3202302

Browse files
sylwiaszunejkodkropachev
authored andcommitted
Integrate client routes handler into Cluster and ControlConnection
Cluster: - Add client_routes_config parameter with mutual exclusivity check against endpoint_factory - Create _ClientRoutesHandler and ClientRoutesEndPointFactory when client_routes_config is provided ControlConnection: - Register CLIENT_ROUTES_CHANGE event watcher when handler is present - Forward events to handler via _handle_client_routes_change - Trigger full route re-read on control connection reconnection
1 parent 1e0e6ca commit 3202302

1 file changed

Lines changed: 98 additions & 5 deletions

File tree

cassandra/cluster.py

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from itertools import groupby, count, chain
3030
import json
3131
import logging
32-
from typing import Optional, Union
32+
from typing import Any, Dict, Optional, Union
3333
from warnings import warn
3434
from random import random
3535
import re
@@ -48,7 +48,8 @@
4848
SchemaTargetType, DriverException, ProtocolVersion,
4949
UnresolvableContactPoints, DependencyException)
5050
from cassandra.auth import _proxy_execute_key, PlainTextAuthProvider
51-
from cassandra.connection import (ConnectionException, ConnectionShutdown,
51+
from cassandra.client_routes import ClientRoutesChangeType, ClientRoutesConfig, _ClientRoutesHandler
52+
from cassandra.connection import (ClientRoutesEndPointFactory, ConnectionException, ConnectionShutdown,
5253
ConnectionHeartbeat, ProtocolVersionUnsupported,
5354
EndPoint, DefaultEndPoint, DefaultEndPointFactory,
5455
SniEndPointFactory, ConnectionBusy, locally_supported_compressions)
@@ -1215,7 +1216,8 @@ def __init__(self,
12151216
shard_aware_options=None,
12161217
metadata_request_timeout: Optional[float] = None,
12171218
column_encryption_policy=None,
1218-
application_info:Optional[ApplicationInfoBase]=None
1219+
application_info:Optional[ApplicationInfoBase]=None,
1220+
client_routes_config:Optional[ClientRoutesConfig]=None
12191221
):
12201222
"""
12211223
``executor_threads`` defines the number of threads in a pool for handling asynchronous tasks such as
@@ -1280,6 +1282,45 @@ def __init__(self,
12801282
if column_encryption_policy is not None:
12811283
self.column_encryption_policy = column_encryption_policy
12821284

1285+
if client_routes_config is not None and endpoint_factory is not None:
1286+
raise ValueError("client_routes_config and endpoint_factory are mutually exclusive")
1287+
1288+
self._client_routes_handler = None
1289+
if client_routes_config is not None:
1290+
if not isinstance(client_routes_config, ClientRoutesConfig):
1291+
raise TypeError("client_routes_config must be a ClientRoutesConfig instance")
1292+
1293+
# SSL hostname verification is incompatible with client routes:
1294+
# connections go through NLB proxies whose addresses won't match
1295+
# server certificates.
1296+
_check_hostname_enabled = False
1297+
if ssl_context is not None and ssl_context.check_hostname:
1298+
_check_hostname_enabled = True
1299+
if ssl_options is not None and ssl_options.get('check_hostname', False):
1300+
_check_hostname_enabled = True
1301+
if _check_hostname_enabled:
1302+
raise ValueError(
1303+
"SSL hostname verification (check_hostname=True) is currently incompatible "
1304+
"with client_routes_config. When using client routes, connections "
1305+
"go through NLB proxies whose addresses won't match server "
1306+
"certificates. Disable hostname verification by setting "
1307+
"ssl_context.check_hostname = False."
1308+
)
1309+
1310+
ssl_enabled = ssl_context is not None or ssl_options is not None
1311+
self._client_routes_handler = _ClientRoutesHandler(client_routes_config, ssl_enabled=ssl_enabled)
1312+
1313+
if contact_points is _NOT_SET or not self._contact_points_explicit:
1314+
seed_addrs = [dep.connection_addr_override for dep in client_routes_config.proxies
1315+
if dep.connection_addr_override]
1316+
if seed_addrs:
1317+
self.contact_points = seed_addrs
1318+
self._contact_points_explicit = True
1319+
log.info("[client routes] Using %d deployment connection addresses as contact points",
1320+
len(seed_addrs))
1321+
1322+
if self._client_routes_handler is not None:
1323+
endpoint_factory = ClientRoutesEndPointFactory(self._client_routes_handler, self.port)
12831324
self.endpoint_factory = endpoint_factory or DefaultEndPointFactory(port=self.port)
12841325
self.endpoint_factory.configure(self)
12851326

@@ -1437,6 +1478,10 @@ def __init__(self,
14371478
self.monitor_reporting_interval = monitor_reporting_interval
14381479
self.shard_aware_options = ShardAwareOptions(opts=shard_aware_options)
14391480

1481+
if (client_routes_config is not None
1482+
and not client_routes_config.advanced_shard_awareness):
1483+
self.shard_aware_options.disable_shardaware_port = True
1484+
14401485
self._listeners = set()
14411486
self._listener_lock = Lock()
14421487

@@ -3612,11 +3657,21 @@ def _try_connect(self, endpoint):
36123657
# this object (after a dereferencing a weakref)
36133658
self_weakref = weakref.ref(self, partial(_clear_watcher, weakref.proxy(connection)))
36143659
try:
3615-
connection.register_watchers({
3660+
watchers = {
36163661
"TOPOLOGY_CHANGE": partial(_watch_callback, self_weakref, '_handle_topology_change'),
36173662
"STATUS_CHANGE": partial(_watch_callback, self_weakref, '_handle_status_change'),
36183663
"SCHEMA_CHANGE": partial(_watch_callback, self_weakref, '_handle_schema_change')
3619-
}, register_timeout=self._timeout)
3664+
}
3665+
3666+
if self._cluster._client_routes_handler is not None:
3667+
watchers["CLIENT_ROUTES_CHANGE"] = partial(_watch_callback, self_weakref, '_handle_client_routes_change')
3668+
3669+
connection.register_watchers(watchers, register_timeout=self._timeout)
3670+
3671+
if self._cluster._client_routes_handler is not None:
3672+
self._cluster._client_routes_handler.initialize(
3673+
connection,
3674+
self._timeout)
36203675

36213676
sel_peers = self._get_peers_query(self.PeersQueryType.PEERS, connection)
36223677
sel_local = self._SELECT_LOCAL if self._token_meta_enabled else self._SELECT_LOCAL_NO_TOKENS
@@ -3979,6 +4034,44 @@ def _handle_status_change(self, event):
39794034
# this will be run by the scheduler
39804035
self._cluster.on_down(host, is_host_addition=False)
39814036

4037+
def _handle_client_routes_change(self, event: Dict[str, Any]) -> None:
4038+
"""
4039+
Handle CLIENT_ROUTES_CHANGE event from the server.
4040+
4041+
This event indicates that the system.client_routes table has been updated
4042+
and we need to refresh our route mappings.
4043+
"""
4044+
if self._cluster._client_routes_handler is None:
4045+
log.warning("[control connection] Received CLIENT_ROUTES_CHANGE but no handler configured")
4046+
return
4047+
4048+
raw_change_type = event.get("change_type")
4049+
try:
4050+
change_type = ClientRoutesChangeType(raw_change_type)
4051+
except ValueError:
4052+
log.warning("[control connection] Unknown CLIENT_ROUTES_CHANGE type: %s", raw_change_type)
4053+
return
4054+
4055+
connection_ids = tuple(event.get("connection_ids", []))
4056+
host_ids = tuple(event.get("host_ids", []))
4057+
4058+
self._cluster.scheduler.schedule_unique(
4059+
0,
4060+
self._handle_client_routes_refresh,
4061+
self._connection, self._timeout, change_type, connection_ids, host_ids
4062+
)
4063+
4064+
def _handle_client_routes_refresh(self, connection, timeout,
4065+
change_type, connection_ids, host_ids):
4066+
try:
4067+
self._cluster._client_routes_handler.handle_client_routes_change(
4068+
connection, timeout, change_type, connection_ids, host_ids)
4069+
except ReferenceError:
4070+
pass # our weak reference to the Cluster is no good
4071+
except Exception:
4072+
log.debug("[control connection] Error handling CLIENT_ROUTES_CHANGE", exc_info=True)
4073+
self._signal_error()
4074+
39824075
def _handle_schema_change(self, event):
39834076
if self._schema_event_refresh_window < 0:
39844077
return

0 commit comments

Comments
 (0)