tarantool / tarantool-python

Python client library for Tarantool
https://www.tarantool.io
BSD 2-Clause "Simplified" License
101 stars 46 forks source link

backward compatibility is broken: module is not compatible with cartridge remote control #283

Closed olegrok closed 1 year ago

olegrok commented 1 year ago

After update to 0.12.0 some my tests started to fail with following backtrace:

self = <tarantool.connection.Connection object at 0x11b35aaa0>

    def connect(self):
        """
        Create a connection to the host and port specified on
        initialization. There is no need to call this method explicitly
        until you have set ``connect_now=False`` on initialization.

        :raise: :exc:`~tarantool.error.NetworkError`,
            :exc:`~tarantool.error.SslError`,
            :exc:`~tarantool.error.SchemaError`,
            :exc:`~tarantool.error.DatabaseError`
        """

        try:
            self.connect_basic()
            if self.transport == SSL_TRANSPORT:
                self.wrap_socket_ssl()
>           self.handshake()

/usr/local/lib/python3.10/site-packages/tarantool/connection.py:1039: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tarantool.connection.Connection object at 0x11b35aaa0>

    def handshake(self):
        """
        Process greeting with Tarantool server.

        :raise: :exc:`~ValueError`,
            :exc:`~tarantool.error.NetworkError`

        :meta private:
        """

        greeting_buf = self._recv(IPROTO_GREETING_SIZE)
        greeting = greeting_decode(greeting_buf)
        if greeting.protocol != "Binary":
            raise NetworkError("Unsupported protocol: " + greeting.protocol)
        self.version_id = greeting.version_id
>       self._check_features()

/usr/local/lib/python3.10/site-packages/tarantool/connection.py:1017: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tarantool.connection.Connection object at 0x11b35aaa0>

    def _check_features(self):
        """
        Execute an ID request: inform the server about the protocol
        version and features connector support and get server-side
        information about it.

        After executing this request, the connector will choose a
        protocol version and features supported both by connector and
        server.

        :raise: :exc:`~AssertionError`,
            :exc:`~tarantool.error.DatabaseError`,
            :exc:`~tarantool.error.SchemaError`,
            :exc:`~tarantool.error.NetworkError`,
            :exc:`~tarantool.error.SslError`
        """

        try:
            request = RequestProtocolVersion(self,
                                             CONNECTOR_IPROTO_VERSION,
                                             CONNECTOR_FEATURES)
            response = self._send_request(request)
            server_protocol_version = response.protocol_version
            server_features = response.features
            server_auth_type = response.auth_type
        except DatabaseError as exc:
            ER_UNKNOWN_REQUEST_TYPE = 48
            if exc.code == ER_UNKNOWN_REQUEST_TYPE:
                server_protocol_version = None
                server_features = []
                server_auth_type = None
            else:
>               raise exc

/usr/local/lib/python3.10/site-packages/tarantool/connection.py:2149: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tarantool.connection.Connection object at 0x11b35aaa0>

    def _check_features(self):
        """
        Execute an ID request: inform the server about the protocol
        version and features connector support and get server-side
        information about it.

        After executing this request, the connector will choose a
        protocol version and features supported both by connector and
        server.

        :raise: :exc:`~AssertionError`,
            :exc:`~tarantool.error.DatabaseError`,
            :exc:`~tarantool.error.SchemaError`,
            :exc:`~tarantool.error.NetworkError`,
            :exc:`~tarantool.error.SslError`
        """

        try:
            request = RequestProtocolVersion(self,
                                             CONNECTOR_IPROTO_VERSION,
                                             CONNECTOR_FEATURES)
>           response = self._send_request(request)

