Hello community,
here is the log from the commit of package python-cassandra-driver for
openSUSE:Factory checked in at 2019-03-04 09:22:42
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-cassandra-driver (Old)
and /work/SRC/openSUSE:Factory/.python-cassandra-driver.new.28833 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-cassandra-driver"
Mon Mar 4 09:22:42 2019 rev:7 rq:680790 version:3.17.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-cassandra-driver/python-cassandra-driver.changes
2018-12-03 10:12:36.531585796 +0100
+++
/work/SRC/openSUSE:Factory/.python-cassandra-driver.new.28833/python-cassandra-driver.changes
2019-03-04 09:22:45.872575184 +0100
@@ -1,0 +2,31 @@
+Sat Mar 2 04:47:54 UTC 2019 - Arun Persaud <[email protected]>
+
+- specfile:
+ * update copyright year
+ * be more specific in %files section
+
+- update to version 3.17.0:
+ * Features
+ + Send driver name and version in startup message (PYTHON-1068)
+ + Add Cluster ssl_context option to enable SSL (PYTHON-995)
+ + Allow encrypted private keys for 2-way SSL cluster connections
+ (PYTHON-995)
+ + Introduce new method ConsistencyLevel.is_serial (PYTHON-1067)
+ + Add Session.get_execution_profile (PYTHON-932)
+ + Add host kwarg to Session.execute/execute_async APIs to send a
+ query to a specific node (PYTHON-993)
+ * Bug Fixes
+ + NoHostAvailable when all hosts are up and connectable
+ (PYTHON-891)
+ + Serial consistency level is not used (PYTHON-1007)
+ * Other
+ + Fail faster on incorrect lz4 import (PYTHON-1042)
+ + Bump Cython dependency version to 0.29 (PYTHON-1036)
+ + Expand Driver SSL Documentation (PYTHON-740)
+ * Deprecations
+ + Using Cluster.ssl_options to enable SSL is deprecated and will
+ be removed in the next major release, use ssl_context.
+ + DowngradingConsistencyRetryPolicy is deprecated and will be
+ removed in the next major release. (PYTHON-937)
+
+-------------------------------------------------------------------
Old:
----
3.16.0.tar.gz
New:
----
3.17.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-cassandra-driver.spec ++++++
--- /var/tmp/diff_new_pack.MZfqxP/_old 2019-03-04 09:22:46.384575092 +0100
+++ /var/tmp/diff_new_pack.MZfqxP/_new 2019-03-04 09:22:46.384575092 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-cassandra-driver
#
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-cassandra-driver
-Version: 3.16.0
+Version: 3.17.0
Release: 0
Summary: Python driver for Cassandra
License: Apache-2.0
@@ -85,6 +85,9 @@
%files %{python_files}
%license LICENSE
%doc README.rst
-%{python_sitearch}/*
+%dir %{python_sitearch}/cassandra
+%dir %{python_sitearch}/cassandra_driver-%{version}-py*.egg-info
+%{python_sitearch}/cassandra/*
+%{python_sitearch}/cassandra_driver-%{version}-py*.egg-info/*
%changelog
++++++ 3.16.0.tar.gz -> 3.17.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/CHANGELOG.rst
new/python-driver-3.17.0/CHANGELOG.rst
--- old/python-driver-3.16.0/CHANGELOG.rst 2018-11-12 19:19:38.000000000
+0100
+++ new/python-driver-3.17.0/CHANGELOG.rst 2019-02-15 22:05:51.000000000
+0100
@@ -1,3 +1,35 @@
+3.17.0
+======
+February 19, 2019
+
+Features
+--------
+* Send driver name and version in startup message (PYTHON-1068)
+* Add Cluster ssl_context option to enable SSL (PYTHON-995)
+* Allow encrypted private keys for 2-way SSL cluster connections (PYTHON-995)
+* Introduce new method ConsistencyLevel.is_serial (PYTHON-1067)
+* Add Session.get_execution_profile (PYTHON-932)
+* Add host kwarg to Session.execute/execute_async APIs to send a query to a
specific node (PYTHON-993)
+
+Bug Fixes
+---------
+* NoHostAvailable when all hosts are up and connectable (PYTHON-891)
+* Serial consistency level is not used (PYTHON-1007)
+
+Other
+-----
+* Fail faster on incorrect lz4 import (PYTHON-1042)
+* Bump Cython dependency version to 0.29 (PYTHON-1036)
+* Expand Driver SSL Documentation (PYTHON-740)
+
+Deprecations
+------------
+
+* Using Cluster.ssl_options to enable SSL is deprecated and will be removed in
+ the next major release, use ssl_context.
+* DowngradingConsistencyRetryPolicy is deprecated and will be
+ removed in the next major release. (PYTHON-937)
+
3.16.0
======
November 12, 2018
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/CONTRIBUTING.rst
new/python-driver-3.17.0/CONTRIBUTING.rst
--- old/python-driver-3.16.0/CONTRIBUTING.rst 2018-11-12 19:19:38.000000000
+0100
+++ new/python-driver-3.17.0/CONTRIBUTING.rst 2019-02-15 22:05:51.000000000
+0100
@@ -26,7 +26,7 @@
Design and Implementation Guidelines
------------------------------------
-- We support Python 2.6+, so any changes must work in any of these runtimes
(we use ``six``, ``futures``, and some internal backports for compatability)
+- We support Python 2.7+, so any changes must work in any of these runtimes
(we use ``six``, ``futures``, and some internal backports for compatability)
- We have integrations (notably Cassandra cqlsh) that require pure Python and
minimal external dependencies. We try to avoid new external dependencies. Where
compiled extensions are concerned, there should always be a pure Python
fallback implementation.
- This project follows `semantic versioning <http://semver.org/>`_, so
breaking API changes will only be introduced in major versions.
- Legacy ``cqlengine`` has varying degrees of overreaching client-side
validation. Going forward, we will avoid client validation where server
feedback is adequate and not overly expensive.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/build.yaml
new/python-driver-3.17.0/build.yaml
--- old/python-driver-3.16.0/build.yaml 2018-11-12 19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/build.yaml 2019-02-15 22:05:51.000000000 +0100
@@ -118,7 +118,6 @@
- '3.0'
- '3.11'
- 'test-dse'
- - 'dse-6.7'
env:
CYTHON:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/cassandra/__init__.py
new/python-driver-3.17.0/cassandra/__init__.py
--- old/python-driver-3.16.0/cassandra/__init__.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/cassandra/__init__.py 2019-02-15
22:05:51.000000000 +0100
@@ -22,7 +22,7 @@
logging.getLogger('cassandra').addHandler(NullHandler())
-__version_info__ = (3, 16, 0)
+__version_info__ = (3, 17, 0)
__version__ = '.'.join(map(str, __version_info__))
@@ -92,6 +92,11 @@
one response.
"""
+ @staticmethod
+ def is_serial(cl):
+ return cl == ConsistencyLevel.SERIAL or cl ==
ConsistencyLevel.LOCAL_SERIAL
+
+
ConsistencyLevel.value_to_name = {
ConsistencyLevel.ANY: 'ANY',
ConsistencyLevel.ONE: 'ONE',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/cassandra/cluster.py
new/python-driver-3.17.0/cassandra/cluster.py
--- old/python-driver-3.16.0/cassandra/cluster.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/cassandra/cluster.py 2019-02-15
22:05:51.000000000 +0100
@@ -290,7 +290,14 @@
self.load_balancing_policy = load_balancing_policy
self.retry_policy = retry_policy or RetryPolicy()
self.consistency_level = consistency_level
+
+ if (serial_consistency_level is not None and
+ not ConsistencyLevel.is_serial(serial_consistency_level)):
+ raise ValueError("serial_consistency_level must be either "
+ "ConsistencyLevel.SERIAL "
+ "or ConsistencyLevel.LOCAL_SERIAL.")
self.serial_consistency_level = serial_consistency_level
+
self.request_timeout = request_timeout
self.row_factory = row_factory
self.speculative_execution_policy = speculative_execution_policy or
NoSpeculativeExecutionPolicy()
@@ -576,9 +583,14 @@
ssl_options = None
"""
- A optional dict which will be used as kwargs for ``ssl.wrap_socket()``
- when new sockets are created. This should be used when client encryption
- is enabled in Cassandra.
+ Using ssl_options without ssl_context is deprecated and will be removed in
the
+ next major release.
+
+ An optional dict which will be used as kwargs for
``ssl.SSLContext.wrap_socket`` (or
+ ``ssl.wrap_socket()`` if used without ssl_context) when new sockets are
created.
+ This should be used when client encryption is enabled in Cassandra.
+
+ The following documentation only applies when ssl_options is used without
ssl_context.
By default, a ``ca_certs`` value should be supplied (the value should be
a string pointing to the location of the CA certs file), and you probably
@@ -594,6 +606,17 @@
with a custom or `back-ported function
<https://pypi.org/project/backports.ssl_match_hostname/>`_.
"""
+ ssl_context = None
+ """
+ An optional ``ssl.SSLContext`` instance which will be used when new
sockets are created.
+ This should be used when client encryption is enabled in Cassandra.
+
+ ``wrap_socket`` options can be set using :attr:`~Cluster.ssl_options`.
ssl_options will
+ be used as kwargs for ``ssl.SSLContext.wrap_socket``.
+
+ .. versionadded:: 3.17.0
+ """
+
sockopts = None
"""
An optional list of tuples which will be used as arguments to
@@ -831,7 +854,8 @@
allow_beta_protocol_version=False,
timestamp_generator=None,
idle_heartbeat_timeout=30,
- no_compact=False):
+ no_compact=False,
+ ssl_context=None):
"""
``executor_threads`` defines the number of threads in a pool for
handling asynchronous tasks such as
extablishing connection pools or refreshing metadata.
@@ -951,7 +975,16 @@
''.format(cp=contact_points,
lbp=load_balancing_policy))
self.metrics_enabled = metrics_enabled
+
+ if ssl_options and not ssl_context:
+ warn('Using ssl_options without ssl_context is '
+ 'deprecated and will result in an error in '
+ 'the next major release. Please use ssl_context '
+ 'to prepare for that release.',
+ DeprecationWarning)
+
self.ssl_options = ssl_options
+ self.ssl_context = ssl_context
self.sockopts = sockopts
self.cql_version = cql_version
self.max_schema_agreement_wait = max_schema_agreement_wait
@@ -1247,6 +1280,7 @@
kwargs_dict.setdefault('compression', self.compression)
kwargs_dict.setdefault('sockopts', self.sockopts)
kwargs_dict.setdefault('ssl_options', self.ssl_options)
+ kwargs_dict.setdefault('ssl_context', self.ssl_context)
kwargs_dict.setdefault('cql_version', self.cql_version)
kwargs_dict.setdefault('protocol_version', self.protocol_version)
kwargs_dict.setdefault('user_type_map', self._user_types)
@@ -2005,6 +2039,12 @@
@default_serial_consistency_level.setter
def default_serial_consistency_level(self, cl):
+ if (cl is not None and
+ not ConsistencyLevel.is_serial(cl)):
+ raise ValueError("default_serial_consistency_level must be either "
+ "ConsistencyLevel.SERIAL "
+ "or ConsistencyLevel.LOCAL_SERIAL.")
+
self._validate_set_legacy_config('default_serial_consistency_level',
cl)
max_trace_wait = 2.0
@@ -2134,7 +2174,9 @@
msg += " using keyspace '%s'" % self.keyspace
raise NoHostAvailable(msg, [h.address for h in hosts])
- def execute(self, query, parameters=None, timeout=_NOT_SET, trace=False,
custom_payload=None, execution_profile=EXEC_PROFILE_DEFAULT, paging_state=None):
+ def execute(self, query, parameters=None, timeout=_NOT_SET, trace=False,
+ custom_payload=None, execution_profile=EXEC_PROFILE_DEFAULT,
+ paging_state=None, host=None):
"""
Execute the given query and synchronously wait for the response.
@@ -2167,10 +2209,16 @@
for example
`paging_state` is an optional paging state, reused from a previous
:class:`ResultSet`.
+
+ `host` is the :class:`pool.Host` that should handle the query. Using
this is discouraged except in a few
+ cases, e.g., querying node-local tables and applying schema changes.
"""
- return self.execute_async(query, parameters, trace, custom_payload,
timeout, execution_profile, paging_state).result()
+ return self.execute_async(query, parameters, trace, custom_payload,
+ timeout, execution_profile, paging_state,
host).result()
- def execute_async(self, query, parameters=None, trace=False,
custom_payload=None, timeout=_NOT_SET, execution_profile=EXEC_PROFILE_DEFAULT,
paging_state=None):
+ def execute_async(self, query, parameters=None, trace=False,
custom_payload=None,
+ timeout=_NOT_SET, execution_profile=EXEC_PROFILE_DEFAULT,
+ paging_state=None, host=None):
"""
Execute the given query and return a :class:`~.ResponseFuture` object
which callbacks may be attached to for asynchronous response
@@ -2205,13 +2253,17 @@
... log.exception("Operation failed:")
"""
- future = self._create_response_future(query, parameters, trace,
custom_payload, timeout, execution_profile, paging_state)
+ future = self._create_response_future(
+ query, parameters, trace, custom_payload, timeout,
+ execution_profile, paging_state, host)
future._protocol_handler = self.client_protocol_handler
self._on_request(future)
future.send_request()
return future
- def _create_response_future(self, query, parameters, trace,
custom_payload, timeout, execution_profile=EXEC_PROFILE_DEFAULT,
paging_state=None):
+ def _create_response_future(self, query, parameters, trace, custom_payload,
+ timeout,
execution_profile=EXEC_PROFILE_DEFAULT,
+ paging_state=None, host=None):
""" Returns the ResponseFuture before calling send_request() on it """
prepared_statement = None
@@ -2236,7 +2288,7 @@
load_balancing_policy = self.cluster.load_balancing_policy
spec_exec_policy = None
else:
- execution_profile = self._get_execution_profile(execution_profile)
+ execution_profile =
self._maybe_get_execution_profile(execution_profile)
if timeout is _NOT_SET:
timeout = execution_profile.request_timeout
@@ -2299,14 +2351,23 @@
return ResponseFuture(
self, message, query, timeout, metrics=self._metrics,
prepared_statement=prepared_statement, retry_policy=retry_policy,
row_factory=row_factory,
- load_balancer=load_balancing_policy, start_time=start_time,
speculative_execution_plan=spec_exec_plan)
+ load_balancer=load_balancing_policy, start_time=start_time,
speculative_execution_plan=spec_exec_plan,
+ host=host)
+
+ def get_execution_profile(self, name):
+ """
+ Returns the execution profile associated with the provided ``name``.
- def _get_execution_profile(self, ep):
+ :param name: The name (or key) of the execution profile.
+ """
profiles = self.cluster.profile_manager.profiles
try:
- return ep if isinstance(ep, ExecutionProfile) else profiles[ep]
+ return profiles[name]
except KeyError:
- raise ValueError("Invalid execution_profile: '%s'; valid profiles
are %s" % (ep, profiles.keys()))
+ raise ValueError("Invalid execution_profile: '%s'; valid profiles
are %s" % (name, profiles.keys()))
+
+ def _maybe_get_execution_profile(self, ep):
+ return ep if isinstance(ep, ExecutionProfile) else
self.get_execution_profile(ep)
def execution_profile_clone_update(self, ep, **kwargs):
"""
@@ -2318,7 +2379,7 @@
by the active profile. In cases where this is not desirable, be sure
to replace the instance instead of manipulating
the shared object.
"""
- clone = copy(self._get_execution_profile(ep))
+ clone = copy(self._maybe_get_execution_profile(ep))
for attr, value in kwargs.items():
setattr(clone, attr, value)
return clone
@@ -3491,11 +3552,13 @@
_timer = None
_protocol_handler = ProtocolHandler
_spec_execution_plan = NoSpeculativeExecutionPlan()
+ _host = None
_warned_timeout = False
def __init__(self, session, message, query, timeout, metrics=None,
prepared_statement=None,
- retry_policy=RetryPolicy(), row_factory=None,
load_balancer=None, start_time=None, speculative_execution_plan=None):
+ retry_policy=RetryPolicy(), row_factory=None,
load_balancer=None, start_time=None,
+ speculative_execution_plan=None, host=None):
self.session = session
# TODO: normalize handling of retry policy and row factory
self.row_factory = row_factory or session.row_factory
@@ -3508,6 +3571,7 @@
self.prepared_statement = prepared_statement
self._callback_lock = Lock()
self._start_time = start_time or time.time()
+ self._host = host
self._spec_execution_plan = speculative_execution_plan or
self._spec_execution_plan
self._make_query_plan()
self._event = Event()
@@ -3603,12 +3667,17 @@
self.send_request(error_no_hosts=False)
self._start_timer()
-
def _make_query_plan(self):
- # convert the list/generator/etc to an iterator so that subsequent
- # calls to send_request (which retries may do) will resume where
- # they last left off
- self.query_plan =
iter(self._load_balancer.make_query_plan(self.session.keyspace, self.query))
+ # set the query_plan according to the load balancing policy,
+ # or to the explicit host target if set
+ if self._host:
+ # returning a single value effectively disables retries
+ self.query_plan = [self._host]
+ else:
+ # convert the list/generator/etc to an iterator so that subsequent
+ # calls to send_request (which retries may do) will resume where
+ # they last left off
+ self.query_plan =
iter(self._load_balancer.make_query_plan(self.session.keyspace, self.query))
def send_request(self, error_no_hosts=True):
""" Internal """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/cassandra/connection.py
new/python-driver-3.17.0/cassandra/connection.py
--- old/python-driver-3.16.0/cassandra/connection.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/cassandra/connection.py 2019-02-15
22:05:51.000000000 +0100
@@ -68,6 +68,17 @@
except ImportError:
lz4_block = lz4
+ try:
+ lz4_block.compress
+ lz4_block.decompress
+ except AttributeError:
+ raise ImportError(
+ 'lz4 not imported correctly. Imported object should have '
+ '.compress and and .decompress attributes but does not. '
+ 'Please file a bug report on JIRA. (Imported object was '
+ '{lz4_block})'.format(lz4_block=repr(lz4_block))
+ )
+
# Cassandra writes the uncompressed message length in big endian order,
# but the lz4 lib requires little endian order, so we wrap these
# functions to handle that
@@ -95,6 +106,8 @@
locally_supported_compressions['snappy'] = (snappy.compress, decompress)
+DRIVER_NAME, DRIVER_VERSION = 'DataStax Python Driver',
sys.modules['cassandra'].__version__
+
PROTOCOL_VERSION_MASK = 0x7f
HEADER_DIRECTION_FROM_CLIENT = 0x00
@@ -212,6 +225,7 @@
decompressor = None
ssl_options = None
+ ssl_context = None
last_error = None
# The current number of operations that are in flight. More precisely,
@@ -260,11 +274,13 @@
def __init__(self, host='127.0.0.1', port=9042, authenticator=None,
ssl_options=None, sockopts=None, compression=True,
cql_version=None,
protocol_version=ProtocolVersion.MAX_SUPPORTED, is_control_connection=False,
- user_type_map=None, connect_timeout=None,
allow_beta_protocol_version=False, no_compact=False):
+ user_type_map=None, connect_timeout=None,
allow_beta_protocol_version=False, no_compact=False,
+ ssl_context=None):
self.host = host
self.port = port
self.authenticator = authenticator
self.ssl_options = ssl_options.copy() if ssl_options else None
+ self.ssl_context = ssl_context
self.sockopts = sockopts
self.compression = compression
self.cql_version = cql_version
@@ -350,7 +366,10 @@
for (af, socktype, proto, canonname, sockaddr) in addresses:
try:
self._socket = self._socket_impl.socket(af, socktype, proto)
- if self.ssl_options:
+ if self.ssl_context:
+ self._socket = self.ssl_context.wrap_socket(self._socket,
+
**(self.ssl_options or {}))
+ elif self.ssl_options:
if not self._ssl_impl:
raise RuntimeError("This version of Python was not
compiled with SSL support")
self._socket = self._ssl_impl.wrap_socket(self._socket,
**self.ssl_options)
@@ -710,7 +729,8 @@
@defunct_on_error
def _send_startup_message(self, compression=None, no_compact=False):
log.debug("Sending StartupMessage on %s", self)
- opts = {}
+ opts = {'DRIVER_NAME': DRIVER_NAME,
+ 'DRIVER_VERSION': DRIVER_VERSION}
if compression:
opts['COMPRESSION'] = compression
if no_compact:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/cassandra/io/asyncorereactor.py
new/python-driver-3.17.0/cassandra/io/asyncorereactor.py
--- old/python-driver-3.16.0/cassandra/io/asyncorereactor.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/cassandra/io/asyncorereactor.py 2019-02-15
22:05:51.000000000 +0100
@@ -402,7 +402,8 @@
sent = self.send(next_msg)
self._readable = True
except socket.error as err:
- if (err.args[0] in NONBLOCKING):
+ if (err.args[0] in NONBLOCKING or
+ err.args[0] in (ssl.SSL_ERROR_WANT_READ,
ssl.SSL_ERROR_WANT_WRITE)):
with self.deque_lock:
self.deque.appendleft(next_msg)
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/cassandra/io/libevreactor.py
new/python-driver-3.17.0/cassandra/io/libevreactor.py
--- old/python-driver-3.16.0/cassandra/io/libevreactor.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/cassandra/io/libevreactor.py 2019-02-15
22:05:51.000000000 +0100
@@ -316,7 +316,8 @@
try:
sent = self._socket.send(next_msg)
except socket.error as err:
- if (err.args[0] in NONBLOCKING):
+ if (err.args[0] in NONBLOCKING or
+ err.args[0] in (ssl.SSL_ERROR_WANT_READ,
ssl.SSL_ERROR_WANT_WRITE)):
with self._deque_lock:
self.deque.appendleft(next_msg)
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/cassandra/policies.py
new/python-driver-3.17.0/cassandra/policies.py
--- old/python-driver-3.16.0/cassandra/policies.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/cassandra/policies.py 2019-02-15
22:05:51.000000000 +0100
@@ -17,6 +17,7 @@
from random import randint, shuffle
from threading import Lock
import socket
+import warnings
from cassandra import WriteType as WT
@@ -814,7 +815,7 @@
This is called when the coordinator node determines that a read or
write operation cannot be successful because the number of live
replicas are too low to meet the requested :class:`.ConsistencyLevel`.
- This means that the read or write operation was never forwared to
+ This means that the read or write operation was never forwarded to
any replicas.
`query` is the :class:`.Statement` that failed.
@@ -830,9 +831,11 @@
`retry_num` counts how many times the operation has been retried, so
the first time this method is called, `retry_num` will be 0.
- By default, no retries will be attempted and the error will be
re-raised.
+ By default, if this is the first retry, it triggers a retry on the next
+ host in the query plan with the same consistency level. If this is not
the
+ first retry, no retries will be attempted and the error will be
re-raised.
"""
- return (self.RETRY_NEXT_HOST, consistency) if retry_num == 0 else
(self.RETHROW, None)
+ return (self.RETRY_NEXT_HOST, None) if retry_num == 0 else
(self.RETHROW, None)
class FallthroughRetryPolicy(RetryPolicy):
@@ -853,6 +856,8 @@
class DowngradingConsistencyRetryPolicy(RetryPolicy):
"""
+ *Deprecated:* This retry policy will be removed in the next major release.
+
A retry policy that sometimes retries with a lower consistency level than
the one initially requested.
@@ -898,6 +903,12 @@
to make sure the data is persisted, and that reading something is better
than reading nothing, even if there is a risk of reading stale data.
"""
+ def __init__(self, *args, **kwargs):
+ super(DowngradingConsistencyRetryPolicy, self).__init__(*args,
**kwargs)
+ warnings.warn('DowngradingConsistencyRetryPolicy is deprecated '
+ 'and will be removed in the next major release.',
+ DeprecationWarning)
+
def _pick_consistency(self, num_responses):
if num_responses >= 3:
return self.RETRY, ConsistencyLevel.THREE
@@ -912,6 +923,9 @@
received_responses, data_retrieved, retry_num):
if retry_num != 0:
return self.RETHROW, None
+ elif ConsistencyLevel.is_serial(consistency):
+ # Downgrading does not make sense for a CAS read query
+ return self.RETHROW, None
elif received_responses < required_responses:
return self._pick_consistency(received_responses)
elif not data_retrieved:
@@ -940,6 +954,9 @@
def on_unavailable(self, query, consistency, required_replicas,
alive_replicas, retry_num):
if retry_num != 0:
return self.RETHROW, None
+ elif ConsistencyLevel.is_serial(consistency):
+ # failed at the paxos phase of a LWT, retry on the next host
+ return self.RETRY_NEXT_HOST, None
else:
return self._pick_consistency(alive_replicas)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/cassandra/query.py
new/python-driver-3.17.0/cassandra/query.py
--- old/python-driver-3.16.0/cassandra/query.py 2018-11-12 19:19:38.000000000
+0100
+++ new/python-driver-3.17.0/cassandra/query.py 2019-02-15 22:05:51.000000000
+0100
@@ -328,8 +328,8 @@
return self._serial_consistency_level
def _set_serial_consistency_level(self, serial_consistency_level):
- acceptable = (None, ConsistencyLevel.SERIAL,
ConsistencyLevel.LOCAL_SERIAL)
- if serial_consistency_level not in acceptable:
+ if (serial_consistency_level is not None and
+ not ConsistencyLevel.is_serial(serial_consistency_level)):
raise ValueError(
"serial_consistency_level must be either
ConsistencyLevel.SERIAL "
"or ConsistencyLevel.LOCAL_SERIAL")
@@ -445,7 +445,7 @@
result_metadata_id = None
routing_key_indexes = None
_routing_key_index_set = None
- serial_consistency_level = None
+ serial_consistency_level = None # TODO never used?
def __init__(self, column_metadata, query_id, routing_key_indexes, query,
keyspace, protocol_version, result_metadata,
result_metadata_id):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/docs/api/cassandra/cluster.rst
new/python-driver-3.17.0/docs/api/cassandra/cluster.rst
--- old/python-driver-3.16.0/docs/api/cassandra/cluster.rst 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/docs/api/cassandra/cluster.rst 2019-02-15
22:05:51.000000000 +0100
@@ -32,6 +32,8 @@
.. autoattribute:: metrics
+ .. autoattribute:: ssl_context
+
.. autoattribute:: ssl_options
.. autoattribute:: sockopts
@@ -158,6 +160,8 @@
.. automethod:: set_keyspace(keyspace)
+ .. automethod:: get_execution_profile
+
.. automethod:: execution_profile_clone_update
.. automethod:: add_request_init_listener
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/docs/security.rst
new/python-driver-3.17.0/docs/security.rst
--- old/python-driver-3.16.0/docs/security.rst 2018-11-12 19:19:38.000000000
+0100
+++ new/python-driver-3.17.0/docs/security.rst 2019-02-15 22:05:51.000000000
+0100
@@ -27,9 +27,6 @@
cluster = Cluster(auth_provider=auth_provider, protocol_version=2)
-When working with version 2 or higher of the driver, the protocol
-version is set to 2 by default, but we've included it in the example
-to be explicit.
Custom Authenticators
^^^^^^^^^^^^^^^^^^^^^
@@ -59,10 +56,200 @@
SSL
---
+SSL should be used when client encryption is enabled in Cassandra.
+
+To give you as much control as possible over your SSL configuration, our SSL
+API takes a user-created `SSLContext` instance from the Python standard
library.
+These docs will include some examples for how to achieve common configurations,
+but the `ssl.SSLContext` documentation gives a more complete description of
+what is possible.
+
+To enable SSL with version 3.17.0 and higher, you will need to set
:attr:`.Cluster.ssl_context` to a
+``ssl.SSLContext`` instance to enable SSL. Optionally, you can also set
:attr:`.Cluster.ssl_options`
+to a dict of options. These will be passed as kwargs to
``ssl.SSLContext.wrap_socket()``
+when new sockets are created.
+
+The following examples assume you have generated your Cassandra certificate and
+keystore files with these intructions:
+
+* `Setup SSL Cert
<https://docs.datastax.com/en/dse/6.7/dse-admin/datastax_enterprise/security/secSetUpSSLCert.html>`_
+
+It might be also useful to learn about the different levels of identity
verification to understand the examples:
+
+* `Using SSL in DSE drivers
<https://docs.datastax.com/en/dse/6.7/dse-dev/datastax_enterprise/appDevGuide/sslDrivers.html>`_
+
+SSL Configuration Examples
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+Here, we'll describe the server and driver configuration necessary to set up
SSL to meet various goals, such as the client verifying the server and the
server verifying the client. We'll also include Python code demonstrating how
to use servers and drivers configured in these ways.
+
+No identity verification
+++++++++++++++++++++++++
+
+No identity verification at all. Note that this is not recommended for for
production deployments.
+
+The Cassandra configuration::
+
+ client_encryption_options:
+ enabled: true
+ keystore: /path/to/127.0.0.1.keystore
+ keystore_password: myStorePass
+ require_client_auth: false
+
+The driver configuration:
+
+.. code-block:: python
+
+ from cassandra.cluster import Cluster, Session
+ from ssl import SSLContext, PROTOCOL_TLSv1
+
+ ssl_context = SSLContext(PROTOCOL_TLSv1)
+
+ cluster = Cluster(['127.0.0.1'], ssl_context=ssl_context)
+ session = cluster.connect()
+
+Client verifies server
+++++++++++++++++++++++
+
+Ensure the python driver verifies the identity of the server.
+
+The Cassandra configuration::
+
+ client_encryption_options:
+ enabled: true
+ keystore: /path/to/127.0.0.1.keystore
+ keystore_password: myStorePass
+ require_client_auth: false
+
+For the driver configuration, it's very important to set
`ssl_context.verify_mode`
+to `CERT_REQUIRED`. Otherwise, the loaded verify certificate will have no
effect:
+
+.. code-block:: python
+
+ from cassandra.cluster import Cluster, Session
+ from ssl import SSLContext, PROTOCOL_TLSv1, CERT_REQUIRED
+
+ ssl_context = SSLContext(PROTOCOL_TLSv1)
+ ssl_context.load_verify_locations('/path/to/rootca.crt')
+ ssl_context.verify_mode = CERT_REQUIRED
+
+ cluster = Cluster(['127.0.0.1'], ssl_context=ssl_context)
+ session = cluster.connect()
+
+Additionally, you can also force the driver to verify the `hostname` of the
server by passing additional options to `ssl_context.wrap_socket` via the
`ssl_options` kwarg:
+
+.. code-block:: python
+
+ from cassandra.cluster import Cluster, Session
+ from ssl import SSLContext, PROTOCOL_TLSv1, CERT_REQUIRED
+
+ ssl_context = SSLContext(PROTOCOL_TLSv1)
+ ssl_context.load_verify_locations('/path/to/rootca.crt')
+ ssl_context.verify_mode = CERT_REQUIRED
+ ssl_context.check_hostname = True
+ ssl_options = {'server_hostname': '127.0.0.1'}
+
+ cluster = Cluster(['127.0.0.1'], ssl_context=ssl_context,
ssl_options=ssl_options)
+ session = cluster.connect()
+
+Server verifies client
+++++++++++++++++++++++
+
+If Cassandra is configured to verify clients (``require_client_auth``), you
need to generate
+SSL key and certificate files.
+
+The cassandra configuration::
+
+ client_encryption_options:
+ enabled: true
+ keystore: /path/to/127.0.0.1.keystore
+ keystore_password: myStorePass
+ require_client_auth: true
+ truststore: /path/to/dse-truststore.jks
+ truststore_password: myStorePass
+
+The Python ``ssl`` APIs require the certificate in PEM format. First, create a
certificate
+conf file:
+
+.. code-block:: bash
+
+ cat > gen_client_cert.conf <<EOF
+ [ req ]
+ distinguished_name = req_distinguished_name
+ prompt = no
+ output_password = ${ROOT_CERT_PASS}
+ default_bits = 2048
+
+ [ req_distinguished_name ]
+ C = ${CERT_COUNTRY}
+ O = ${CERT_ORG_NAME}
+ OU = ${CERT_OU}
+ CN = client
+ EOF
+
+Make sure you replaced the variables with the same values you used for the
initial
+root CA certificate. Then, generate the key:
+
+.. code-block:: bash
+
+ openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr
-config gen_client_cert.conf
+
+And generate the client signed certificate:
+
+.. code-block:: bash
+
+ openssl x509 -req -CA ${ROOT_CA_BASE_NAME}.crt -CAkey
${ROOT_CA_BASE_NAME}.key -passin pass:${ROOT_CERT_PASS} \
+ -in client.csr -out client.crt_signed -days ${CERT_VALIDITY}
-CAcreateserial
+
+Finally, you can use that configuration with the following driver code:
+
+.. code-block:: python
+
+ from cassandra.cluster import Cluster, Session
+ from ssl import SSLContext, PROTOCOL_TLSv1
+
+ ssl_context = SSLContext(PROTOCOL_TLSv1)
+ ssl_context.load_cert_chain(
+ certfile='/path/to/client.crt_signed',
+ keyfile='/path/to/client.key')
+
+ cluster = Cluster(['127.0.0.1'], ssl_context=ssl_context)
+ session = cluster.connect()
+
+
+Server verifies client and client verifies server
++++++++++++++++++++++++++++++++++++++++++++++++++
+
+See the previous section for examples of Cassandra configuration and preparing
+the client certificates.
+
+The following driver code specifies that the connection should use two-way
verification:
+
+.. code-block:: python
+
+ from cassandra.cluster import Cluster, Session
+ from ssl import SSLContext, PROTOCOL_TLSv1, CERT_REQUIRED
+
+ ssl_context = SSLContext(PROTOCOL_TLSv1)
+ ssl_context.load_verify_locations('/path/to/rootca.crt')
+ ssl_context.verify_mode = CERT_REQUIRED
+ ssl_context.load_cert_chain(
+ certfile='/path/to/client.crt_signed',
+ keyfile='/path/to/client.key')
+
+ cluster = Cluster(['127.0.0.1'], ssl_context=ssl_context)
+ session = cluster.connect()
+
+
+The driver uses ``SSLContext`` directly to give you many other options in
configuring SSL. Consider reading the `Python SSL documentation
<https://docs.python.org/library/ssl.html#ssl.SSLContext>`_
+for more details about ``SSLContext`` configuration.
+
+Versions 3.16.0 and lower
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
To enable SSL you will need to set :attr:`.Cluster.ssl_options` to a
dict of options. These will be passed as kwargs to ``ssl.wrap_socket()``
-when new sockets are created. This should be used when client encryption
-is enabled in Cassandra.
+when new sockets are created. Note that this use of ssl_options will be
+deprecated in the next major release.
By default, a ``ca_certs`` value should be supplied (the value should be
a string pointing to the location of the CA certs file), and you probably
@@ -88,5 +275,8 @@
your configuration. For further reading, Andrew Mussey has published a
thorough guide on
`Using SSL with the DataStax Python driver
<http://blog.amussey.com/post/64036730812/cassandra-2-0-client-server-ssl-with-datastax-python>`_.
-*Note*: In case the twisted event loop is used pyOpenSSL must be installed or
an exception will be risen. Also
+SSL with Twisted
+++++++++++++++++
+
+In case the twisted event loop is used pyOpenSSL must be installed or an
exception will be risen. Also
to set the ``ssl_version`` and ``cert_reqs`` in ``ssl_opts`` the appropriate
constants from pyOpenSSL are expected.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/docs.yaml
new/python-driver-3.17.0/docs.yaml
--- old/python-driver-3.16.0/docs.yaml 2018-11-12 19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/docs.yaml 2019-02-15 22:05:51.000000000 +0100
@@ -22,6 +22,8 @@
# build extensions like libev
CASS_DRIVER_NO_CYTHON=1 python setup.py build_ext --inplace --force
versions:
+ - name: '3.17'
+ ref: 38e359e1
- name: '3.16'
ref: '3.16.0'
- name: '3.15'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/setup.py
new/python-driver-3.17.0/setup.py
--- old/python-driver-3.16.0/setup.py 2018-11-12 19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/setup.py 2019-02-15 22:05:51.000000000 +0100
@@ -395,7 +395,7 @@
# 1.) build_ext eats errors at compile time, letting the install
complete while producing useful feedback
# 2.) there could be a case where the python environment has cython
installed but the system doesn't have build tools
if pre_build_check():
- cython_dep = 'Cython>=0.20,!=0.25,<0.29'
+ cython_dep = 'Cython>=0.20,!=0.25,<0.30'
user_specified_cython_version =
os.environ.get('CASS_DRIVER_ALLOWED_CYTHON_VERSION')
if user_specified_cython_version is not None:
cython_dep = 'Cython==%s' % (user_specified_cython_version,)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/test-requirements.txt
new/python-driver-3.17.0/test-requirements.txt
--- old/python-driver-3.16.0/test-requirements.txt 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/test-requirements.txt 2019-02-15
22:05:51.000000000 +0100
@@ -11,6 +11,6 @@
twisted[tls]
gevent>=1.0
eventlet
-cython>=0.20,<0.29
+cython>=0.20,<0.30
packaging
asynctest; python_version > '3.4'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/tests/integration/__init__.py
new/python-driver-3.17.0/tests/integration/__init__.py
--- old/python-driver-3.16.0/tests/integration/__init__.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/tests/integration/__init__.py 2019-02-15
22:05:51.000000000 +0100
@@ -250,6 +250,7 @@
def _id_and_mark(f):
f.local = True
+ return f
return _id_and_mark
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/python-driver-3.16.0/tests/integration/long/ssl/driver_encrypted.key
new/python-driver-3.17.0/tests/integration/long/ssl/driver_encrypted.key
--- old/python-driver-3.16.0/tests/integration/long/ssl/driver_encrypted.key
1970-01-01 01:00:00.000000000 +0100
+++ new/python-driver-3.17.0/tests/integration/long/ssl/driver_encrypted.key
2019-02-15 22:05:51.000000000 +0100
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,4B3084A6FB74C60EF6AD51C4677FE509
+
+PRmOOSwQNcBYCBbGuv/YgKgSep00GGqCeyhej2soLaGKgSjPhe/cE7sFvm6jk72+
+wDJNpuY6gLoKmizvxrgi5iHnoVdaPkRXKkcQHgaLt4TjB2EUfPp/+V5N4FA4DxN2
+/jqfWZ5Fw8ltHbW1hEkxQ58cYWd5OQB8EesQ865Pv364px5GIcDyr9FEgTiE/Ulc
+wS5V1DSgytL/iwBP7FE/zejvZLiS0OoqNGq/G0nBSsv68CH7/lCs3i0Gn2E8YNW8
+CYdMnRzsxwPgbvWYYFhNeK1mrXC+RZa7FgLeeXWR6K28aSB8wc35PePBbZT6TViz
+jZkmBFvbpLrLP7KNogLdzhFLqBzkP0NgkYtseYFFcuUAmIrWPVrsoA4xHWMkdcB6
+OLMz+zpb5u8rjpa3gWh5xQJbdVDxXGJNxCYzT7sc28wzr8vHFJFg6U2DJL9x+ufW
+BXwBafb8biS1TBWGSDsc36NUwkyvv/2zjSytEIkdJ4lp4S69TGcU0Qvr1gA+8zLm
+o6/1G5wUTt461mAAlvcy/9pHZlm8K1VShBvBCYouoVfUyIYAKhq0bBm64E/jdE3v
+/Rxd/hom0d+m595wbH7f2AZCBVmII+hEyaagtKEO87qztq/WVMjN6h6BtXR5Xjsv
+fXg71mMlQvqYuTB7hOhpyPdux0vvGzCdlGmvUsAC+urqCfkUseLAFfTnD7IrfYK8
+lzRyFJPABtrDiVqQpCBsOq/ACERjVpn/XcJgEBui2jhaTodwWIVD9DPlf4o93Fhf
+oKE2o2W0TkaU/uaTLNt4TLJ0EXJ8S0XIqXnEvt3eRu0jOcC3ZWlFINdJISjm53LW
+o1q/6mlvj2COLxCipUfGLVmIRuuMDybndSiXH+vddLQDZSw9mynYzJUvMcurBYyL
+Lb9t+8hSRftRNqnM9ojDB+gmCJ0uQUlSAt47SZHGIi91S5pM7IELgP55SUG02mGn
+Pr+BBtQCVNbd1szPXH7dlUfD9eUHZ34uL/2wOMtNT7da4ajviR6HIyLiz3lrI+e+
+2X62fMFD5tnxffMIZLVPhAHHlT8RPzvspA7QHdJzWCsdxZ3VVmWCFL0WHKuP3Hhh
+0jIBOh0Y9eFCMcL7yP0rbjv/3MyBqqM4T5RfVkoRE3M/F8+kHObQzdUqy4JxApyb
+cb0ipAU3JxgEy2rOkXDoeYeRoCKVNU3MZ3x3/+oTZXBhlP+oIbqMo/bTr8JJFC6y
+y38YcWcVdUzYBI3KbH2aRCtUXNK404zm8GNs6hTuB95IrpI6APspNkQS9yhcxBdr
+zOjZh8snZ52gVDtgrNCSfSgflPEs1CNeg33PpVfZWCai/zwSVFwJuL4iyQqkTVlL
+wmbLUFdZN1AmYC2dJpHm30+cykXWJr/xFpAlZXmtgHlttD02pnTVl98G1rP3Oq8u
+NplSV/TOXVc3keBnQ7N5BohnQ1NSLq4wusVd7n9UEoCE6HxCUbWWkAKQIHom+5HI
+7PgRw0RJRUFbydBz+bDXz3KEFcTOT5ihtI/qq6tJpXMKz7uKGLAnzTIk2DecQYm4
+c40zTUKGVISky357ZIYB6uG9NRVffqsm9M1oIeprwYNpVzyqxz6Yyen+VCxdPq9w
+-----END RSA PRIVATE KEY-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/python-driver-3.16.0/tests/integration/long/test_policies.py
new/python-driver-3.17.0/tests/integration/long/test_policies.py
--- old/python-driver-3.16.0/tests/integration/long/test_policies.py
1970-01-01 01:00:00.000000000 +0100
+++ new/python-driver-3.17.0/tests/integration/long/test_policies.py
2019-02-15 22:05:51.000000000 +0100
@@ -0,0 +1,70 @@
+# Copyright DataStax, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest # noqa
+
+from cassandra import ConsistencyLevel, Unavailable
+from cassandra.cluster import Cluster, ExecutionProfile, EXEC_PROFILE_DEFAULT
+
+from tests.integration import use_cluster, get_cluster, get_node
+
+
+def setup_module():
+ use_cluster('test_cluster', [4])
+
+
+class RetryPolicyTests(unittest.TestCase):
+
+ @classmethod
+ def tearDownClass(cls):
+ cluster = get_cluster()
+ cluster.start() # make sure other nodes are restarted
+
+ def test_should_rethrow_on_unvailable_with_default_policy_if_cas(self):
+ """
+ Tests for the default retry policy in combination with lightweight
transactions.
+
+ @since 3.17
+ @jira_ticket PYTHON-1007
+ @expected_result the query is retried with the default CL, not the
serial one.
+
+ @test_category policy
+ """
+ ep = ExecutionProfile(consistency_level=ConsistencyLevel.ALL,
+ serial_consistency_level=ConsistencyLevel.SERIAL)
+
+ cluster = Cluster(execution_profiles={EXEC_PROFILE_DEFAULT: ep})
+ session = cluster.connect()
+
+ session.execute("CREATE KEYSPACE test_retry_policy_cas WITH
replication = {'class':'SimpleStrategy','replication_factor': 3};")
+ session.execute("CREATE TABLE test_retry_policy_cas.t (id int PRIMARY
KEY, data text);")
+ session.execute('INSERT INTO test_retry_policy_cas.t ("id", "data")
VALUES (%(0)s, %(1)s)', {'0': 42, '1': 'testing'})
+
+ get_node(2).stop()
+ get_node(4).stop()
+
+ # before fix: cassandra.InvalidRequest: Error from server: code=2200
[Invalid query] message="SERIAL is not
+ # supported as conditional update commit consistency. ....""
+
+ # after fix: cassandra.Unavailable (expected since replicas are down)
+ with self.assertRaises(Unavailable) as cm:
+ session.execute("update test_retry_policy_cas.t set data =
'staging' where id = 42 if data ='testing'")
+
+ exception = cm.exception
+ self.assertEqual(exception.consistency, ConsistencyLevel.SERIAL)
+ self.assertEqual(exception.required_replicas, 2)
+ self.assertEqual(exception.alive_replicas, 1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/python-driver-3.16.0/tests/integration/long/test_ssl.py
new/python-driver-3.17.0/tests/integration/long/test_ssl.py
--- old/python-driver-3.16.0/tests/integration/long/test_ssl.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/tests/integration/long/test_ssl.py 2019-02-15
22:05:51.000000000 +0100
@@ -17,7 +17,7 @@
except ImportError:
import unittest
-import os, sys, traceback, logging, ssl, time
+import os, sys, traceback, logging, ssl, time, math, uuid
from cassandra.cluster import Cluster, NoHostAvailable
from cassandra import ConsistencyLevel
from cassandra.query import SimpleStatement
@@ -34,6 +34,7 @@
# Client specific keys/certs
CLIENT_CA_CERTS = 'tests/integration/long/ssl/cassandra.pem'
DRIVER_KEYFILE = "tests/integration/long/ssl/driver.key"
+DRIVER_KEYFILE_ENCRYPTED = "tests/integration/long/ssl/driver_encrypted.key"
DRIVER_CERTFILE = "tests/integration/long/ssl/driver.pem"
DRIVER_CERTFILE_BAD = "tests/integration/long/ssl/python_driver_bad.pem"
@@ -48,10 +49,11 @@
verify_certs = {'cert_reqs': ssl.CERT_REQUIRED,
'check_hostname': True}
+
def setup_cluster_ssl(client_auth=False):
"""
We need some custom setup for this module. This will start the ccm cluster
with basic
- ssl connectivity, and client authenticiation if needed.
+ ssl connectivity, and client authentication if needed.
"""
use_single_node(start=False)
@@ -78,14 +80,18 @@
ccm_cluster.start(wait_for_binary_proto=True, wait_other_notice=True)
-def validate_ssl_options(ssl_options):
+def validate_ssl_options(**kwargs):
+ ssl_options = kwargs.get('ssl_options', None)
+ ssl_context = kwargs.get('ssl_context', None)
+
# find absolute path to client CA_CERTS
tries = 0
while True:
if tries > 5:
raise RuntimeError("Failed to connect to SSL cluster after 5
attempts")
try:
- cluster = Cluster(protocol_version=PROTOCOL_VERSION,
ssl_options=ssl_options)
+ cluster = Cluster(protocol_version=PROTOCOL_VERSION,
+ ssl_options=ssl_options,
ssl_context=ssl_context)
session = cluster.connect(wait_for_all_pools=True)
break
except Exception:
@@ -237,7 +243,7 @@
'ssl_version': ssl_version,
'keyfile': abs_driver_keyfile,
'certfile': abs_driver_certfile}
- validate_ssl_options(ssl_options)
+ validate_ssl_options(ssl_options=ssl_options)
def test_can_connect_with_ssl_client_auth_host_name(self):
"""
@@ -265,7 +271,7 @@
'certfile': abs_driver_certfile}
ssl_options.update(verify_certs)
- validate_ssl_options(ssl_options)
+ validate_ssl_options(ssl_options=ssl_options)
def test_cannot_connect_without_client_auth(self):
"""
@@ -315,3 +321,93 @@
with self.assertRaises(NoHostAvailable) as context:
cluster.connect()
cluster.shutdown()
+
+
+class SSLSocketErrorTests(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ setup_cluster_ssl()
+
+ @classmethod
+ def tearDownClass(cls):
+ ccm_cluster = get_cluster()
+ ccm_cluster.stop()
+ remove_cluster()
+
+ def test_ssl_want_write_errors_are_retried(self):
+ """
+ Test that when a socket receives a WANT_WRITE error, the message chunk
sending is retried.
+
+ @since 3.17.0
+ @jira_ticket PYTHON-891
+ @expected_result The query is executed successfully
+
+ @test_category connection:ssl
+ """
+ abs_path_ca_cert_path = os.path.abspath(CLIENT_CA_CERTS)
+ ssl_options = {'ca_certs': abs_path_ca_cert_path,
+ 'ssl_version': ssl_version}
+ cluster = Cluster(protocol_version=PROTOCOL_VERSION,
ssl_options=ssl_options)
+ session = cluster.connect(wait_for_all_pools=True)
+ try:
+ session.execute('drop keyspace ssl_error_test')
+ except:
+ pass
+ session.execute(
+ "CREATE KEYSPACE ssl_error_test WITH replication =
{'class':'SimpleStrategy','replication_factor':1};")
+ session.execute("CREATE TABLE ssl_error_test.big_text (id uuid PRIMARY
KEY, data text);")
+
+ params = {
+ '0': uuid.uuid4(),
+ '1': "0" * int(math.pow(10, 7))
+ }
+
+ session.execute('INSERT INTO ssl_error_test.big_text ("id", "data")
VALUES (%(0)s, %(1)s)', params)
+
+
+class SSLConnectionWithSSLContextTests(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ setup_cluster_ssl()
+
+ @classmethod
+ def tearDownClass(cls):
+ ccm_cluster = get_cluster()
+ ccm_cluster.stop()
+ remove_cluster()
+
+ def test_can_connect_with_sslcontext_certificate(self):
+ """
+ Test to validate that we are able to connect to a cluster using a
SSLContext.
+
+ @since 3.17.0
+ @jira_ticket PYTHON-995
+ @expected_result The client can connect via SSL and preform some basic
operations
+
+ @test_category connection:ssl
+ """
+ abs_path_ca_cert_path = os.path.abspath(CLIENT_CA_CERTS)
+ ssl_context = ssl.SSLContext(ssl_version)
+ ssl_context.load_verify_locations(abs_path_ca_cert_path)
+ validate_ssl_options(ssl_context=ssl_context)
+
+ def test_can_connect_with_ssl_client_auth_password_private_key(self):
+ """
+ Identical test to
SSLConnectionAuthTests.test_can_connect_with_ssl_client_auth,
+ the only difference is that the DRIVER_KEYFILE is encrypted with a
password.
+
+ @since 3.17.0
+ @jira_ticket PYTHON-995
+ @expected_result The client can connect via SSL and preform some basic
operations
+
+ @test_category connection:ssl
+ """
+ abs_driver_keyfile = os.path.abspath(DRIVER_KEYFILE_ENCRYPTED)
+ abs_driver_certfile = os.path.abspath(DRIVER_CERTFILE)
+ ssl_context = ssl.SSLContext(ssl_version)
+ ssl_context.load_cert_chain(certfile=abs_driver_certfile,
+ keyfile=abs_driver_keyfile,
+ password='cassandra')
+ validate_ssl_options(ssl_context=ssl_context)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/python-driver-3.16.0/tests/integration/standard/test_query.py
new/python-driver-3.17.0/tests/integration/standard/test_query.py
--- old/python-driver-3.16.0/tests/integration/standard/test_query.py
2018-11-12 19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/tests/integration/standard/test_query.py
2019-02-15 22:05:51.000000000 +0100
@@ -33,8 +33,15 @@
from tests.integration import greaterthanorequalcass30, get_node
import time
+import random
import re
+import mock
+
+
+log = logging.getLogger(__name__)
+
+
def setup_module():
if not USE_CASS_EXTERNAL:
use_singledc(start=False)
@@ -326,6 +333,33 @@
self.assertEqual(results.column_names, ["[json]"])
self.assertEqual(results[0][0], '{"k": 1, "v": 1}')
+ def test_host_targeting_query(self):
+ """
+ Test to validate the the single host targeting works.
+
+ @since 3.17.0
+ @jira_ticket PYTHON-933
+ @expected_result the coordinator host is always the one set
+ """
+
+ default_ep = self.cluster.profile_manager.default
+ # copy of default EP with checkable LBP
+ checkable_ep = self.session.execution_profile_clone_update(
+ ep=default_ep,
+
load_balancing_policy=mock.Mock(wraps=default_ep.load_balancing_policy)
+ )
+ query = SimpleStatement("INSERT INTO test3rf.test(k, v) values (1, 1)")
+
+ for i in range(10):
+ host = random.choice(self.cluster.metadata.all_hosts())
+ log.debug('targeting {}'.format(host))
+ future = self.session.execute_async(query, host=host,
execution_profile=checkable_ep)
+ future.result()
+ # check we're using the selected host
+ self.assertEqual(host, future.coordinator_host)
+ # check that this bypasses the LBP
+
self.assertFalse(checkable_ep.load_balancing_policy.make_query_plan.called)
+
class PreparedStatementTests(unittest.TestCase):
@@ -568,7 +602,7 @@
select_results = session.execute("SELECT * FROM %s" % table)
expected_results = [(1, None, 2, None, 3), (2, None, 3, None, 4),
(3, None, 4, None, 5), (4, None, 5, None, 6)]
-
+
self.assertEqual(set(expected_results),
set(select_results._current_rows))
@@ -1421,7 +1455,7 @@
# <Host: 127.0.0.1 datacenter1>: ConnectionException('Host has been
marked down or removed',)})
with self.assertRaises(NoHostAvailable):
session.execute(simple_stmt)
-
+
def _check_set_keyspace_in_statement(self, session):
simple_stmt = SimpleStatement("SELECT * from
{}".format(self.table_name), keyspace=self.ks_name)
results = session.execute(simple_stmt)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/tests/unit/test_cluster.py
new/python-driver-3.17.0/tests/unit/test_cluster.py
--- old/python-driver-3.16.0/tests/unit/test_cluster.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/tests/unit/test_cluster.py 2019-02-15
22:05:51.000000000 +0100
@@ -17,6 +17,7 @@
import unittest # noqa
import logging
+import six
from mock import patch, Mock
@@ -147,8 +148,13 @@
# default is None
self.assertIsNone(s.default_serial_consistency_level)
- sentinel = 1001
- for cl in (None, ConsistencyLevel.LOCAL_SERIAL,
ConsistencyLevel.SERIAL, sentinel):
+ # Should fail
+ with self.assertRaises(ValueError):
+ s.default_serial_consistency_level = ConsistencyLevel.ANY
+ with self.assertRaises(ValueError):
+ s.default_serial_consistency_level = 1001
+
+ for cl in (None, ConsistencyLevel.LOCAL_SERIAL,
ConsistencyLevel.SERIAL):
s.default_serial_consistency_level = cl
# default is passed through
@@ -204,19 +210,37 @@
@mock_session_pools
def test_default_profile(self):
- non_default_profile = ExecutionProfile(RoundRobinPolicy(), *[object()
for _ in range(3)])
+ non_default_profile = ExecutionProfile(RoundRobinPolicy(), *[object()
for _ in range(2)])
cluster = Cluster(execution_profiles={'non-default':
non_default_profile})
session = Session(cluster, hosts=[Host("127.0.0.1",
SimpleConvictionPolicy)])
self.assertEqual(cluster._config_mode, _ConfigMode.PROFILES)
- default_profile =
cluster.profile_manager.profiles[EXEC_PROFILE_DEFAULT]
+ default_profile = session.get_execution_profile(EXEC_PROFILE_DEFAULT)
rf = session.execute_async("query")
self._verify_response_future_profile(rf, default_profile)
rf = session.execute_async("query", execution_profile='non-default')
self._verify_response_future_profile(rf, non_default_profile)
+ for name, ep in six.iteritems(cluster.profile_manager.profiles):
+ self.assertEqual(ep, session.get_execution_profile(name))
+
+ # invalid ep
+ with self.assertRaises(ValueError):
+ session.get_execution_profile('non-existent')
+
+ def test_serial_consistency_level_validation(self):
+ # should pass
+ ep = ExecutionProfile(RoundRobinPolicy(),
serial_consistency_level=ConsistencyLevel.SERIAL)
+ ep = ExecutionProfile(RoundRobinPolicy(),
serial_consistency_level=ConsistencyLevel.LOCAL_SERIAL)
+
+ # should not pass
+ with self.assertRaises(ValueError):
+ ep = ExecutionProfile(RoundRobinPolicy(),
serial_consistency_level=ConsistencyLevel.ANY)
+ with self.assertRaises(ValueError):
+ ep = ExecutionProfile(RoundRobinPolicy(),
serial_consistency_level=42)
+
@mock_session_pools
def test_statement_params_override_legacy(self):
cluster = Cluster(load_balancing_policy=RoundRobinPolicy(),
default_retry_policy=DowngradingConsistencyRetryPolicy())
@@ -240,7 +264,7 @@
@mock_session_pools
def test_statement_params_override_profile(self):
- non_default_profile = ExecutionProfile(RoundRobinPolicy(), *[object()
for _ in range(3)])
+ non_default_profile = ExecutionProfile(RoundRobinPolicy(), *[object()
for _ in range(2)])
cluster = Cluster(execution_profiles={'non-default':
non_default_profile})
session = Session(cluster, hosts=[Host("127.0.0.1",
SimpleConvictionPolicy)])
@@ -309,7 +333,7 @@
@mock_session_pools
def test_profile_name_value(self):
- internalized_profile = ExecutionProfile(RoundRobinPolicy(), *[object()
for _ in range(3)])
+ internalized_profile = ExecutionProfile(RoundRobinPolicy(), *[object()
for _ in range(2)])
cluster = Cluster(execution_profiles={'by-name': internalized_profile})
session = Session(cluster, hosts=[Host("127.0.0.1",
SimpleConvictionPolicy)])
self.assertEqual(cluster._config_mode, _ConfigMode.PROFILES)
@@ -317,7 +341,7 @@
rf = session.execute_async("query", execution_profile='by-name')
self._verify_response_future_profile(rf, internalized_profile)
- by_value = ExecutionProfile(RoundRobinPolicy(), *[object() for _ in
range(3)])
+ by_value = ExecutionProfile(RoundRobinPolicy(), *[object() for _ in
range(2)])
rf = session.execute_async("query", execution_profile=by_value)
self._verify_response_future_profile(rf, by_value)
@@ -337,7 +361,7 @@
# default and one named
for profile in (EXEC_PROFILE_DEFAULT, 'one'):
- active = cluster.profile_manager.profiles[profile]
+ active = session.get_execution_profile(profile)
clone = session.execution_profile_clone_update(profile)
self.assertIsNot(clone, active)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/tests/unit/test_connection.py
new/python-driver-3.17.0/tests/unit/test_connection.py
--- old/python-driver-3.16.0/tests/unit/test_connection.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/tests/unit/test_connection.py 2019-02-15
22:05:51.000000000 +0100
@@ -411,14 +411,6 @@
[call(connection)] * get_holders.call_count)
-class LZ4Tests(unittest.TestCase):
- def test_lz4_is_correctly_imported(self):
- try:
- import lz4
- except ImportError:
- return
- from lz4 import block as lz4_block
-
class TimerTest(unittest.TestCase):
def test_timer_collision(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.16.0/tests/unit/test_policies.py
new/python-driver-3.17.0/tests/unit/test_policies.py
--- old/python-driver-3.16.0/tests/unit/test_policies.py 2018-11-12
19:19:38.000000000 +0100
+++ new/python-driver-3.17.0/tests/unit/test_policies.py 2019-02-15
22:05:51.000000000 +0100
@@ -1021,13 +1021,13 @@
query=None, consistency=ONE,
required_replicas=1, alive_replicas=2, retry_num=0)
self.assertEqual(retry, RetryPolicy.RETRY_NEXT_HOST)
- self.assertEqual(consistency, ONE)
+ self.assertEqual(consistency, None)
retry, consistency = policy.on_unavailable(
query=None, consistency=ONE,
required_replicas=10000, alive_replicas=1, retry_num=0)
self.assertEqual(retry, RetryPolicy.RETRY_NEXT_HOST)
- self.assertEqual(consistency, ONE)
+ self.assertEqual(consistency, None)
class FallthroughRetryPolicyTest(unittest.TestCase):