def _requeue_message(self, message: BrightsideMessage) -> None: message.increment_handled_count() if self._discard_requeued_messages_enabled(): if message.handled_count_reached(self._requeue_count): self._logger.error("MessagePump: Have tried {} times to handle this message {} dropping message \n. Message Body {} ".format( self._requeue_count, message.id, message.body.value)) self._channel.acknowledge(message) return self._logger.debug("MessagePump: Re-queueing message {} from {}".format( message.id, self._channel.name)) self._channel.requeue(message)
def test_the_pump_should_fail_on_a_missing_message_mapper(self): """ Given that I have a message pump for a channel When there is no message mapper for that channel Then we shhould throw an exception to indicate a configuration error """ handler = MyCommandHandler() request = MyCommand() channel = Mock(spec=Channel) command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, None) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) quit_message = create_quit_message() # add messages to that when channel is called it returns first message then qui tmessage response_queue = [message, quit_message] channel_spec = {"receive.side_effect": response_queue} channel.configure_mock(**channel_spec) excepton_caught = False try: message_pump.run() except ConfigurationException: excepton_caught = True self.assertTrue(excepton_caught)
def test_requeueing_a_message(self): """Given that I have an RMQ consumer when I requeue a message then it should return to the end of the queue """ request = TestMessage() header = BrightsideMessageHeader(uuid4(), self.test_topic, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) self._consumer.purge( ) # if errors with akes 1 positional argument but 2 were given then manually purge self._producer.send(message) read_message = self._consumer.receive(3) self._consumer.requeue(read_message) # should now be able to receive it again reread_message = self._consumer.receive(3) self._consumer.acknowledge(reread_message) self.assertEqual(message.id, reread_message.id) self.assertEqual(message.body.value, reread_message.body.value) self.assertTrue(self._consumer.has_acknowledged(reread_message))
def test_the_pump_should_dispatch_a_command_processor(self): """ Given that I have a message pump for a channel When I read a message from that channel Then the message should be dispatched to a handler """ handler = MyCommandHandler() request = MyCommand() channel = Mock(spec=Channel) command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, map_my_command_to_request) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) quit_message = create_quit_message() # add messages to that when channel is called it returns first message then quit message response_queue = [message, quit_message] channel_spec = {"receive.side_effect": response_queue} channel.configure_mock(**channel_spec) message_pump.run() channel.receive.assert_called_with(0.5) self.assertEqual(channel.receive.call_count, 2) self.assertTrue(command_processor.send.call_count, 1) self.assertEqual(channel.acknowledge.call_count, 1)
def test_the_pump_should_ack_failed_messages(self): """ Given that I have a message pump for a channel When the handler raises an application exception for that message Then ack the message to prevent 'poison pill' message """ handler = MyCommandHandler() request = MyCommand() channel = Mock(spec=Channel) command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, map_my_command_to_request) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) quit_message = create_quit_message() # add messages to that when channel is called it returns first message then qui tmessage response_queue = [message, quit_message] channel_spec = {"receive.side_effect": response_queue} channel.configure_mock(**channel_spec) app_error_spec = {"send.side_effect": ZeroDivisionError()} command_processor.configure_mock(**app_error_spec) message_pump.run() channel.receive.assert_called_with(0.5) self.assertEqual(channel.receive.call_count, 2) self.assertTrue(command_processor.send.call_count, 1) self.assertEqual(channel.acknowledge.call_count, 1)
def test_requeueing_a_message(self): """Given that I have an RMQ consumer when I requeue a message then it should return to the end of the queue """ test_topic = "kombu_gateway_tests" + str(uuid4()) request = TestMessage() header = BrightsideMessageHeader(uuid4(), test_topic, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) queue_name = "brightside_tests" + str(uuid4()) consumer = ArameConsumer( self._connection, BrightsideConsumerConfiguration(self._pipeline, queue_name, test_topic)) self._producer.send(message) read_message = consumer.receive(3) consumer.requeue(read_message) # should now be able to receive it again reread_message = consumer.receive(3) consumer.acknowledge(reread_message) self.assertEqual(message.id, reread_message.id) self.assertEqual(message.body.value, reread_message.body.value) self.assertTrue(consumer.has_acknowledged(reread_message))
def test_posting_a_message(self): """Given that I have an RMQ message producer when I send that message via the producer then I should be able to read that message via the consumer """ test_topic = "kombu_gateway_tests" + str(uuid4()) header = BrightsideMessageHeader(uuid4(), test_topic, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody("test content") message = BrightsideMessage(header, body) queue_name = "brightside_tests" + str(uuid4()) consumer = ArameConsumer( self._connection, BrightsideConsumerConfiguration(self._pipeline, queue_name, test_topic)) self._producer.send(message) read_message = consumer.receive(3) consumer.acknowledge(read_message) self.assertEqual(message.id, read_message.id) self.assertEqual(message.body.value, read_message.body.value) self.assertTrue(consumer.has_acknowledged(read_message))
def test_posting_object_state(self): """Given that I have an RMQ producer when I deserialize an object via the producer then I should be able to re-hydrate it via the consumer """ test_topic = "kombu_gateway_tests" + str(uuid4()) request = TestMessage() header = BrightsideMessageHeader(uuid4(), test_topic, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) queue_name = "brightside_tests" + str(uuid4()) consumer = ArameConsumer( self._connection, BrightsideConsumerConfiguration(self._pipeline, queue_name, test_topic)) self._producer.send(message) read_message = consumer.receive(3) consumer.acknowledge(read_message) deserialized_request = JsonRequestSerializer(request=TestMessage(), serialized_request=read_message.body.value)\ .deserialize_from_json() self.assertIsNotNone(deserialized_request) self.assertEqual(request.bool_value, deserialized_request.bool_value) self.assertEqual(request.float_value, deserialized_request.float_value) self.assertEqual(request.integer_value, deserialized_request.integer_value) self.assertEqual(request.id, deserialized_request.id)
def map_myevent_to_message(request: Request) -> BrightsideMessage: message_body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json()) message = BrightsideMessage( BrightsideMessageHeader(request.id, "my_event", BrightsideMessageType.MT_EVENT), message_body) return message
def test_posting_object_state(self): """Given that I have an RMQ producer when I deserialize an object via the producer then I should be able to re-hydrate it via the consumer """ request = TestMessage() header = BrightsideMessageHeader(uuid4(), self.test_topic, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) self._consumer.purge( ) # if errors with akes 1 positional argument but 2 were given then manually purge self._producer.send(message) read_message = self._consumer.receive(3) self._consumer.acknowledge(read_message) deserialized_request = JsonRequestSerializer(request=TestMessage(), serialized_request=read_message.body.value)\ .deserialize_from_json() self.assertIsNotNone(deserialized_request) self.assertEqual(request.bool_value, deserialized_request.bool_value) self.assertEqual(request.float_value, deserialized_request.float_value) self.assertEqual(request.integer_value, deserialized_request.integer_value) self.assertEqual(request.id, deserialized_request.id)
def receive(self, timeout: int) -> BrightsideMessage: self._message = BrightsideMessage(BrightsideMessageHeader(uuid4(), "", BrightsideMessageType.MT_NONE), BrightsideMessageBody("")) def _consume(cnx: BrokerConnection, timesup: int) -> None: try: cnx.drain_events(timeout=timesup) except kombu_exceptions.TimeoutError: self._logger.debug("Time out reading from queue %s", self._queue_name) cnx.heartbeat_check() except(kombu_exceptions.ChannelLimitExceeded, kombu_exceptions.ConnectionLimitExceeded, kombu_exceptions.OperationalError, kombu_exceptions.NotBoundError, kombu_exceptions.MessageStateError, kombu_exceptions.LimitExceeded) as err: raise ChannelFailureException("Error connecting to RabbitMQ, see inner exception for details", err) except (OSError, IOError, ConnectionError) as socket_err: self._reset_connection() raise ChannelFailureException("Error connecting to RabbitMQ, see inner exception for details", socket_err) def _consume_errors(exc, interval: int)-> None: self._logger.error('Draining error: %s, will retry triggering in %s seconds', exc, interval, exc_info=True) self._ensure_connection() ensure_kwargs = self.RETRY_OPTIONS.copy() ensure_kwargs['errback'] = _consume_errors safe_drain = self._conn.ensure(self._consumer, _consume, **ensure_kwargs) safe_drain(self._conn, timeout) return self._message
def test_the_pump_should_acknowledge_and_discard_an_unacceptable_message(self): """ Given that I have a message pump for a channel When I cannot read the message received from that channel Then I should acknowledge the message to dicard it """ handler = MyCommandHandler() request = MyCommand() channel = Mock(spec=Channel) command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, map_my_command_to_request) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_UNACCEPTABLE) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) quit_message = create_quit_message() # add messages to that when channel is called it returns first message then qui tmessage response_queue = [message, quit_message] channel_spec = {"receive.side_effect": response_queue} channel.configure_mock(**channel_spec) message_pump.run() channel.receive.assert_called_with(0.5) self.assertEqual(channel.receive.call_count, 2) # We acknowledge so that a 'poison pill' message cannot block our queue self.assertEqual(channel.acknowledge.call_count, 1) # Does not send the message, just discards it self.assertEqual(command_processor.send.call_count, 0)
def map_hellworldcommand_to_message( request: HelloWorldCommand) -> BrightsideMessage: message_body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json()) message = BrightsideMessage( BrightsideMessageHeader(request.id, "greeting.event", BrightsideMessageType.MT_EVENT), message_body) return message
def map_mycommand_to_message(request: Request) -> BrightsideMessage: message_body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json()) message = BrightsideMessage( BrightsideMessageHeader(request.id, "my_command", BrightsideMessageType.MT_COMMAND), message_body) return message
def create_message(row) -> BrightsideMessage: bag = deserialize_header_bag(row[messages.c.HeaderBag]) message = BrightsideMessage( BrightsideMessageHeader(identity=row[messages.c.MessageId], topic=row[messages.c.Topic], message_type=row[messages.c.MessageType], header_bag=bag), BrightsideMessageBody(row[messages.c.Body])) return message
def map_hellworldcommand_to_message( request: HelloWorldCommand) -> BrightsideMessage: message_body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json()) message = BrightsideMessage( BrightsideMessageHeader(identity=request.id, topic="hello_world", message_type=BrightsideMessageType.MT_COMMAND), message_body) return message
def map_longrunningcommand_to_message( request: LongRunningCommand) -> BrightsideMessage: message_body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json()) message = BrightsideMessage( BrightsideMessageHeader(identity=request.id, topic="long_running", message_type=BrightsideMessageType.MT_COMMAND), message_body) return message
def get_message(self, key: uuid) -> BrightsideMessage: for msg in self._messages: if msg.id == key: return msg return BrightsideMessage( BrightsideMessageHeader( identity=uuid4(), topic="", message_type=BrightsideMessageType.MT_NONE), BrightsideMessageBody(""))
def receive(self, timeout: int) -> BrightsideMessage: self._message = BrightsideMessage( BrightsideMessageHeader(uuid4(), "", BrightsideMessageType.MT_NONE), BrightsideMessageBody("")) def _consume(cnx: BrokerConnection, timesup: int) -> None: try: cnx.drain_events(timeout=timesup) except kombu_exceptions.TimeoutError: pass except (kombu_exceptions.ChannelLimitExceeded, kombu_exceptions.ConnectionLimitExceeded, kombu_exceptions.OperationalError, kombu_exceptions.NotBoundError, kombu_exceptions.MessageStateError, kombu_exceptions.LimitExceeded) as err: raise ChannelFailureException( "Error connecting to RabbitMQ, see inner exception for details", err) def _consume_errors(exc, interval: int) -> None: self._logger.error( 'Draining error: %s, will retry triggering in %s seconds', exc, interval, exc_info=True) def _read_message(body: str, msg: KombuMessage) -> None: self._logger.debug( "Monitoring event received at: %s headers: %s payload: %s", datetime.utcnow().isoformat(), msg.headers, body) self._msg = msg self._message = self._message_factory.create_message(msg) connection = BrokerConnection(hostname=self._amqp_uri) with connections[connection].acquire(block=True) as conn: self._logger.debug('Got connection: %s', conn.as_uri()) with Consumer(conn, queues=[self._queue], callbacks=[_read_message]) as consumer: consumer.qos(prefetch_count=1) ensure_kwargs = self.RETRY_OPTIONS.copy() ensure_kwargs['errback'] = _consume_errors safe_drain = conn.ensure(consumer, _consume, **ensure_kwargs) safe_drain(conn, timeout) return self._message
def test_handle_requeue_has_upper_bound(self): """ Given that I have a channel When I receive a requeue on that channel I should ask the consumer to requeue, up to a retry limit So that poison messages do not fill our queues """ handler = MyCommandHandler() request = MyCommand() channel = FakeChannel(name="MyCommand") command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, map_my_command_to_request, requeue_count=3) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) channel.add(message) requeue_spec = {"send.side_effect": DeferMessageException()} command_processor.configure_mock(**requeue_spec) started_event = Event() t = Thread(target=message_pump.run, args=(started_event,)) t.start() started_event.wait() time.sleep(1) channel.stop() t.join() self.assertTrue(command_processor.send.call_count, 3)
def test_posting_a_message(self): """Given that I have an RMQ message producer when I send that message via the producer then I should be able to read that message via the consumer """ header = BrightsideMessageHeader(uuid4(), self.test_topic, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody("test content") message = BrightsideMessage(header, body) self._consumer.purge() self._producer.send( message ) # if errors with akes 1 positional argument but 2 were given then manually purge read_message = self._consumer.receive(3) self._consumer.acknowledge(read_message) self.assertEqual(message.id, read_message.id) self.assertEqual(message.body.value, read_message.body.value) self.assertTrue(self._consumer.has_acknowledged(read_message))
def test_stop_performer(self): """ Given that I have started a performer When I stop the performer Then it should terminate the pump :return: """ request = MyCommand() pipeline = Queue() connection = Connection(config.broker_uri, "examples.perfomer.exchange") configuration = BrightsideConsumerConfiguration( pipeline, "performer.test.queue", "examples.tests.mycommand") performer = Performer("test_channel", connection, configuration, mock_consumer_factory, mock_command_processor_factory, map_my_command_to_request) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) pipeline.put(message) started_event = Event() p = performer.run(started_event) started_event.wait() time.sleep(1) performer.stop() p.join() self.assertTrue(True)
def test_get_from_message_store(self): """ Given that I have a message in When I retrieve from the store by Id THen it should be found """ store = SqlAlchemyMessageStore() message_id = uuid4() topic = "test topic" header = BrightsideMessageHeader(message_id, topic, BrightsideMessageType.MT_COMMAND) content = "test content" body = BrightsideMessageBody(content) message = BrightsideMessage(header, body) store.add(message) retreived_message = store.get_message(message_id) self.assertNotEqual(BrightsideMessageType.MT_NONE, retreived_message.header.message_type) self.assertEqual(message_id, retreived_message.id) self.assertEqual(content, retreived_message.body.value)
def test_stop_consumer(self): """Given that I have a dispatcher When I stop a consumer Then the performer should terminate """ request = MyCommand() pipeline = Queue() connection = Connection(config.broker_uri, "examples.perfomer.exchange") configuration = BrightsideConsumerConfiguration( pipeline, "dispatcher.test.queue", "examples.tests.mycommand") consumer = ConsumerConfiguration(connection, configuration, mock_consumer_factory, mock_command_processor_factory, map_my_command_to_request) dispatcher = Dispatcher({"MyCommand": consumer}) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) pipeline.put(message) self.assertEqual(dispatcher.state, DispatcherState.ds_awaiting) dispatcher.receive() time.sleep(1) dispatcher.end() self.assertEqual(dispatcher.state, DispatcherState.ds_stopped) self.assertTrue(pipeline.empty())
def create_empty_message() -> BrightsideMessage: return BrightsideMessage( BrightsideMessageHeader(identity=uuid4(), topic="", message_type=BrightsideMessageType.MT_NONE), BrightsideMessageBody(""))
def test_restart_consumer(self): """Given that I have a dispatcher with all consumers stopped When I restart a consumer Then the dispatcher should have one running consumer """ connection = Connection(config.broker_uri, "examples.perfomer.exchange") # First consumer request = MyCommand() pipeline_one = Queue() configuration_one = BrightsideConsumerConfiguration( pipeline_one, "restart_command.test.queue", "examples.tests.mycommand") consumer_one = ConsumerConfiguration(connection, configuration_one, mock_consumer_factory, mock_command_processor_factory, map_my_command_to_request) header_one = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body_one = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message_one = BrightsideMessage(header_one, body_one) pipeline_one.put(message_one) # Second consumer event = MyEvent() pipeline_two = Queue() configuration_two = BrightsideConsumerConfiguration( pipeline_two, "restart_event.test.queue", "examples.tests.myevent") consumer_two = ConsumerConfiguration(connection, configuration_two, mock_consumer_factory, mock_command_processor_factory, map_my_event_to_request) header_two = BrightsideMessageHeader(uuid4(), event.__class__.__name__, BrightsideMessageType.MT_EVENT) body_two = BrightsideMessageBody( JsonRequestSerializer(request=event).serialize_to_json(), BrightsideMessageBodyType.application_json) message_two = BrightsideMessage(header_two, body_two) pipeline_two.put_nowait(message_two) # Dispatcher dispatcher = Dispatcher({ "consumer_one": consumer_one, "consumer_two": consumer_two }) # Consume the messages and stop self.assertEqual(dispatcher.state, DispatcherState.ds_awaiting) dispatcher.receive() time.sleep(1) dispatcher.end() self.assertEqual(dispatcher.state, DispatcherState.ds_stopped) self.assertTrue(pipeline_one.empty()) #Now add a new message, restart a consumer, and eat event_three = MyEvent() header_three = BrightsideMessageHeader(uuid4(), event.__class__.__name__, BrightsideMessageType.MT_EVENT) body_three = BrightsideMessageBody( JsonRequestSerializer(request=event_three).serialize_to_json(), BrightsideMessageBodyType.application_json) message_three = BrightsideMessage(header_three, body_three) pipeline_two.put_nowait(message_three) dispatcher.open("consumer_two") time.sleep(1) dispatcher.end() self.assertEqual(dispatcher.state, DispatcherState.ds_stopped) self.assertTrue(pipeline_two.empty())
def create_null_message(): return BrightsideMessage( BrightsideMessageHeader(uuid4(), "", BrightsideMessageType.MT_NONE), BrightsideMessageBody(""))
def create_message(self, message: Message) -> BrightsideMessage: self._has_read_errors = False def _get_correlation_id() -> UUID: header, err = self._read_header(message_correlation_id_header, message) if err is None: return UUID(header) else: self._has_read_errors = True return uuid4() def _get_message_id() -> UUID: header, err = self._read_header(message_id_header, message) if err is None: return UUID(header) else: self._has_read_errors = True return uuid4() def _get_message_type() -> BrightsideMessageType: header, err = self._read_header(message_type_header, message) if err is None: return BrightsideMessageType[header] else: self._has_read_errors = True return BrightsideMessageType.MT_UNACCEPTABLE def _get_payload() -> str: body, err = self._read_payload(message) if err is None: return body else: self._has_read_errors = True return "" def _get_payload_type() -> str: payload_type, err = self._read_payload_type(message) if err is None: return payload_type else: self._has_read_errors = True return "" def _get_topic() -> str: header, err = self._read_header(message_topic_name_header, message) if err is None: return header else: self._has_read_errors = True return "" message_id = _get_message_id() topic = _get_topic() message_type = _get_message_type( ) if not message.errors or self._has_read_errors else BrightsideMessageType.MT_UNACCEPTABLE correlation_id = _get_correlation_id() payload = _get_payload() payload_type = _get_payload_type() message_header = BrightsideMessageHeader(identity=message_id, topic=topic, message_type=message_type, correlation_id=correlation_id, content_type="json") message_body = BrightsideMessageBody(body=payload, body_type=payload_type) return BrightsideMessage(message_header, message_body)
def create_quit_message(): body = BrightsideMessageBody(body="") header = BrightsideMessageHeader( uuid4(), topic="", message_type=BrightsideMessageType.MT_QUIT) return BrightsideMessage(header, body)