def setUp(self):
        self.factory_patcher = mock.patch(
            'silverberg.thrift_client._ThriftClientFactory')

        self.factory = self.factory_patcher.start()

        def _create_factory(client_class, connection_lost):
            self.connection_lost = connection_lost

        self.factory.side_effect = _create_factory

        self.addCleanup(self.factory_patcher.stop)

        self.endpoint = mock.Mock()
        self.twisted_transport = mock.Mock()
        self.thrift_cassandra_client = mock.Mock(Client)

        def _connect(factory):
            self.connect_d = Deferred()
            wrapper = mock.Mock()
            wrapper.transport = self.twisted_transport
            wrapper.wrapped.client = self.thrift_cassandra_client
            return self.connect_d.addCallback(lambda _: wrapper)

        self.endpoint.connect.side_effect = _connect

        self.client = OnDemandThriftClient(self.endpoint, mock.Mock())
Exemple #2
0
    def __init__(self, cass_endpoint, keyspace, user=None, password=None,
                 disconnect_on_cancel=False):
        self._client = OnDemandThriftClient(cass_endpoint, Cassandra.Client)

        self._keyspace = keyspace
        self._user = user
        self._password = password
        self._disconnect_on_cancel = disconnect_on_cancel
    def setUp(self):
        self.factory_patcher = mock.patch('silverberg.thrift_client._ThriftClientFactory')

        self.factory = self.factory_patcher.start()

        def _create_factory(client_class, connection_lost):
            self.connection_lost = connection_lost

        self.factory.side_effect = _create_factory

        self.addCleanup(self.factory_patcher.stop)

        self.endpoint = mock.Mock()
        self.twisted_transport = mock.Mock()
        self.thrift_cassandra_client = mock.Mock(Client)

        def _connect(factory):
            self.connect_d = Deferred()
            wrapper = mock.Mock()
            wrapper.transport = self.twisted_transport
            wrapper.wrapped.client = self.thrift_cassandra_client
            return self.connect_d.addCallback(lambda _: wrapper)

        self.endpoint.connect.side_effect = _connect

        self.client = OnDemandThriftClient(self.endpoint, mock.Mock())
Exemple #4
0
    def __init__(self, cass_endpoint, keyspace, user=None, password=None,
                 disconnect_on_cancel=False):
        self._client = OnDemandThriftClient(cass_endpoint, Cassandra.Client)

        self._keyspace = keyspace
        self._user = user
        self._password = password
        self._disconnect_on_cancel = disconnect_on_cancel
Exemple #5
0
    def __init__(self, cass_endpoint, keyspace, user=None, password=None):
        self._client = OnDemandThriftClient(cass_endpoint, Cassandra.Client)

        self._keyspace = keyspace
        self._user = user
        self._password = password