/usr/local/lib/python3.10/site-packages/tarantool/connection.py:2138: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tarantool.connection.Connection object at 0x11b35aaa0>
request = <tarantool.request.RequestProtocolVersion object at 0x11b35b640>, on_push = None
on_push_ctx = None

    def _send_request(self, request, on_push=None, on_push_ctx=None):
        """
        Send a request to the server through the socket.

        :param request: Request to send.
        :type request: :class:`~tarantool.request.Request`

        :param on_push: Сallback for processing out-of-band messages.
        :type on_push: :obj:`function`, optional

        :param on_push_ctx: Сontext for working with on_push callback.
        :type on_push_ctx: optional

        :rtype: :class:`~tarantool.response.Response`

        :raise: :exc:`~AssertionError`,
            :exc:`~tarantool.error.DatabaseError`,
            :exc:`~tarantool.error.SchemaError`,
            :exc:`~tarantool.error.NetworkError`,
            :exc:`~tarantool.error.SslError`

        :meta private:
        """
        assert isinstance(request, Request)

        self._opt_reconnect()

>       return self._send_request_wo_reconnect(request, on_push, on_push_ctx)

/usr/local/lib/python3.10/site-packages/tarantool/connection.py:1249: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tarantool.connection.Connection object at 0x11b35aaa0>
request = <tarantool.request.RequestProtocolVersion object at 0x11b35b640>, on_push = None
on_push_ctx = None

    def _send_request_wo_reconnect(self, request, on_push=None, on_push_ctx=None):
        """
        Send request without trying to reconnect.
        Reload schema, if required.

        :param request: Request to send.
        :type request: :class:`~tarantool.request.Request`

        :param on_push: Сallback for processing out-of-band messages.
        :type on_push: :obj:`function`, optional

        :param on_push_ctx: Сontext for working with on_push callback.
        :type on_push_ctx: optional

        :rtype: :class:`~tarantool.response.Response`

        :raise: :exc:`~AssertionError`,
            :exc:`~tarantool.error.SchemaError`,
            :exc:`~tarantool.error.NetworkError`

        :meta private:
        """

        assert isinstance(request, Request)

        response = None
        while True:
            try:
                self._socket.sendall(bytes(request))
>               response = request.response_class(self, self._read_response())

/usr/local/lib/python3.10/site-packages/tarantool/connection.py:1136: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tarantool.connection.Connection object at 0x11b35aaa0>

    def _read_response(self):
        """
        Read response from the transport (socket).

        :return: Tuple of the form ``(header, body)``.
        :rtype: :obj:`tuple`

        :meta private:
        """

        # Read packet length
>       length = msgpack.unpackb(self._recv(5))

/usr/local/lib/python3.10/site-packages/tarantool/connection.py:1103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tarantool.connection.Connection object at 0x11b35aaa0>, to_read = 5

    def _recv(self, to_read):
        """
        Receive binary data from connection socket.

        :param to_read: Amount of data to read, in bytes.
        :type to_read: :obj:`int`

        :return: Buffer with read data
        :rtype: :obj:`bytes`

        :meta private:
        """

        buf = b""
        while to_read > 0:
            try:
                tmp = self._socket.recv(to_read)
            except OverflowError:
                self._socket.close()
                err = socket.error(
                    errno.ECONNRESET,
                    "Packet too large. Closing connection to server"
                )
                raise NetworkError(err)
            except socket.error:
                err = socket.error(
                    errno.ECONNRESET,
                    "Lost connection to server during query"
                )
                raise NetworkError(err)
            else:
                if len(tmp) == 0:
                    err = socket.error(
                        errno.ECONNRESET,
                        "Lost connection to server during query"
                    )
>                   raise NetworkError(err)
E                   tarantool.error.NetworkError: (54, 'Connection reset by peer')

/usr/local/lib/python3.10/site-packages/tarantool/connection.py:1087: NetworkError

tarantoolctl is able to connect to my instance. However tarantool-python is not. Seems it happens because module now is not compatible with cartridge.remote-control (and probably some ancient Tarantool versions). There is workaround in cartridge code to make it compatible with netbox - https://github.com/tarantool/cartridge/commit/1649b40743bd05f2e71f2ce1ad5b5caf19f864d4 but seems these connector doesn't consider it.