Skip to content

Commit f856fd9

Browse files
committed
Merge branch 'master' into oss-next
2 parents 2b5399d + 78f634c commit f856fd9

33 files changed

+1524
-265
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,18 @@ Other
7373

7474
3.20.0
7575
======
76-
Unreleased
76+
October 28, 2019
77+
78+
Features
79+
--------
80+
* DataStax Apollo Support (PYTHON-1074)
81+
* Use 4.0 schema parser in 4 alpha and snapshot builds (PYTHON-1158)
7782

7883
Bug Fixes
7984
---------
8085
* Connection setup methods prevent using ExecutionProfile in cqlengine (PYTHON-1009)
86+
* Driver deadlock if all connections dropped by heartbeat whilst request in flight and request times out (PYTHON-1044)
87+
* Exception when use pk__token__gt filter In python 3.7 (PYTHON-1121)
8188

8289
3.19.0
8390
======

README.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@ Features
2626
* Configurable `load balancing <http://datastax.github.io/python-driver/api/cassandra/policies.html#load-balancing>`_ and `retry policies <http://datastax.github.io/python-driver/api/cassandra/policies.html#retrying-failed-operations>`_
2727
* `Concurrent execution utilities <http://datastax.github.io/python-driver/api/cassandra/concurrent.html>`_
2828
* `Object mapper <http://datastax.github.io/python-driver/object_mapper.html>`_
29+
* `Connecting to DataStax Apollo database (cloud) <https://docs.datastax.com/en/developer/python-driver/latest/cloud/>`_
2930
* DSE Graph execution API
3031
* DSE Geometric type serialization
3132
* DSE PlainText and GSSAPI authentication
3233

33-
A fluent API extension for DSE Graph is available in the ``dse-graph`` package. For more information, see `the documentation here <http://docs.datastax.com/en/developer/python-dse-graph/>`_.
34-
3534
Installation
3635
------------
3736
Installation through pip is recommended::

build.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
schedules:
22
nightly_master:
33
schedule: nightly
4+
disable_pull_requests: true
45
branches:
56
include: [master]
67
env_vars: |
@@ -50,13 +51,15 @@ schedules:
5051

5152
release_test:
5253
schedule: per_commit
54+
disable_pull_requests: true
5355
branches:
5456
include: [/release-.+/]
5557
env_vars: |
5658
EVENT_LOOP_MANAGER='libev'
5759
5860
weekly_master:
5961
schedule: 0 10 * * 6
62+
disable_pull_requests: true
6063
branches:
6164
include: [master]
6265
env_vars: |
@@ -68,6 +71,7 @@ schedules:
6871

6972
weekly_gevent:
7073
schedule: 0 14 * * 6
74+
disable_pull_requests: true
7175
branches:
7276
include: [master]
7377
env_vars: |
@@ -79,6 +83,7 @@ schedules:
7983

8084
weekly_eventlet:
8185
schedule: 0 18 * * 6
86+
disable_pull_requests: true
8287
branches:
8388
include: [master]
8489
env_vars: |
@@ -90,6 +95,7 @@ schedules:
9095

9196
weekly_asyncio:
9297
schedule: 0 22 * * 6
98+
disable_pull_requests: true
9399
branches:
94100
include: [master]
95101
env_vars: |
@@ -101,6 +107,7 @@ schedules:
101107

102108
weekly_async:
103109
schedule: 0 10 * * 7
110+
disable_pull_requests: true
104111
branches:
105112
include: [master]
106113
env_vars: |
@@ -112,6 +119,7 @@ schedules:
112119