Exemple #6
0
class CQLClient(object):
    """
    Cassandra CQL Client object.

    Instantiate it and it will on-demand create a connection to the Cassandra
    cluster.

    :param cass_endpoint: A twisted Endpoint
    :type cass_endpoint: twisted.internet.interfaces.IStreamClientEndpoint

    :param keyspace: A keyspace to connect to
    :type keyspace: str.

    :param user: Username to connect with.
    :type user: str.

    :param password: Password to use in conjunction with Username.
    :type password: str.

    Upon connecting, the client will authenticate (if paramaters are provided)
    and obtain the keyspace definition so that it can de-serialize properly.

    n.b. Cassandra presently doesn't have any real support for password
    authentication in the mainline as the simple access control options
    are disabled; you probably need to secure your Cassandra server using
    different methods and the password code isn't heavily tested.
    """

    def __init__(self, cass_endpoint, keyspace, user=None, password=None):
        self._client = OnDemandThriftClient(cass_endpoint, Cassandra.Client)

        self._keyspace = keyspace
        self._user = user
        self._password = password

    def _set_keyspace(self, client):
        d = client.set_keyspace(self._keyspace)
        return d.addCallback(lambda _: client)

    def _login(self, client):
        creds = {'user': self._user, 'password': self._password}
        authreq = ttypes.AuthenticationRequest(creds)
        d = client.login(authreq)
        d.addCallback(lambda _: client)
        return d

    def _connection(self):
        def _handshake(client):
            d = succeed(client)
            if self._user and self._password:
                d.addCallback(self._login)
            d.addCallback(self._set_keyspace)
            return d

        ds = self._client.connection(_handshake)
        return ds

    def disconnect(self):
        """
        Disconnect from the cassandra cluster.  Cassandara and Silverberg do
        not require the connection to be closed before exiting.  However, this
        method may be useful if resources are constrained, or for testing
        purposes if using or injecting :class:`TestCQLClient: is impossible.

        :return: a :class:`Deferred` that fires with None when disconnected.
        """
        return self._client.disconnect()

    def describe_version(self):
        """
        Query the Cassandra server for the version.

        :returns: string -- the version tag
        """
        def _vers(client):
            return client.describe_version()

        d = self._connection()
        d.addCallback(_vers)
        return d

    def _unmarshal_result(self, schema, raw_rows, _unmarshallers):
        rows = []

        def _unmarshal_val(vtype, val):
            if val is None:
                return

            # Differentiate between regular and collection types
            # Collection types look like 'ListType(SomeCassandraType)',
            # so try split into two parts and check if we can marshal them
            types = str(vtype).replace(")", "").split("(")

            # Regular type
            if len(types) == 1 and vtype in _unmarshallers:
                return _unmarshallers[vtype](val)

            # Collection
            elif len(types) == 2 and types[0] in _unmarshallers and types[1] in _unmarshallers:
                return _unmarshallers[types[0]](types[1], val)

            # XXX: We do not currently implement the full range of types.
            # So we can not unmarshal all types in which case we should just
            # return the raw bytes.
            return val

        for raw_row in raw_rows:
            row = {}
            for raw_col in raw_row.columns:
                specific = schema.value_types[raw_col.name]
                row[raw_col.name] = _unmarshal_val(specific, raw_col.value)
            rows.append(row)
        return rows

    def execute(self, query, args, consistency):
        """
        Execute a CQL query against the server.

        :param query: The CQL query to execute
        :type query: str.

        :param args: The arguments to substitute
        :type args: dict.

        :param consistency: The consistency level
        :type consistency: ConsistencyLevel

        In order to avoid unpleasant issues of CQL injection
        (Hey, just because there's no SQL doesn't mean that Little
        Bobby Tables won't mess things up for you like in XKCD #327)
        you probably want to use argument substitution instead of
        concatting strings together to build a query.

        Thus, like the official CQL driver for non-Twisted python
        that comes with the Cassandra distro, we do variable substitution.

        Example::

            d = client.execute("UPDATE :table SET 'fff' = :val WHERE "
            "KEY = :key",{"val":1234, "key": "fff", "table": "blah"})

        :returns: A Deferred that fires with either None, an int, or an
                  iterable of `{'column': value, ...}` dictionaries, depending
                  on the CQL query.  e.g. a UPDATE would return None,
                  whereas a SELECT would return an int or some rows

        Example output::

            [{"fff": 1222}]
        """
        prep_query = prepare(query, args)

        def _execute(client):
            return client.execute_cql3_query(prep_query,
                                             ttypes.Compression.NONE, consistency)

        def _proc_results(result):
            if result.type == ttypes.CqlResultType.ROWS:
                return self._unmarshal_result(result.schema, result.rows,
                                              unmarshallers)
            elif result.type == ttypes.CqlResultType.INT:
                return result.num
            else:
                return None

        d = self._connection()
        d.addCallback(_execute)
        d.addCallback(_proc_results)
        return d
