|
|
|
|
@@ -336,6 +336,7 @@ class Cluster(object):
|
|
|
|
|
"""
|
|
|
|
|
The maximum duration (in seconds) that the driver will wait for schema
|
|
|
|
|
agreement across the cluster. Defaults to ten seconds.
|
|
|
|
|
If set <= 0, the driver will bypass schema agreement waits altogether.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
metadata = None
|
|
|
|
|
@@ -1044,14 +1045,26 @@ class Cluster(object):
|
|
|
|
|
for pool in session._pools.values():
|
|
|
|
|
pool.ensure_core_connections()
|
|
|
|
|
|
|
|
|
|
def submit_schema_refresh(self, keyspace=None, table=None):
|
|
|
|
|
def refresh_schema(self, keyspace=None, table=None, usertype=None, schema_agreement_wait=None):
|
|
|
|
|
"""
|
|
|
|
|
Synchronously refresh the schema metadata.
|
|
|
|
|
By default timeout for this operation is governed by :attr:`~.Cluster.max_schema_agreement_wait`
|
|
|
|
|
and :attr:`~.Cluster.control_connection_timeout`.
|
|
|
|
|
Passing schema_agreement_wait here overrides :attr:`~.Cluster.max_schema_agreement_wait`.
|
|
|
|
|
Setting schema_agreement_wait <= 0 will bypass schema agreement and refresh schema immediately.
|
|
|
|
|
An Exception is raised if schema refresh fails for any reason.
|
|
|
|
|
"""
|
|
|
|
|
if not self.control_connection.refresh_schema(keyspace, table, usertype, schema_agreement_wait):
|
|
|
|
|
raise Exception("Schema was not refreshed. See log for details.")
|
|
|
|
|
|
|
|
|
|
def submit_schema_refresh(self, keyspace=None, table=None, usertype=None):
|
|
|
|
|
"""
|
|
|
|
|
Schedule a refresh of the internal representation of the current
|
|
|
|
|
schema for this cluster. If `keyspace` is specified, only that
|
|
|
|
|
keyspace will be refreshed, and likewise for `table`.
|
|
|
|
|
"""
|
|
|
|
|
return self.executor.submit(
|
|
|
|
|
self.control_connection.refresh_schema, keyspace, table)
|
|
|
|
|
self.control_connection.refresh_schema, keyspace, table, usertype)
|
|
|
|
|
|
|
|
|
|
def _prepare_all_queries(self, host):
|
|
|
|
|
if not self._prepared_statements:
|
|
|
|
|
@@ -1810,6 +1823,9 @@ class ControlConnection(object):
|
|
|
|
|
|
|
|
|
|
self._refresh_node_list_and_token_map(connection, preloaded_results=shared_results)
|
|
|
|
|
self._refresh_schema(connection, preloaded_results=shared_results)
|
|
|
|
|
if not self._cluster.metadata.keyspaces:
|
|
|
|
|
log.warning("[control connection] No schema built on connect; retrying without wait for schema agreement")
|
|
|
|
|
self._refresh_schema(connection, preloaded_results=shared_results, schema_agreement_wait=0)
|
|
|
|
|
except Exception:
|
|
|
|
|
connection.close()
|
|
|
|
|
raise
|
|
|
|
|
@@ -1883,26 +1899,32 @@ class ControlConnection(object):
|
|
|
|
|
self._connection.close()
|
|
|
|
|
del self._connection
|
|
|
|
|
|
|
|
|
|
def refresh_schema(self, keyspace=None, table=None, usertype=None):
|
|
|
|
|
def refresh_schema(self, keyspace=None, table=None, usertype=None,
|
|
|
|
|
schema_agreement_wait=None):
|
|
|
|
|
try:
|
|
|
|
|
if self._connection:
|
|
|
|
|
self._refresh_schema(self._connection, keyspace, table, usertype)
|
|
|
|
|
return self._refresh_schema(self._connection, keyspace, table, usertype,
|
|
|
|
|
schema_agreement_wait=schema_agreement_wait)
|
|
|
|
|
except ReferenceError:
|
|
|
|
|
pass # our weak reference to the Cluster is no good
|
|
|
|
|
except Exception:
|
|
|
|
|
log.debug("[control connection] Error refreshing schema", exc_info=True)
|
|
|
|
|
self._signal_error()
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _refresh_schema(self, connection, keyspace=None, table=None, usertype=None, preloaded_results=None):
|
|
|
|
|
def _refresh_schema(self, connection, keyspace=None, table=None, usertype=None,
|
|
|
|
|
preloaded_results=None, schema_agreement_wait=None):
|
|
|
|
|
if self._cluster.is_shutdown:
|
|
|
|
|
return
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
assert table is None or usertype is None
|
|
|
|
|
|
|
|
|
|
agreed = self.wait_for_schema_agreement(connection, preloaded_results=preloaded_results)
|
|
|
|
|
agreed = self.wait_for_schema_agreement(connection,
|
|
|
|
|
preloaded_results=preloaded_results,
|
|
|
|
|
wait_time=schema_agreement_wait)
|
|
|
|
|
if not agreed:
|
|
|
|
|
log.debug("Skipping schema refresh due to lack of schema agreement")
|
|
|
|
|
return
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
cl = ConsistencyLevel.ONE
|
|
|
|
|
if table:
|
|
|
|
|
@@ -1918,7 +1940,7 @@ class ControlConnection(object):
|
|
|
|
|
col_query = QueryMessage(query=self._SELECT_COLUMNS + where_clause, consistency_level=cl)
|
|
|
|
|
triggers_query = QueryMessage(query=self._SELECT_TRIGGERS + where_clause, consistency_level=cl)
|
|
|
|
|
(cf_success, cf_result), (col_success, col_result), (triggers_success, triggers_result) \
|
|
|
|
|
= connection.wait_for_responses(cf_query, col_query, triggers_query, fail_on_error=False)
|
|
|
|
|
= connection.wait_for_responses(cf_query, col_query, triggers_query, timeout=self._timeout, fail_on_error=False)
|
|
|
|
|
|
|
|
|
|
log.debug("[control connection] Fetched table info for %s.%s, rebuilding metadata", keyspace, table)
|
|
|
|
|
cf_result = _handle_results(cf_success, cf_result)
|
|
|
|
|
@@ -1957,7 +1979,7 @@ class ControlConnection(object):
|
|
|
|
|
QueryMessage(query=self._SELECT_TRIGGERS, consistency_level=cl)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
responses = connection.wait_for_responses(*queries, fail_on_error=False)
|
|
|
|
|
responses = connection.wait_for_responses(*queries, timeout=self._timeout, fail_on_error=False)
|
|
|
|
|
(ks_success, ks_result), (cf_success, cf_result), \
|
|
|
|
|
(col_success, col_result), (types_success, types_result), \
|
|
|
|
|
(trigger_success, triggers_result) = responses
|
|
|
|
|
@@ -1985,8 +2007,8 @@ class ControlConnection(object):
|
|
|
|
|
log.debug("[control connection] triggers table not found")
|
|
|
|
|
triggers_result = {}
|
|
|
|
|
elif isinstance(triggers_result, Unauthorized):
|
|
|
|
|
log.warn("[control connection] this version of Cassandra does not allow access to schema_triggers metadata with authorization enabled (CASSANDRA-7967); "
|
|
|
|
|
"The driver will operate normally, but will not reflect triggers in the local metadata model, or schema strings.")
|
|
|
|
|
log.warning("[control connection] this version of Cassandra does not allow access to schema_triggers metadata with authorization enabled (CASSANDRA-7967); "
|
|
|
|
|
"The driver will operate normally, but will not reflect triggers in the local metadata model, or schema strings.")
|
|
|
|
|
triggers_result = {}
|
|
|
|
|
else:
|
|
|
|
|
raise triggers_result
|
|
|
|
|
@@ -2003,6 +2025,7 @@ class ControlConnection(object):
|
|
|
|
|
|
|
|
|
|
log.debug("[control connection] Fetched schema, rebuilding metadata")
|
|
|
|
|
self._cluster.metadata.rebuild_schema(ks_result, types_result, cf_result, col_result, triggers_result)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def refresh_node_list_and_token_map(self, force_token_rebuild=False):
|
|
|
|
|
try:
|
|
|
|
|
@@ -2063,7 +2086,7 @@ class ControlConnection(object):
|
|
|
|
|
|
|
|
|
|
tokens = row.get("tokens")
|
|
|
|
|
if not tokens:
|
|
|
|
|
log.warn("Excluding host (%s) with no tokens in system.peers table of %s." % (addr, connection.host))
|
|
|
|
|
log.warning("Excluding host (%s) with no tokens in system.peers table of %s." % (addr, connection.host))
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
found_hosts.add(addr)
|
|
|
|
|
@@ -2137,12 +2160,17 @@ class ControlConnection(object):
|
|
|
|
|
self._cluster.on_down(host, is_host_addition=False)
|
|
|
|
|
|
|
|
|
|
def _handle_schema_change(self, event):
|
|
|
|
|
keyspace = event['keyspace'] or None
|
|
|
|
|
table = event.get('table') or None
|
|
|
|
|
keyspace = event.get('keyspace')
|
|
|
|
|
table = event.get('table')
|
|
|
|
|
usertype = event.get('type')
|
|
|
|
|
self._submit(self.refresh_schema, keyspace, table, usertype)
|
|
|
|
|
|
|
|
|
|
def wait_for_schema_agreement(self, connection=None, preloaded_results=None):
|
|
|
|
|
def wait_for_schema_agreement(self, connection=None, preloaded_results=None, wait_time=None):
|
|
|
|
|
|
|
|
|
|
total_timeout = wait_time if wait_time is not None else self._cluster.max_schema_agreement_wait
|
|
|
|
|
if total_timeout <= 0:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
# Each schema change typically generates two schema refreshes, one
|
|
|
|
|
# from the response type and one from the pushed notification. Holding
|
|
|
|
|
# a lock is just a simple way to cut down on the number of schema queries
|
|
|
|
|
@@ -2167,7 +2195,6 @@ class ControlConnection(object):
|
|
|
|
|
start = self._time.time()
|
|
|
|
|
elapsed = 0
|
|
|
|
|
cl = ConsistencyLevel.ONE
|
|
|
|
|
total_timeout = self._cluster.max_schema_agreement_wait
|
|
|
|
|
schema_mismatches = None
|
|
|
|
|
while elapsed < total_timeout:
|
|
|
|
|
peers_query = QueryMessage(query=self._SELECT_SCHEMA_PEERS, consistency_level=cl)
|
|
|
|
|
@@ -2196,8 +2223,8 @@ class ControlConnection(object):
|
|
|
|
|
self._time.sleep(0.2)
|
|
|
|
|
elapsed = self._time.time() - start
|
|
|
|
|
|
|
|
|
|
log.warn("Node %s is reporting a schema disagreement: %s",
|
|
|
|
|
connection.host, schema_mismatches)
|
|
|
|
|
log.warning("Node %s is reporting a schema disagreement: %s",
|
|
|
|
|
connection.host, schema_mismatches)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _get_schema_mismatches(self, peers_result, local_result, local_address):
|
|
|
|
|
|