Skip to content

Commit a048827

Browse files
committed
Merge branch 'oss-next' into ngdg_master_ft
2 parents d95acb2 + fdab49f commit a048827

33 files changed

+1524
-265
lines changed

CHANGELOG.rst

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

9999
3.20.0
100100
======
101-
Unreleased
101+
October 28, 2019
102+
103+
Features
104+
--------
105+
* DataStax Apollo Support (PYTHON-1074)
106+
* Use 4.0 schema parser in 4 alpha and snapshot builds (PYTHON-1158)
102107

103108
Bug Fixes
104109
---------
105110
* Connection setup methods prevent using ExecutionProfile in cqlengine (PYTHON-1009)
111+
* Driver deadlock if all connections dropped by heartbeat whilst request in flight and request times out (PYTHON-1044)
112+
* Exception when use pk__token__gt filter In python 3.7 (PYTHON-1121)
106113

107114
3.19.0
108115
======

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,6 +51,7 @@ schedules:
5051

5152
release_test:
5253
schedule: per_commit
54+
disable_pull_requests: true
5355
branches:
5456
include: [/release-.+/]
5557
env_vars: |
@@ -69,6 +71,7 @@ ngdg:
6971

7072
weekly_master:
7173
schedule: 0 10 * * 6
74+
disable_pull_requests: true
7275
branches:
7376
include: [master]
7477
env_vars: |
@@ -80,6 +83,7 @@ ngdg:
8083

8184
weekly_gevent:
8285
schedule: 0 14 * * 6
86+
disable_pull_requests: true
8387
branches:
8488
include: [master]
8589
env_vars: |
@@ -91,6 +95,7 @@ ngdg:
9195

9296
weekly_eventlet:
9397
schedule: 0 18 * * 6
98+
disable_pull_requests: true
9499
branches:
95100
include: [master]
96101
env_vars: |
@@ -102,6 +107,7 @@ ngdg:
102107

103108
weekly_asyncio:
104109
schedule: 0 22 * * 6
110+
disable_pull_requests: true
105111
branches:
106112
include: [master]
107113
env_vars: |
@@ -113,6 +119,7 @@ ngdg:
113119

114120
weekly_async:
115121
schedule: 0 10 * * 7
122+
disable_pull_requests: true
116123
branches:
117124
include: [master]
118125
env_vars: |
@@ -124,6 +131,7 @@ ngdg:
124131

125132
weekly_twister:
126133
schedule: 0 14 * * 7
134+
disable_pull_requests: true
127135
branches:
128136
include: [master]
129137
env_vars: |
@@ -192,6 +200,9 @@ build:
192200
export PYTHONPATH=""
193201
export CCM_MAX_HEAP_SIZE=1024M
194202
203+
# Required for unix socket tests
204+
sudo apt-get install socat
205+
195206
# Install latest setuptools
196207
pip install --upgrade pip
197208
pip install -U setuptools
@@ -277,9 +288,13 @@ build:
277288
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
278289
fi
279290
291+
echo "==========RUNNING ADVANCED AND CLOUD TESTS=========="
292+
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
293+
280294
if [ -z "$EXCLUDE_LONG" ]; then
281295
echo "==========RUNNING LONG INTEGRATION TESTS=========="
282296
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
283297
fi
298+
284299
- xunit:
285300
- "*_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, '20190910+labs')
25+
__version_info__ = (3, 20, 0, '20191030+labs')
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,
@@ -93,11 +93,11 @@
9393
graph_graphson2_row_factory, graph_graphson3_row_factory,
9494
GraphSON3Serializer)
9595
from cassandra.datastax.graph.query import _request_timeout_key, _GraphSONContextRowFactory
96+
from cassandra.datastax import cloud as dscloud
9697

9798
if six.PY3:
9899
long = int
99100

100-
101101
def _is_eventlet_monkey_patched():
102102
if 'eventlet.patcher' not in sys.modules:
103103
return False
@@ -357,19 +357,28 @@ class ExecutionProfile(object):
357357

