예제 #1
0
 def test_rpc_remove_response(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['Test'])
     self.assertEqual(len(rpc._response), 1)
     self.assertEqual(len(rpc._response[uuid]), 0)
     rpc.remove_response(uuid)
     self.assertEqual(len(rpc._response), 0)
예제 #2
0
 def test_rpc_remove_response(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     self.assertEqual(len(rpc._response), 1)
     self.assertEqual(len(rpc._response[uuid]), 0)
     rpc.remove_response(uuid)
     self.assertEqual(len(rpc._response), 0)
예제 #3
0
 def test_rpc_get_request_timeout_raw(self):
     rpc = Rpc(FakeConnection(), timeout=0.1)
     uuid = rpc.register_request(['travis-ci'])
     self.assertRaisesRegexp(AMQPChannelError,
                             'rpc requests %s \(travis-ci\) took too long' %
                             uuid,
                             rpc.get_request,
                             uuid=uuid,
                             raw=False)
예제 #4
0
 def test_rpc_get_request_timeout_raw(self):
     rpc = Rpc(FakeConnection(), timeout=0.1)
     uuid = rpc.register_request(['travis-ci'])
     self.assertRaisesRegexp(
         AMQPChannelError,
         'rpc requests %s \(travis-ci\) took too long'
         % uuid,
         rpc.get_request, uuid=uuid, raw=False
     )
예제 #5
0
 def __init__(self, channel_id, connection, rpc_timeout):
     super(Channel, self).__init__(channel_id)
     self.consumer_callback = None
     self.rpc = Rpc(self, timeout=rpc_timeout)
     self._confirming_deliveries = False
     self._connection = connection
     self._inbound = []
     self._basic = Basic(self)
     self._exchange = Exchange(self)
     self._tx = Tx(self)
     self._queue = Queue(self)
예제 #6
0
 def __init__(self, channel_id, connection, rpc_timeout,
              on_close_impl=None):
     super(Channel, self).__init__(channel_id)
     self.rpc = Rpc(self, timeout=rpc_timeout)
     self._consumer_callbacks = {}
     self._confirming_deliveries = False
     self._connection = connection
     self._on_close_impl = on_close_impl
     self._inbound = []
     self._basic = Basic(self, connection.max_frame_size)
     self._exchange = Exchange(self)
     self._tx = Tx(self)
     self._queue = Queue(self)
예제 #7
0
    def test_wait_for_request(self):
        rpc = Rpc(FakeConnection(), timeout=1)
        uuid = rpc.register_request(['travis-ci'])

        def delivery_payload():
            time.sleep(0.1)
            rpc.on_frame(FakePayload(name='travis-ci'))

        thread = threading.Thread(target=delivery_payload)
        thread.start()

        rpc._wait_for_request(
            uuid, connection_adapter=rpc._default_connection_adapter)
예제 #8
0
    def test_wait_for_request(self):
        rpc = Rpc(FakeConnection(), timeout=1)
        uuid = rpc.register_request(['travis-ci'])

        def delivery_payload():
            time.sleep(0.1)
            rpc.on_frame(FakePayload(name='travis-ci'))

        thread = threading.Thread(target=delivery_payload)
        thread.start()

        rpc._wait_for_request(
            uuid, connection_adapter=rpc._default_connection_adapter
        )
예제 #9
0
    def test_with_for_request_with_custom_adapter(self):
        class Adapter(object):
            def check_for_errors(self):
                pass

        rpc = Rpc(FakeConnection(), timeout=1)
        uuid = rpc.register_request(['travis-ci'])

        def delivery_payload():
            time.sleep(0.1)
            rpc.on_frame(FakePayload(name='travis-ci'))

        thread = threading.Thread(target=delivery_payload)
        thread.start()

        rpc._wait_for_request(uuid, Adapter())
예제 #10
0
    def test_with_for_request_with_custom_adapter(self):
        class Adapter(object):
            def check_for_errors(self):
                pass

        rpc = Rpc(FakeConnection(), timeout=1)
        uuid = rpc.register_request(['travis-ci'])

        def delivery_payload():
            time.sleep(0.1)
            rpc.on_frame(FakePayload(name='travis-ci'))

        thread = threading.Thread(target=delivery_payload)
        thread.start()

        rpc._wait_for_request(uuid, Adapter())
예제 #11
0
    def test_rpc_get_request_multiple_2(self):
        rpc = Rpc(FakeConnection())
        uuid = rpc.register_request(['Test'])
        for index in range(1000):
            rpc.on_frame(FakePayload(name='Test', value=index))
            result = rpc.get_request(uuid=uuid, raw=True, multiple=True)
            self.assertEqual(result.value, index)

        rpc.remove(uuid)
예제 #12
0
    def test_with_for_request_with_custom_adapter_and_error(self):
        class Adapter(object):
            def check_for_errors(self):
                raise AMQPChannelError('travis-ci')

        rpc = Rpc(FakeConnection(), timeout=1)
        uuid = rpc.register_request(['travis-ci'])

        def delivery_payload():
            time.sleep(0.1)
            rpc.on_frame(FakePayload(name='travis-ci'))

        thread = threading.Thread(target=delivery_payload)
        thread.start()

        adapter = Adapter()
        self.assertRaises(AMQPChannelError, rpc._wait_for_request, uuid,
                          adapter)
예제 #13
0
    def test_rpc_get_request_multiple_2(self):
        rpc = Rpc(FakeConnection())
        uuid = rpc.register_request(['travis-ci'])
        for index in range(1000):
            rpc.on_frame(FakePayload(name='travis-ci', value=index))
            result = rpc.get_request(uuid=uuid, raw=True, multiple=True)
            self.assertEqual(result.value, index)

        rpc.remove(uuid)
예제 #14
0
    def test_with_for_request_with_custom_adapter_and_error(self):
        class Adapter(object):
            def check_for_errors(self):
                raise AMQPChannelError('travis-ci')

        rpc = Rpc(FakeConnection(), timeout=1)
        uuid = rpc.register_request(['travis-ci'])

        def delivery_payload():
            time.sleep(0.1)
            rpc.on_frame(FakePayload(name='travis-ci'))

        thread = threading.Thread(target=delivery_payload)
        thread.start()

        adapter = Adapter()
        self.assertRaises(
            AMQPChannelError,
            rpc._wait_for_request, uuid, adapter
        )
예제 #15
0
 def test_rpc_on_multiple_frames(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['Test'])
     self.assertEqual(rpc._response[uuid], [])
     rpc.on_frame(FakePayload(name='Test'))
     rpc.on_frame(FakePayload(name='Test'))
     rpc.on_frame(FakePayload(name='Test'))
     self.assertIsInstance(rpc._response[uuid][0], FakePayload)
     self.assertIsInstance(rpc._response[uuid][1], FakePayload)
     self.assertIsInstance(rpc._response[uuid][2], FakePayload)
예제 #16
0
 def test_rpc_remove_multiple(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['Test'])
     for index in range(1000):
         rpc.on_frame(FakePayload(name='Test', value=index))
     self.assertEqual(len(rpc._request), 1)
     self.assertEqual(len(rpc._response[uuid]), 1000)
     rpc.remove(uuid)
     self.assertEqual(len(rpc._request), 0)
     self.assertEqual(len(rpc._response), 0)
예제 #17
0
 def test_rpc_remove(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['Test'])
     self.assertEqual(len(rpc._request), 1)
     self.assertEqual(len(rpc._response), 1)
     self.assertEqual(len(rpc._response[uuid]), 0)
     rpc.on_frame(FakePayload(name='Test'))
     rpc.remove(uuid)
     self.assertEqual(len(rpc._request), 0)
     self.assertEqual(len(rpc._response), 0)
예제 #18
0
    def __init__(self, channel_id, connection, rpc_timeout):
        super(Channel, self).__init__(channel_id)
        self.consumer_callback = None
        self.rpc = Rpc(self, timeout=rpc_timeout)
        self._confirming_deliveries = False
        self._connection = connection
        self._inbound = []
        self._basic = Basic(self)
        self._exchange = Exchange(self)
        self._tx = Tx(self)
        self._queue = Queue(self)

        self._die = multiprocessing.Value("b", 0)
예제 #19
0
 def test_rpc_raises_on_timeout(self):
     rpc = Rpc(FakeConnection(), timeout=0.01)
     rpc.register_request(['travis-ci-1'])
     uuid = rpc.register_request(['travis-ci-2'])
     rpc.register_request(['travis-ci-3'])
     self.assertEqual(rpc._response[uuid], [])
     time.sleep(0.1)
     self.assertRaisesRegexp(
         AMQPChannelError,
         'rpc requests %s \(travis-ci-2\) took too long' % uuid,
         rpc.get_request, uuid)
예제 #20
0
 def test_rpc_remove(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     self.assertEqual(len(rpc._request), 1)
     self.assertEqual(len(rpc._response), 1)
     self.assertEqual(len(rpc._response[uuid]), 0)
     rpc.on_frame(FakePayload(name='travis-ci'))
     rpc.remove(uuid)
     self.assertEqual(len(rpc._request), 0)
     self.assertEqual(len(rpc._response), 0)
예제 #21
0
 def test_rpc_remove_multiple(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     for index in range(1000):
         rpc.on_frame(FakePayload(name='travis-ci', value=index))
     self.assertEqual(len(rpc._request), 1)
     self.assertEqual(len(rpc._response[uuid]), 1000)
     rpc.remove(uuid)
     self.assertEqual(len(rpc._request), 0)
     self.assertEqual(len(rpc._response), 0)
예제 #22
0
 def test_rpc_on_multiple_frames(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     self.assertEqual(rpc._response[uuid], [])
     rpc.on_frame(FakePayload(name='travis-ci'))
     rpc.on_frame(FakePayload(name='travis-ci'))
     rpc.on_frame(FakePayload(name='travis-ci'))
     self.assertIsInstance(rpc._response[uuid][0], FakePayload)
     self.assertIsInstance(rpc._response[uuid][1], FakePayload)
     self.assertIsInstance(rpc._response[uuid][2], FakePayload)
예제 #23
0
 def test_rpc_raises_on_timeout(self):
     rpc = Rpc(FakeConnection(), timeout=0.01)
     rpc.register_request(['travis-ci-1'])
     uuid = rpc.register_request(['travis-ci-2'])
     rpc.register_request(['travis-ci-3'])
     self.assertEqual(rpc._response[uuid], [])
     time.sleep(0.1)
     self.assertRaisesRegexp(
         AMQPChannelError,
         'rpc requests %s \(travis-ci-2\) took too long' % uuid,
         rpc.get_request, uuid
     )
예제 #24
0
 def test_rpc_get_request_not_found(self):
     rpc = Rpc(FakeConnection())
     self.assertIsNone(rpc.get_request(None))
예제 #25
0
 def test_rpc_get_request(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['Test'])
     self.assertTrue(rpc.on_frame(FakePayload(name='Test')))
     self.assertIsInstance(rpc.get_request(uuid=uuid, raw=True),
                           FakePayload)
예제 #26
0
 def test_rpc_get_response_frame_empty(self):
     rpc = Rpc(FakeConnection())
     rpc._response['travis-ci'] = []
     self.assertIsNone(rpc._get_response_frame('travis-ci'))
예제 #27
0
 def test_rpc_on_frame(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     self.assertEqual(rpc._response[uuid], [])
     rpc.on_frame(FakePayload(name='travis-ci'))
     self.assertIsInstance(rpc._response[uuid][0], FakePayload)
예제 #28
0
 def test_rpc_get_request_not_found(self):
     rpc = Rpc(FakeConnection())
     self.assertIsNone(rpc.get_request(None))
예제 #29
0
 def test_rpc_register_request(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     self.assertEqual(len(rpc._request), 1)
     for key in rpc._request:
         self.assertEqual(uuid, rpc._request[key])
예제 #30
0
 def test_rpc_raises_on_timeout(self):
     rpc = Rpc(FakeConnection(), timeout=0.1)
     uuid = rpc.register_request(['Test'])
     self.assertEqual(rpc._response[uuid], [])
     time.sleep(0.25)
     self.assertRaises(AMQPChannelError, rpc.get_request, uuid)
예제 #31
0
 def test_rpc_register_request(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['Test'])
     self.assertEqual(len(rpc._request), 1)
     for key in rpc._request:
         self.assertEqual(uuid, rpc._request[key])
예제 #32
0
 def test_rpc_get_request(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     self.assertTrue(rpc.on_frame(FakePayload(name='travis-ci')))
     self.assertIsInstance(rpc.get_request(uuid=uuid, raw=True),
                           FakePayload)
예제 #33
0
 def test_rpc_get_response_frame(self):
     rpc = Rpc(FakeConnection())
     rpc._response['travis-ci'] = ['travis-ci']
     self.assertEqual(rpc._get_response_frame('travis-ci'), 'travis-ci')
예제 #34
0
 def test_rpc_get_request_no_reply_raw(self, _):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     self.assertIsNone(rpc.get_request(uuid=uuid, raw=True),
                       FakePayload)
예제 #35
0
 def test_rpc_get_request_no_reply_raw(self, _):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     self.assertIsNone(rpc.get_request(uuid=uuid, raw=True), FakePayload)
예제 #36
0
 def test_rpc_get_response_frame_empty(self):
     rpc = Rpc(FakeConnection())
     rpc._response['travis-ci'] = []
     self.assertIsNone(rpc._get_response_frame('travis-ci'))
예제 #37
0
 def test_rpc_remove_response_none(self):
     rpc = Rpc(FakeConnection())
     self.assertIsNone(rpc.remove_response(None))
예제 #38
0
 def test_rpc_remove_response_none(self):
     rpc = Rpc(FakeConnection())
     self.assertIsNone(rpc.remove_response(None))
예제 #39
0
class Channel(BaseChannel):
    """RabbitMQ Channel."""
    __slots__ = [
        '_consumer_callbacks', 'rpc', '_basic', '_confirming_deliveries',
        '_connection', '_exchange', '_inbound', '_queue', '_tx'
    ]

    def __init__(self, channel_id, connection, rpc_timeout,
                 on_close_impl=None):
        super(Channel, self).__init__(channel_id)
        self.rpc = Rpc(self, timeout=rpc_timeout)
        self._consumer_callbacks = {}
        self._confirming_deliveries = False
        self._connection = connection
        self._on_close_impl = on_close_impl
        self._inbound = []
        self._basic = Basic(self, connection.max_frame_size)
        self._exchange = Exchange(self)
        self._tx = Tx(self)
        self._queue = Queue(self)

    def __enter__(self):
        return self

    def __exit__(self, exception_type, exception_value, _):
        if exception_type:
            LOGGER.warning(
                'Closing channel due to an unhandled exception: %s',
                exception_value
            )
        if not self.is_open:
            return
        self.close()

    def __int__(self):
        return self._channel_id

    @property
    def basic(self):
        """RabbitMQ Basic Operations.

        :rtype: amqpstorm.basic.Basic
        """
        return self._basic

    @property
    def exchange(self):
        """RabbitMQ Exchange Operations.

        :rtype: amqpstorm.exchange.Exchange
        """
        return self._exchange

    @property
    def tx(self):
        """RabbitMQ Tx Operations.

        :rtype: amqpstorm.tx.Tx
        """
        return self._tx

    @property
    def queue(self):
        """RabbitMQ Queue Operations.

        :rtype: amqpstorm.queue.Queue
        """
        return self._queue

    def build_inbound_messages(self, break_on_empty=False, to_tuple=False,
                               auto_decode=True):
        """Build messages in the inbound queue.

        :param bool break_on_empty: Should we break the loop when there are
                                    no more messages in our inbound queue.

                                    This does not guarantee that the upstream
                                    queue is empty, as it's possible that if
                                    messages are consumed faster than
                                    delivered, the inbound queue will then be
                                    emptied and the consumption will be broken.
        :param bool to_tuple: Should incoming messages be converted to a
                              tuple before delivery.
        :param bool auto_decode: Auto-decode strings when possible.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :rtype: :py:class:`generator`
        """
        self.check_for_errors()
        while not self.is_closed:
            message = self._build_message(auto_decode=auto_decode)
            if not message:
                self.check_for_errors()
                sleep(IDLE_WAIT)
                if break_on_empty and not self._inbound:
                    break
                continue
            if to_tuple:
                yield message.to_tuple()
                continue
            yield message

    def close(self, reply_code=200, reply_text=''):
        """Close Channel.

        :param int reply_code: Close reply code (e.g. 200)
        :param str reply_text: Close reply text

        :raises AMQPInvalidArgument: Invalid Parameters
        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        if not compatibility.is_integer(reply_code):
            raise AMQPInvalidArgument('reply_code should be an integer')
        elif not compatibility.is_string(reply_text):
            raise AMQPInvalidArgument('reply_text should be a string')
        try:
            if self._connection.is_closed or self.is_closed:
                self.stop_consuming()
                LOGGER.debug('Channel #%d forcefully Closed', self.channel_id)
                return
            self.set_state(self.CLOSING)
            LOGGER.debug('Channel #%d Closing', self.channel_id)
            try:
                self.stop_consuming()
            except AMQPChannelError:
                self.remove_consumer_tag()
            self.rpc_request(specification.Channel.Close(
                reply_code=reply_code,
                reply_text=reply_text),
                connection_adapter=self._connection
            )
        finally:
            if self._inbound:
                del self._inbound[:]
            self.set_state(self.CLOSED)
            if self._on_close_impl:
                self._on_close_impl(self.channel_id)
        LOGGER.debug('Channel #%d Closed', self.channel_id)

    def check_for_errors(self):
        """Check connection and channel for errors.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.
        :return:
        """
        try:
            self._connection.check_for_errors()
        except AMQPConnectionError:
            self.set_state(self.CLOSED)
            raise

        if self.exceptions:
            exception = self.exceptions[0]
            if self.is_open:
                self.exceptions.pop(0)
            raise exception

        if self.is_closed:
            raise AMQPChannelError('channel was closed')

    def confirm_deliveries(self):
        """Set the channel to confirm that each message has been
        successfully delivered.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        self._confirming_deliveries = True
        confirm_frame = specification.Confirm.Select()
        return self.rpc_request(confirm_frame)

    @property
    def confirming_deliveries(self):
        """Is the channel set to confirm deliveries.

        :return:
        """
        return self._confirming_deliveries

    def on_frame(self, frame_in):
        """Handle frame sent to this specific channel.

        :param pamqp.Frame frame_in: Amqp frame.
        :return:
        """
        if self.rpc.on_frame(frame_in):
            return

        if frame_in.name in CONTENT_FRAME:
            self._inbound.append(frame_in)
        elif frame_in.name == 'Basic.Cancel':
            self._basic_cancel(frame_in)
        elif frame_in.name == 'Basic.CancelOk':
            self.remove_consumer_tag(frame_in.consumer_tag)
        elif frame_in.name == 'Basic.ConsumeOk':
            self.add_consumer_tag(frame_in['consumer_tag'])
        elif frame_in.name == 'Basic.Return':
            self._basic_return(frame_in)
        elif frame_in.name == 'Channel.Close':
            self._close_channel(frame_in)
        elif frame_in.name == 'Channel.Flow':
            self.write_frame(specification.Channel.FlowOk(frame_in.active))
        else:
            LOGGER.error(
                '[Channel%d] Unhandled Frame: %s -- %s',
                self.channel_id, frame_in.name, dict(frame_in)
            )

    def open(self):
        """Open Channel.

        :return:
        """
        self._inbound = []
        self._exceptions = []
        self.set_state(self.OPENING)
        self.rpc_request(specification.Channel.Open())
        self.set_state(self.OPEN)

    def process_data_events(self, to_tuple=False, auto_decode=True):
        """Consume inbound messages.

        :param bool to_tuple: Should incoming messages be converted to a
                              tuple before delivery.
        :param bool auto_decode: Auto-decode strings when possible.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        if not self._consumer_callbacks:
            raise AMQPChannelError('no consumer callback defined')
        for message in self.build_inbound_messages(break_on_empty=True,
                                                   auto_decode=auto_decode):
            consumer_tag = message._method.get('consumer_tag')
            if to_tuple:
                # noinspection PyCallingNonCallable
                self._consumer_callbacks[consumer_tag](*message.to_tuple())
                continue
            # noinspection PyCallingNonCallable
            self._consumer_callbacks[consumer_tag](message)

    def rpc_request(self, frame_out, connection_adapter=None):
        """Perform a RPC Request.

        :param specification.Frame frame_out: Amqp frame.
        :rtype: dict
        """
        with self.rpc.lock:
            uuid = self.rpc.register_request(frame_out.valid_responses)
            self._connection.write_frame(self.channel_id, frame_out)
            return self.rpc.get_request(
                uuid, connection_adapter=connection_adapter
            )

    def start_consuming(self, to_tuple=False, auto_decode=True):
        """Start consuming messages.

        :param bool to_tuple: Should incoming messages be converted to a
                              tuple before delivery.
        :param bool auto_decode: Auto-decode strings when possible.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        while not self.is_closed:
            self.process_data_events(
                to_tuple=to_tuple,
                auto_decode=auto_decode
            )
            if self.consumer_tags:
                sleep(IDLE_WAIT)
                continue
            break

    def stop_consuming(self):
        """Stop consuming messages.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        if not self.consumer_tags:
            return
        if not self.is_closed:
            for tag in self.consumer_tags:
                self.basic.cancel(tag)
        self.remove_consumer_tag()

    def write_frame(self, frame_out):
        """Write a pamqp frame from the current channel.

        :param specification.Frame frame_out: A single pamqp frame.

        :return:
        """
        self.check_for_errors()
        self._connection.write_frame(self.channel_id, frame_out)

    def write_frames(self, frames_out):
        """Write multiple pamqp frames from the current channel.

        :param list frames_out: A list of pamqp frames.

        :return:
        """
        self.check_for_errors()
        self._connection.write_frames(self.channel_id, frames_out)

    def _basic_cancel(self, frame_in):
        """Handle a Basic Cancel frame.

        :param specification.Basic.Cancel frame_in: Amqp frame.

        :return:
        """
        LOGGER.warning(
            'Received Basic.Cancel on consumer_tag: %s',
            try_utf8_decode(frame_in.consumer_tag)
        )
        self.remove_consumer_tag(frame_in.consumer_tag)

    def _basic_return(self, frame_in):
        """Handle a Basic Return Frame and treat it as an error.

        :param specification.Basic.Return frame_in: Amqp frame.

        :return:
        """
        reply_text = try_utf8_decode(frame_in.reply_text)
        message = (
            "Message not delivered: %s (%s) to queue '%s' from exchange '%s'" %
            (
                reply_text,
                frame_in.reply_code,
                frame_in.routing_key,
                frame_in.exchange
            )
        )
        exception = AMQPMessageError(message,
                                     reply_code=frame_in.reply_code)
        self.exceptions.append(exception)

    def _build_message(self, auto_decode):
        """Fetch and build a complete Message from the inbound queue.

        :param bool auto_decode: Auto-decode strings when possible.

        :rtype: Message
        """
        with self.lock:
            if len(self._inbound) < 2:
                return None
            headers = self._build_message_headers()
            if not headers:
                return None
            basic_deliver, content_header = headers
            body = self._build_message_body(content_header.body_size)

        message = Message(channel=self,
                          body=body,
                          method=dict(basic_deliver),
                          properties=dict(content_header.properties),
                          auto_decode=auto_decode)
        return message

    def _build_message_headers(self):
        """Fetch Message Headers (Deliver & Header Frames).

        :rtype: tuple|None
        """
        basic_deliver = self._inbound.pop(0)
        if not isinstance(basic_deliver, specification.Basic.Deliver):
            LOGGER.warning(
                'Received an out-of-order frame: %s was '
                'expecting a Basic.Deliver frame',
                type(basic_deliver)
            )
            return None
        content_header = self._inbound.pop(0)
        if not isinstance(content_header, ContentHeader):
            LOGGER.warning(
                'Received an out-of-order frame: %s was '
                'expecting a ContentHeader frame',
                type(content_header)
            )
            return None

        return basic_deliver, content_header

    def _build_message_body(self, body_size):
        """Build the Message body from the inbound queue.

        :rtype: str
        """
        body = bytes()
        while len(body) < body_size:
            if not self._inbound:
                self.check_for_errors()
                sleep(IDLE_WAIT)
                continue
            body_piece = self._inbound.pop(0)
            if not body_piece.value:
                break
            body += body_piece.value
        return body

    def _close_channel(self, frame_in):
        """Close Channel.

        :param specification.Channel.Close frame_in: Channel Close frame.
        :return:
        """
        if frame_in.reply_code != 200:
            reply_text = try_utf8_decode(frame_in.reply_text)
            message = (
                'Channel %d was closed by remote server: %s' %
                (
                    self._channel_id,
                    reply_text
                )
            )
            exception = AMQPChannelError(message,
                                         reply_code=frame_in.reply_code)
            self.exceptions.append(exception)
        self.set_state(self.CLOSED)
        if self._connection.is_open:

            try:
                self._connection.write_frame(
                    self.channel_id, specification.Channel.CloseOk()
                )
            except AMQPConnectionError:
                pass
        self.close()
예제 #40
0
 def test_rpc_get_response_frame(self):
     rpc = Rpc(FakeConnection())
     rpc._response['travis-ci'] = ['travis-ci']
     self.assertEqual(rpc._get_response_frame('travis-ci'), 'travis-ci')
예제 #41
0
class Channel(BaseChannel):
    """RabbitMQ Channel."""
    __slots__ = [
        'consumer_callback', 'rpc', '_basic', '_confirming_deliveries',
        '_connection', '_exchange', '_inbound', '_queue', '_tx', '_die'
    ]

    def __init__(self, channel_id, connection, rpc_timeout):
        super(Channel, self).__init__(channel_id)
        self.consumer_callback = None
        self.rpc = Rpc(self, timeout=rpc_timeout)
        self._confirming_deliveries = False
        self._connection = connection
        self._inbound = []
        self._basic = Basic(self)
        self._exchange = Exchange(self)
        self._tx = Tx(self)
        self._queue = Queue(self)

        self._die = multiprocessing.Value("b", 0)

    def __enter__(self):
        return self

    def __exit__(self, exception_type, exception_value, _):
        if exception_type:
            LOGGER.warning('Closing channel due to an unhandled exception: %s',
                           exception_value)
        if not self.is_open:
            return
        self.close()

    def __int__(self):
        return self._channel_id

    @property
    def basic(self):
        """RabbitMQ Basic Operations.

        :rtype: amqpstorm.basic.Basic
        """
        return self._basic

    @property
    def exchange(self):
        """RabbitMQ Exchange Operations.

        :rtype: amqpstorm.exchange.Exchange
        """
        return self._exchange

    @property
    def tx(self):
        """RabbitMQ Tx Operations.

        :rtype: amqpstorm.tx.Tx
        """
        return self._tx

    @property
    def queue(self):
        """RabbitMQ Queue Operations.

        :rtype: amqpstorm.queue.Queue
        """
        return self._queue

    def build_inbound_messages(self,
                               break_on_empty=False,
                               to_tuple=False,
                               auto_decode=True):
        """Build messages in the inbound queue.

        :param bool break_on_empty: Should we break the loop when there are
                                    no more messages to consume.
        :param bool to_tuple: Should incoming messages be converted to a
                              tuple before delivery.
        :param bool auto_decode: Auto-decode strings when possible.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :rtype: :py:class:`generator`
        """
        self.check_for_errors()
        while not self.is_closed:
            if self._die.value != 0:
                return
            if self.is_closed:
                return

            message = self._build_message(auto_decode=auto_decode)
            if not message:
                if break_on_empty:
                    break
                self.check_for_errors()
                sleep(IDLE_WAIT)
                continue
            if to_tuple:
                yield message.to_tuple()
                continue
            yield message

    def kill(self):
        self._die.value = 1
        self.set_state(self.CLOSED)

    def close(self, reply_code=200, reply_text=''):
        """Close Channel.

        :param int reply_code: Close reply code (e.g. 200)
        :param str reply_text: Close reply text

        :raises AMQPInvalidArgument: Invalid Parameters
        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        if not compatibility.is_integer(reply_code):
            raise AMQPInvalidArgument('reply_code should be an integer')
        elif not compatibility.is_string(reply_text):
            raise AMQPInvalidArgument('reply_text should be a string')
        try:
            if self._connection.is_closed or self.is_closed:
                self.stop_consuming()
                LOGGER.debug('Channel #%d forcefully Closed', self.channel_id)
                return
            self.set_state(self.CLOSING)
            LOGGER.debug('Channel #%d Closing', self.channel_id)
            self.stop_consuming()
            self.rpc_request(
                specification.Channel.Close(reply_code=reply_code,
                                            reply_text=reply_text))
        finally:
            if self._inbound:
                del self._inbound[:]
            self.set_state(self.CLOSED)
        LOGGER.debug('Channel #%d Closed', self.channel_id)

    def check_for_errors(self):
        """Check connection and channel for errors.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.
        :return:
        """
        try:
            self._connection.check_for_errors()
        except AMQPConnectionError:
            self.set_state(self.CLOSED)
            raise

        if self.exceptions:
            exception = self.exceptions[0]
            if self.is_open:
                self.exceptions.pop(0)
            raise exception

        if self.is_closed:
            raise AMQPChannelError('channel was closed')

    def confirm_deliveries(self):
        """Set the channel to confirm that each message has been
        successfully delivered.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        self._confirming_deliveries = True
        confirm_frame = specification.Confirm.Select()
        return self.rpc_request(confirm_frame)

    @property
    def confirming_deliveries(self):
        """Is the channel set to confirm deliveries.

        :return:
        """
        return self._confirming_deliveries

    def on_frame(self, frame_in):
        """Handle frame sent to this specific channel.

        :param pamqp.Frame frame_in: Amqp frame.
        :return:
        """
        if self.rpc.on_frame(frame_in):
            return

        if frame_in.name in CONTENT_FRAME:
            self._inbound.append(frame_in)
        elif frame_in.name == 'Basic.Cancel':
            self._basic_cancel(frame_in)
        elif frame_in.name == 'Basic.CancelOk':
            self.remove_consumer_tag(frame_in.consumer_tag)
        elif frame_in.name == 'Basic.ConsumeOk':
            self.add_consumer_tag(frame_in['consumer_tag'])
        elif frame_in.name == 'Basic.Return':
            self._basic_return(frame_in)
        elif frame_in.name == 'Channel.Close':
            self._close_channel(frame_in)
        elif frame_in.name == 'Channel.Flow':
            self.write_frame(specification.Channel.FlowOk(frame_in.active))
        else:
            LOGGER.error('[Channel%d] Unhandled Frame: %s -- %s',
                         self.channel_id, frame_in.name, dict(frame_in))

    def open(self):
        """Open Channel.

        :return:
        """
        self._inbound = []
        self._exceptions = []
        self.set_state(self.OPENING)
        self.rpc_request(specification.Channel.Open())
        self.set_state(self.OPEN)

    def process_data_events(self, to_tuple=False, auto_decode=True):
        """Consume inbound messages.

        :param bool to_tuple: Should incoming messages be converted to a
                              tuple before delivery.
        :param bool auto_decode: Auto-decode strings when possible.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        if not self.consumer_callback:
            raise AMQPChannelError('no consumer_callback defined')
        for message in self.build_inbound_messages(break_on_empty=True,
                                                   to_tuple=to_tuple,
                                                   auto_decode=auto_decode):
            if self._die.value != 0:
                return

            if to_tuple:
                # noinspection PyCallingNonCallable
                self.consumer_callback(*message)
                continue
            # noinspection PyCallingNonCallable
            self.consumer_callback(message)
        sleep(IDLE_WAIT)

    def rpc_request(self, frame_out):
        """Perform a RPC Request.

        :param specification.Frame frame_out: Amqp frame.
        :rtype: dict
        """
        with self.rpc.lock:
            uuid = self.rpc.register_request(frame_out.valid_responses)
            self.write_frame(frame_out)
            return self.rpc.get_request(uuid)

    def start_consuming(self, to_tuple=False, auto_decode=True):
        """Start consuming messages.

        :param bool to_tuple: Should incoming messages be converted to a
                              tuple before delivery.
        :param bool auto_decode: Auto-decode strings when possible.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        while not self.is_closed:
            self.process_data_events(to_tuple=to_tuple,
                                     auto_decode=auto_decode)
            if not self.consumer_tags:
                break
            if self._die.value != 0:
                break

    def stop_consuming(self):
        """Stop consuming messages.

        :raises AMQPChannelError: Raises if the channel encountered an error.
        :raises AMQPConnectionError: Raises if the connection
                                     encountered an error.

        :return:
        """
        if not self.consumer_tags:
            return
        if not self.is_closed:
            for tag in self.consumer_tags:
                self.basic.cancel(tag)
        self.remove_consumer_tag()

    def write_frame(self, frame_out):
        """Write a pamqp frame from the current channel.

        :param specification.Frame frame_out: A single pamqp frame.
        :return:
        """
        self.check_for_errors()
        self._connection.write_frame(self.channel_id, frame_out)

    def write_frames(self, frames_out):
        """Write multiple pamqp frames from the current channel.

        :param list frames_out: A list of pamqp frames.
        :return:
        """
        self.check_for_errors()
        self._connection.write_frames(self.channel_id, frames_out)

    def _basic_cancel(self, frame_in):
        """Handle a Basic Cancel frame.

        :param specification.Basic.Cancel frame_in: Amqp frame.
        :return:
        """
        LOGGER.warning('Received Basic.Cancel on consumer_tag: %s',
                       try_utf8_decode(frame_in.consumer_tag))
        self.remove_consumer_tag(frame_in.consumer_tag)

    def _basic_return(self, frame_in):
        """Handle a Basic Return Frame and treat it as an error.

        :param specification.Basic.Return frame_in: Amqp frame.
        :return:
        """
        reply_text = try_utf8_decode(frame_in.reply_text)
        message = (
            "Message not delivered: %s (%s) to queue '%s' from exchange '%s'" %
            (reply_text, frame_in.reply_code, frame_in.routing_key,
             frame_in.exchange))
        exception = AMQPMessageError(message, reply_code=frame_in.reply_code)
        self.exceptions.append(exception)

    def _build_message(self, auto_decode):
        """Fetch and build a complete Message from the inbound queue.

        :param bool auto_decode: Auto-decode strings when possible.

        :rtype: Message
        """
        with self.lock:
            if len(self._inbound) < 2:
                return None
            headers = self._build_message_headers()
            if not headers:
                return None
            basic_deliver, content_header = headers
            body = self._build_message_body(content_header.body_size)

        message = Message(channel=self,
                          body=body,
                          method=dict(basic_deliver),
                          properties=dict(content_header.properties),
                          auto_decode=auto_decode)
        return message

    def _build_message_headers(self):
        """Fetch Message Headers (Deliver & Header Frames).

        :rtype: tuple|None
        """
        basic_deliver = self._inbound.pop(0)
        if not isinstance(basic_deliver, specification.Basic.Deliver):
            LOGGER.warning(
                'Received an out-of-order frame: %s was '
                'expecting a Basic.Deliver frame', type(basic_deliver))
            return None
        content_header = self._inbound.pop(0)
        if not isinstance(content_header, ContentHeader):
            LOGGER.warning(
                'Received an out-of-order frame: %s was '
                'expecting a ContentHeader frame', type(content_header))
            return None

        return basic_deliver, content_header

    def _build_message_body(self, body_size):
        """Build the Message body from the inbound queue.

        :rtype: str
        """
        body = bytes()
        while len(body) < body_size:
            if not self._inbound:
                self.check_for_errors()
                sleep(IDLE_WAIT)
                continue
            body_piece = self._inbound.pop(0)
            if not body_piece.value:
                break
            body += body_piece.value
        return body

    def _close_channel(self, frame_in):
        """Close Channel.

        :param specification.Channel.Close frame_in: Amqp frame.
        :return:
        """
        self.set_state(self.CLOSED)
        if frame_in.reply_code != 200:
            reply_text = try_utf8_decode(frame_in.reply_text)
            message = ('Channel %d was closed by remote server: %s' %
                       (self._channel_id, reply_text))
            exception = AMQPChannelError(message,
                                         reply_code=frame_in.reply_code)
            self.exceptions.append(exception)
        self.close()
예제 #42
0
 def test_rpc_remove_request(self):
     rpc = Rpc(FakeConnection())
     uuid = rpc.register_request(['travis-ci'])
     self.assertEqual(len(rpc._request), 1)
     rpc.remove_request(uuid)
     self.assertEqual(len(rpc._request), 0)