class OnDemandThriftClientTests(BaseTestCase):
    def setUp(self):
        self.factory_patcher = mock.patch('silverberg.thrift_client._ThriftClientFactory')

        self.factory = self.factory_patcher.start()

        def _create_factory(client_class, connection_lost):
            self.connection_lost = connection_lost

        self.factory.side_effect = _create_factory

        self.addCleanup(self.factory_patcher.stop)

        self.endpoint = mock.Mock()
        self.twisted_transport = mock.Mock()
        self.thrift_cassandra_client = mock.Mock(Client)

        def _connect(factory):
            self.connect_d = Deferred()
            wrapper = mock.Mock()
            wrapper.transport = self.twisted_transport
            wrapper.wrapped.client = self.thrift_cassandra_client
            return self.connect_d.addCallback(lambda _: wrapper)

        self.endpoint.connect.side_effect = _connect

        self.client = OnDemandThriftClient(self.endpoint, mock.Mock())

    def test_initial_connect(self):
        d = self.client.connection()

        self.connect_d.callback(None)

        self.assertEqual(self.assertFired(d), self.thrift_cassandra_client)

    def test_initial_handshake_non_deferred(self):
        def _mock_handshake(client):
            return None

        m = mock.MagicMock(side_effect=_mock_handshake)

        d = self.client.connection(m)

        self.connect_d.callback(None)

        self.assertEqual(self.assertFired(d), self.thrift_cassandra_client)
        m.assert_called_once()

    def test_initial_handshake_deferred(self):
        def _mock_handshake_d(client):
            return succeed(None)

        m = mock.MagicMock(side_effect=_mock_handshake_d)

        d = self.client.connection(m)
        self.connect_d.callback(None)

        self.assertEqual(self.assertFired(d), self.thrift_cassandra_client)
        m.assert_called_once()

    def test_connected(self):
        d1 = self.client.connection()

        self.connect_d.callback(None)

        d2 = self.client.connection()

        self.assertNotIdentical(d1, d2)

        self.assertEqual(self.assertFired(d2), self.thrift_cassandra_client)

    def test_connect_while_connecting(self):
        d1 = self.client.connection()

        d2 = self.client.connection()

        self.assertNotIdentical(d1, d2)

        self.connect_d.callback(None)

        self.assertEqual(self.assertFired(d1), self.thrift_cassandra_client)
        self.assertEqual(self.assertFired(d2), self.thrift_cassandra_client)

    def test_connect_failed(self):
        d = self.client.connection()

        self.connect_d.errback(_TestConnectError())

        self.assertFailed(d, _TestConnectError)

    def test_connection_lost_cleanly(self):
        d = self.client.connection()

        self.connect_d.callback(None)
        self.assertFired(d)

        self.connection_lost(Failure(_TestConnectionDone()))

    @mock.patch('silverberg.thrift_client.log')
    def test_connection_lost_uncleanly(self, mock_log):
        self.twisted_transport.getPeer.return_value = 'peer addr'
        d = self.client.connection()

        self.connect_d.callback(None)

        self.assertFired(d)

        reason = Failure(_TestConnectionLost())
        self.connection_lost(reason)

        mock_log.err.assert_called_once_with(
            reason, "Lost current connection to 'peer addr', reconnecting on demand.",
            system='OnDemandThriftClient', node='peer addr')

    def test_disconnect(self):
        d1 = self.client.connection()

        self.connect_d.callback(None)

        self.assertFired(d1)

        d2 = self.client.disconnect()

        self.twisted_transport.loseConnection.assert_called_once()

        self.connection_lost(Failure(_TestConnectionDone()))

        self.assertFired(d2)

    def test_connect_while_disconnecting(self):
        d1 = self.client.connection()

        self.connect_d.callback(None)

        self.assertFired(d1)

        d2 = self.client.disconnect()

        d3 = self.client.connection()

        self.connection_lost(Failure(_TestConnectionDone()))

        self.assertFired(d2)

        self.assertFailed(d3, ClientDisconnecting)

    def test_disconnect_while_connecting(self):
        d1 = self.client.connection()
        d2 = self.client.disconnect()

        self.assertFailed(d2, ClientConnecting)
        self.assertEqual(
            len(self.twisted_transport.loseConnection.mock_calls), 0,
            "loseConnection should not be called since not connected")

        self.connect_d.callback(None)

        self.assertFired(d1)

    def test_disconnect_while_disconnecting(self):
        self.client.connection()
        self.connect_d.callback(None)

        d1 = self.client.disconnect()
        self.twisted_transport.reset_mock()
        d2 = self.client.disconnect()

        self.connection_lost(Failure(_TestConnectionDone()))
        self.assertEqual(
            len(self.twisted_transport.loseConnection.mock_calls), 0,
            "loseConnection should not be called since not connected")

        self.assertFired(d1)
        self.assertFired(d2)

    def test_disconnect_while_not_connected(self):
        d1 = self.client.disconnect()
        self.assertIdentical(self.assertFired(d1), None)
        self.assertEqual(
            len(self.twisted_transport.loseConnection.mock_calls), 0,
            "loseConnection should not be called since not connected")