358358
# indicates if lbp was set explicitly or uses default values
359359
_load_balancing_policy_explicit = False
360+
_consistency_level_explicit = False
360361

361362
def __init__(self, load_balancing_policy=_NOT_SET, retry_policy=None,
362-
consistency_level=ConsistencyLevel.LOCAL_ONE, serial_consistency_level=None,
363+
consistency_level=_NOT_SET, serial_consistency_level=None,
363364
request_timeout=10.0, row_factory=named_tuple_factory, speculative_execution_policy=None,
364365
continuous_paging_options=None):
366+
365367
if load_balancing_policy is _NOT_SET:
366368
self._load_balancing_policy_explicit = False
367369
self.load_balancing_policy = default_lbp_factory()
368370
else:
369371
self._load_balancing_policy_explicit = True
370372
self.load_balancing_policy = load_balancing_policy
373+
374+
if consistency_level is _NOT_SET:
375+
self._consistency_level_explicit = False
376+
self.consistency_level = ConsistencyLevel.LOCAL_ONE
377+
else:
378+
self._consistency_level_explicit = True
379+
self.consistency_level = consistency_level
380+
371381
self.retry_policy = retry_policy or RetryPolicy()
372-
self.consistency_level = consistency_level
373382

374383
if (serial_consistency_level is not None and
375384
not ConsistencyLevel.is_serial(serial_consistency_level)):
@@ -964,6 +973,19 @@ def default_retry_policy(self, policy):
964973
A string identifiying this application's version to Insights
965974
"""
966975

976+
cloud = None
977+
"""
978+
A dict of the cloud configuration. Example::
979+
980+
{
981+
# path to the secure connect bundle
982+
'secure_connect_bundle': '/path/to/secure-connect-dbname.zip'
983+
}
984+
985+
The zip file will be temporarily extracted in the same directory to
986+
load the configuration and certificates.
987+
"""
988+
967989
@property
968990
def schema_metadata_enabled(self):
969991
"""
@@ -1064,13 +1086,34 @@ def __init__(self,
10641086
application_version=None,
10651087
monitor_reporting_enabled=True,
10661088
monitor_reporting_interval=30,
1067-
client_id=None):
1089+
client_id=None,
1090+
cloud=None):
10681091
"""
10691092
``executor_threads`` defines the number of threads in a pool for handling asynchronous tasks such as
10701093
extablishing connection pools or refreshing metadata.
10711094
10721095
Any of the mutable Cluster attributes may be set as keyword arguments to the constructor.
10731096
"""
1097+
1098+
if cloud is not None:
1099+
if contact_points is not _NOT_SET or endpoint_factory or ssl_context or ssl_options:
1100+
raise ValueError("contact_points, endpoint_factory, ssl_context, and ssl_options "
1101+
"cannot be specified with a cloud configuration")
1102+
1103+
cloud_config = dscloud.get_cloud_config(cloud)
1104+
1105+
ssl_context = cloud_config.ssl_context
1106+
ssl_options = {'check_hostname': True}
1107+
if (auth_provider is None and cloud_config.username
1108+
and cloud_config.password):
1109+
auth_provider = PlainTextAuthProvider(cloud_config.username, cloud_config.password)
1110+
1111+
endpoint_factory = SniEndPointFactory(cloud_config.sni_host, cloud_config.sni_port)
1112+
contact_points = [
1113+
endpoint_factory.create_from_sni(host_id)
1114+
for host_id in cloud_config.host_ids
1115+
]
1116+
10741117
if contact_points is not None:
10751118
if contact_points is _NOT_SET:
10761119
self._contact_points_explicit = False
@@ -1160,12 +1203,12 @@ def __init__(self,
11601203
self.timestamp_generator = MonotonicTimestampGenerator()
11611204

11621205
self.profile_manager = ProfileManager()
1163-
self.profile_manager.profiles[EXEC_PROFILE_DEFAULT] = ExecutionProfile(self.load_balancing_policy,
1164-
self.default_retry_policy,
1165-
Session._default_consistency_level,
1166-
Session._default_serial_consistency_level,
1167-
Session._default_timeout,
1168-
Session._row_factory)
1206+
self.profile_manager.profiles[EXEC_PROFILE_DEFAULT] = ExecutionProfile(
1207+
self.load_balancing_policy,
1208+
self.default_retry_policy,
1209+
request_timeout=Session._default_timeout,
1210+
row_factory=Session._row_factory
1211+
)
11691212

11701213
# legacy mode if either of these is not default
11711214
if load_balancing_policy or default_retry_policy:
@@ -1430,6 +1473,7 @@ def add_execution_profile(self, name, profile, pool_wait_timeout=5):
14301473
profile.load_balancing_policy.on_up(host)
14311474
futures = set()
14321475
for session in tuple(self.sessions):
1476+
self._set_default_dbaas_consistency(session)
14331477
futures.update(session.update_created_pools())
14341478
_, not_done = wait_futures(futures, pool_wait_timeout)
14351479
if not_done:
@@ -1648,8 +1692,18 @@ def connect(self, keyspace=None, wait_for_all_pools=False):
16481692
session = self._new_session(keyspace)
16491693
if wait_for_all_pools:
16501694
wait_futures(session._initial_connect_futures)
1695+
1696+
self._set_default_dbaas_consistency(session)
1697+
16511698
return session
16521699

1700+
def _set_default_dbaas_consistency(self, session):
1701+
if session.cluster.metadata.dbaas:
1702+
for profile in self.profile_manager.profiles.values():
1703+
if not profile._consistency_level_explicit:
1704+
profile.consistency_level = ConsistencyLevel.LOCAL_QUORUM
1705+
session._default_consistency_level = ConsistencyLevel.LOCAL_QUORUM
1706+
16531707
def get_connection_holders(self):
16541708
holders = []
16551709
for s in tuple(self.sessions):
@@ -3341,7 +3395,7 @@ class ControlConnection(object):
33413395
# Used only when token_metadata_enabled is set to False
33423396
_SELECT_LOCAL_NO_TOKENS_RPC_ADDRESS = "SELECT rpc_address FROM system.local WHERE key='local'"
33433397

3344-
_SELECT_SCHEMA_PEERS_TEMPLATE = "SELECT peer, {nt_col_name}, schema_version FROM system.peers"
3398+
_SELECT_SCHEMA_PEERS_TEMPLATE = "SELECT peer, host_id, {nt_col_name}, schema_version FROM system.peers"
33453399
_SELECT_SCHEMA_LOCAL = "SELECT schema_version FROM system.local WHERE key='local'"
33463400

33473401
_MINIMUM_NATIVE_ADDRESS_VERSION = "4.0"
@@ -3393,6 +3447,8 @@ def connect(self):
33933447
self._protocol_version = self._cluster.protocol_version
33943448
self._set_new_connection(self._reconnect_internal())
33953449

3450+
self._cluster.metadata.dbaas = self._connection._product_type == dscloud.PRODUCT_APOLLO
3451+
33963452
def _set_new_connection(self, conn):
33973453
"""
33983454
Replace existing connection (if there is one) and close it.
@@ -4206,10 +4262,14 @@ def _on_timeout(self, _attempts=0):
42064262
if self._connection is not None:
42074263
try:
42084264
self._connection._requests.pop(self._req_id)
4209-
# This prevents the race condition of the
4210-
# event loop thread just receiving the waited message
4211-
# If it arrives after this, it will be ignored
4265+
# PYTHON-1044
4266+
# This request might have been removed from the connection after the latter was defunct by heartbeat.
4267+
# We should still raise OperationTimedOut to reject the future so that the main event thread will not
4268+
# wait for it endlessly
42124269
except KeyError:
4270+
key = "Connection defunct by heartbeat"
4271+
errors = {key: "Client request timeout. See Session.execute[_async](timeout)"}
4272+
self._set_final_exception(OperationTimedOut(errors, self._current_host))
42134273
return
42144274

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

0 commit comments

Comments
 (0)