113120
weekly_twister:
114121
schedule: 0 14 * * 7
122+
disable_pull_requests: true
115123
branches:
116124
include: [master]
117125
env_vars: |
@@ -178,6 +186,9 @@ build:
178186
export PATH=$JAVA_HOME/bin:$PATH
179187
export PYTHONPATH=""
180188
189+
# Required for unix socket tests
190+
sudo apt-get install socat
191+
181192
# Install latest setuptools
182193
pip install --upgrade pip
183194
pip install -U setuptools
@@ -263,9 +274,13 @@ build:
263274
EVENT_LOOP_MANAGER=$EVENT_LOOP_MANAGER CASSANDRA_DIR=$CCM_INSTALL_DIR DSE_VERSION=$DSE_VERSION ADS_HOME=$HOME/ VERIFY_CYTHON=$FORCE_CYTHON nosetests -s -v --logging-format="[%(levelname)s] %(asctime)s %(thread)d: %(message)s" --with-ignore-docstrings --with-xunit --xunit-file=dse_results.xml tests/integration/advanced/ || true
264275
fi
265276
277+
echo "==========RUNNING ADVANCED AND CLOUD TESTS=========="
278+
EVENT_LOOP_MANAGER=$EVENT_LOOP_MANAGER CLOUD_PROXY_PATH="$HOME/proxy/" CASSANDRA_VERSION=$CCM_CASSANDRA_VERSION MAPPED_CASSANDRA_VERSION=$MAPPED_CASSANDRA_VERSION VERIFY_CYTHON=$FORCE_CYTHON nosetests -s -v --logging-format="[%(levelname)s] %(asctime)s %(thread)d: %(message)s" --with-ignore-docstrings --with-xunit --xunit-file=advanced_results.xml tests/integration/advanced/ || true
279+
266280
if [ -z "$EXCLUDE_LONG" ]; then
267281
echo "==========RUNNING LONG INTEGRATION TESTS=========="
268282
EVENT_LOOP_MANAGER=$EVENT_LOOP_MANAGER CCM_ARGS="$CCM_ARGS" DSE_VERSION=$DSE_VERSION CASSANDRA_VERSION=$CCM_CASSANDRA_VERSION MAPPED_CASSANDRA_VERSION=$MAPPED_CASSANDRA_VERSION VERIFY_CYTHON=$FORCE_CYTHON nosetests -s -v --logging-format="[%(levelname)s] %(asctime)s %(thread)d: %(message)s" --exclude-dir=tests/integration/long/upgrade --with-ignore-docstrings --with-xunit --xunit-file=long_results.xml tests/integration/long/ || true
269283
fi
284+
270285
- xunit:
271286
- "*_results.xml"

cassandra/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def emit(self, record):
2222

2323
logging.getLogger('cassandra').addHandler(NullHandler())
2424

25-
__version_info__ = (3, 19, 0, 'post0')
25+
__version_info__ = (3, 20, 0)
2626
__version__ = '.'.join(map(str, __version_info__))
2727

2828