class OnDemandThriftClientTests(BaseTestCase):
    def setUp(self):
        self.factory_patcher = mock.patch(
            'silverberg.thrift_client._ThriftClientFactory')

        self.factory = self.factory_patcher.start()

        def _create_factory(client_class, connection_lost):
            self.connection_lost = connection_lost

        self.factory.side_effect = _create_factory

        self.addCleanup(self.factory_patcher.stop)

        self.endpoint = mock.Mock()
        self.twisted_transport = mock.Mock()
        self.thrift_cassandra_client = mock.Mock(Client)

        def _connect(factory):
            self.connect_d = Deferred()
            wrapper = mock.Mock()
            wrapper.transport = self.twisted_transport
            wrapper.wrapped.client = self.thrift_cassandra_client
            return self.connect_d.addCallback(lambda _: wrapper)

        self.endpoint.connect.side_effect = _connect

        self.client = OnDemandThriftClient(self.endpoint, mock.Mock())

    def test_initial_connect(self):
        d = self.client.connection()

        self.connect_d.callback(None)

        self.assertEqual(self.assertFired(d), self.thrift_cassandra_client)

    def test_initial_handshake_non_deferred(self):
        def _mock_handshake(client):
            return None

        m = mock.MagicMock(side_effect=_mock_handshake)

        d = self.client.connection(m)

        self.connect_d.callback(None)

        self.assertEqual(self.assertFired(d), self.thrift_cassandra_client)
        m.assert_called_once()

    def test_initial_handshake_deferred(self):
        def _mock_handshake_d(client):
            return succeed(None)

        m = mock.MagicMock(side_effect=_mock_handshake_d)

        d = self.client.connection(m)
        self.connect_d.callback(None)

        self.assertEqual(self.assertFired(d), self.thrift_cassandra_client)
        m.assert_called_once()

    def test_connected(self):
        d1 = self.client.connection()

        self.connect_d.callback(None)

        d2 = self.client.connection()

        self.assertNotIdentical(d1, d2)

        self.assertEqual(self.assertFired(d2), self.thrift_cassandra_client)

    def test_connect_while_connecting(self):
        d1 = self.client.connection()

        d2 = self.client.connection()

        self.assertNotIdentical(d1, d2)

        self.connect_d.callback(None)

        self.assertEqual(self.assertFired(d1), self.thrift_cassandra_client)
        self.assertEqual(self.assertFired(d2), self.thrift_cassandra_client)

    def test_connect_failed(self):
        d = self.client.connection()

        self.connect_d.errback(_TestConnectError())

        self.assertFailed(d, _TestConnectError)

    def test_connection_lost_cleanly(self):
        d = self.client.connection()

        self.connect_d.callback(None)
        self.assertFired(d)

        self.connection_lost(Failure(_TestConnectionDone()))

    @mock.patch('silverberg.thrift_client.log')
    def test_connection_lost_uncleanly(self, mock_log):
        self.twisted_transport.getPeer.return_value = 'peer addr'
        d = self.client.connection()

        self.connect_d.callback(None)

        self.assertFired(d)

        reason = Failure(_TestConnectionLost())
        self.connection_lost(reason)

        mock_log.err.assert_called_once_with(
            reason,
            "Lost current connection to 'peer addr', reconnecting on demand.",
            system='OnDemandThriftClient',
            node='peer addr')

    def test_disconnect(self):
        d1 = self.client.connection()

        self.connect_d.callback(None)

        self.assertFired(d1)

        d2 = self.client.disconnect()

        self.twisted_transport.loseConnection.assert_called_once()

        self.connection_lost(Failure(_TestConnectionDone()))

        self.assertFired(d2)

    def test_connect_while_disconnecting(self):
        d1 = self.client.connection()

        self.connect_d.callback(None)

        self.assertFired(d1)

        d2 = self.client.disconnect()

        d3 = self.client.connection()

        self.connection_lost(Failure(_TestConnectionDone()))

        self.assertFired(d2)

        self.assertFailed(d3, ClientDisconnecting)

    def test_disconnect_while_connecting(self):
        d1 = self.client.connection()
        d2 = self.client.disconnect()

        self.assertFailed(d2, ClientConnecting)
        self.assertEqual(
            len(self.twisted_transport.loseConnection.mock_calls), 0,
            "loseConnection should not be called since not connected")

        self.connect_d.callback(None)

        self.assertFired(d1)

    def test_disconnect_while_disconnecting(self):
        self.client.connection()
        self.connect_d.callback(None)

        d1 = self.client.disconnect()
        self.twisted_transport.reset_mock()
        d2 = self.client.disconnect()

        self.connection_lost(Failure(_TestConnectionDone()))
        self.assertEqual(
            len(self.twisted_transport.loseConnection.mock_calls), 0,
            "loseConnection should not be called since not connected")

        self.assertFired(d1)
        self.assertFired(d2)

    def test_disconnect_while_not_connected(self):
        d1 = self.client.disconnect()
        self.assertIdentical(self.assertFired(d1), None)
        self.assertEqual(
            len(self.twisted_transport.loseConnection.mock_calls), 0,
            "loseConnection should not be called since not connected")