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):


Reply via email to