cassandra/cluster.py

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
from cassandra.connection import (ConnectionException, ConnectionShutdown,
5252
ConnectionHeartbeat, ProtocolVersionUnsupported,
5353
EndPoint, DefaultEndPoint, DefaultEndPointFactory,
54-
ContinuousPagingState)
54+
ContinuousPagingState, SniEndPointFactory)
5555
from cassandra.cqltypes import UserType
5656
from cassandra.encoder import Encoder
5757
from cassandra.protocol import (QueryMessage, ResultMessage,
@@ -91,11 +91,11 @@
9191
from cassandra.datastax.graph import (graph_object_row_factory, GraphOptions, GraphSON1Serializer,
9292
GraphProtocol, GraphSON2Serializer, GraphStatement, SimpleGraphStatement)
9393
from cassandra.datastax.graph.query import _request_timeout_key
94+
from cassandra.datastax import cloud as dscloud
9495

9596
if six.PY3:
9697
long = int
9798

98-
9999
def _is_eventlet_monkey_patched():
100100
if 'eventlet.patcher' not in sys.modules:
101101
return False
@@ -354,19 +354,28 @@ class ExecutionProfile(object):
354354

355355
# indicates if lbp was set explicitly or uses default values
356356
_load_balancing_policy_explicit = False
357+
_consistency_level_explicit = False
357358

358359
def __init__(self, load_balancing_policy=_NOT_SET, retry_policy=None,
359-
consistency_level=ConsistencyLevel.LOCAL_ONE, serial_consistency_level=None,
360+
consistency_level=ConsistencyLevel._NOT_SET, serial_consistency_level=None,
360361
request_timeout=10.0, row_factory=named_tuple_factory, speculative_execution_policy=None,
361362
continuous_paging_options=None):
363+
362364
if load_balancing_policy is _NOT_SET:
363365
self._load_balancing_policy_explicit = False
364366
self.load_balancing_policy = default_lbp_factory()
365367
else:
366368
self._load_balancing_policy_explicit = True
367369
self.load_balancing_policy = load_balancing_policy
370+
371+
if consistency_level is _NOT_SET:
372+
self._consistency_level_explicit = False
373+
self.consistency_level = ConsistencyLevel.LOCAL_ONE
374+
else:
375+
self._consistency_level_explicit = True
376+
self.consistency_level = consistency_level
377+
368378
self.retry_policy = retry_policy or RetryPolicy()
369-
self.consistency_level = consistency_level
370379

371380
if (serial_consistency_level is not None and
372381
not ConsistencyLevel.is_serial(serial_consistency_level)):
@@ -960,6 +969,19 @@ def default_retry_policy(self, policy):
960969
A string identifiying this application's version to Insights
961970
"""
962971

972+
cloud = None
973+
"""
974+
A dict of the cloud configuration. Example::
975+
976+
{
977+
# path to the secure connect bundle
978+
'secure_connect_bundle': '/path/to/secure-connect-dbname.zip'
979+
}
980+
981+
The zip file will be temporarily extracted in the same directory to
982+
load the configuration and certificates.
983+
"""
984+
963985
@property
964986
def schema_metadata_enabled(self):
965987
"""
@@ -1060,13 +1082,34 @@ def __init__(self,
10601082
application_version=None,
10611083
monitor_reporting_enabled=True,
10621084
monitor_reporting_interval=30,
1063-
client_id=None):
1085+
client_id=None,
1086+
cloud=None):
10641087
"""
10651088
``executor_threads`` defines the number of threads in a pool for handling asynchronous tasks such as
10661089
extablishing connection pools or refreshing metadata.
10671090
10681091
Any of the mutable Cluster attributes may be set as keyword arguments to the constructor.
10691092
"""
1093+
1094+
if cloud is not None:
1095+
if contact_points is not _NOT_SET or endpoint_factory or ssl_context or ssl_options:
1096+
raise ValueError("contact_points, endpoint_factory, ssl_context, and ssl_options "
1097+
"cannot be specified with a cloud configuration")
1098+
1099+
cloud_config = dscloud.get_cloud_config(cloud)
1100+
1101+
ssl_context = cloud_config.ssl_context
1102+
ssl_options = {'check_hostname': True}
1103+
if (auth_provider is None and cloud_config.username
1104+
and cloud_config.password):
1105+
auth_provider = PlainTextAuthProvider(cloud_config.username, cloud_config.password)
1106+
1107+
endpoint_factory = SniEndPointFactory(cloud_config.sni_host, cloud_config.sni_port)
1108+
contact_points = [
1109+
endpoint_factory.create_from_sni(host_id)
1110+
for host_id in cloud_config.host_ids
1111+
]
1112+
10701113
if contact_points is not None:
10711114
if contact_points is _NOT_SET:
10721115
self._contact_points_explicit = False
@@ -1156,12 +1199,12 @@ def __init__(self,
11561199
self.timestamp_generator = MonotonicTimestampGenerator()
11571200

11581201
self.profile_manager = ProfileManager()
1159-
self.profile_manager.profiles[EXEC_PROFILE_DEFAULT] = ExecutionProfile(self.load_balancing_policy,
1160-
self.default_retry_policy,
1161-
Session._default_consistency_level,
1162-
Session._default_serial_consistency_level,
1163-
Session._default_timeout,
1164-
Session._row_factory)
1202+
self.profile_manager.profiles[EXEC_PROFILE_DEFAULT] = ExecutionProfile(
1203+
self.load_balancing_policy,
1204+
self.default_retry_policy,
1205+
request_timeout=Session._default_timeout,
1206+
row_factory=Session._row_factory
1207+
)
11651208

11661209
# legacy mode if either of these is not default
11671210
if load_balancing_policy or default_retry_policy:
@@ -1426,6 +1469,7 @@ def add_execution_profile(self, name, profile, pool_wait_timeout=5):
14261469
profile.load_balancing_policy.on_up(host)
14271470
futures = set()
14281471
for session in tuple(self.sessions):
1472+
self._set_default_dbaas_consistency(session)
14291473
futures.update(session.update_created_pools())
14301474
_, not_done = wait_futures(futures, pool_wait_timeout)
14311475
if not_done:
@@ -1644,8 +1688,18 @@ def connect(self, keyspace=None, wait_for_all_pools=False):
16441688
session = self._new_session(keyspace)
16451689
if wait_for_all_pools:
16461690
wait_futures(session._initial_connect_futures)
1691+
1692+
self._set_default_dbaas_consistency(session)
1693+
16471694
return session
16481695

1696+
def _set_default_dbaas_consistency(self, session):
1697+
if session.cluster.metadata.dbaas:
1698+
for profile in self.profile_manager.profiles.values():
1699+
if not profile._consistency_level_explicit:
1700+
profile.consistency_level = ConsistencyLevel.LOCAL_QUORUM
1701+
session._default_consistency_level = ConsistencyLevel.LOCAL_QUORUM
1702+
16491703
def get_connection_holders(self):
16501704
holders = []
16511705
for s in tuple(self.sessions):
@@ -3248,7 +3302,7 @@ class ControlConnection(object):
32483302
# Used only when token_metadata_enabled is set to False
32493303
_SELECT_LOCAL_NO_TOKENS_RPC_ADDRESS = "SELECT rpc_address FROM system.local WHERE key='local'"
32503304

3251-
_SELECT_SCHEMA_PEERS_TEMPLATE = "SELECT peer, {nt_col_name}, schema_version FROM system.peers"
3305+
_SELECT_SCHEMA_PEERS_TEMPLATE = "SELECT peer, host_id, {nt_col_name}, schema_version FROM system.peers"
32523306
_SELECT_SCHEMA_LOCAL = "SELECT schema_version FROM system.local WHERE key='local'"
32533307

32543308
_MINIMUM_NATIVE_ADDRESS_VERSION = "4.0"
@@ -3300,6 +3354,8 @@ def connect(self):
33003354
self._protocol_version = self._cluster.protocol_version
33013355
self._set_new_connection(self._reconnect_internal())
33023356

3357+
self._cluster.metadata.dbaas = self._connection._product_type == dscloud.PRODUCT_APOLLO
3358+
33033359
def _set_new_connection(self, conn):
33043360
"""
33053361
Replace existing connection (if there is one) and close it.
@@ -4113,10 +4169,14 @@ def _on_timeout(self, _attempts=0):
41134169
if self._connection is not None:
41144170
try:
41154171
self._connection._requests.pop(self._req_id)
4116-
# This prevents the race condition of the
4117-
# event loop thread just receiving the waited message
4118-
# If it arrives after this, it will be ignored
4172+
# PYTHON-1044
4173+
# This request might have been removed from the connection after the latter was defunct by heartbeat.
4174+
# We should still raise OperationTimedOut to reject the future so that the main event thread will not
4175+
# wait for it endlessly
41194176
except KeyError:
4177+
key = "Connection defunct by heartbeat"
4178+
errors = {key: "Client request timeout. See Session.execute[_async](timeout)"}
4179+
self._set_final_exception(OperationTimedOut(errors, self._current_host))
41204180
return
41214181

41224182
pool = self.session._pools.get(self._current_host)

0 commit comments

Comments
 (0)