Exemplo n.º 1
0
def test_get_produced_message_routing_key_requeue(settings):
    settings.CQRS['queue'] = 'replica'
    payload = TransportPayload(SignalType.SAVE, 'CQRS_ID', {}, None)
    payload.is_requeue = True

    routing_key = PublicRabbitMQTransport.get_produced_message_routing_key(
        payload)

    assert routing_key == 'cqrs.replica.CQRS_ID'
Exemplo n.º 2
0
def test_get_produced_message_routing_key_dead_letter(settings):
    settings.CQRS['replica']['dead_letter_queue'] = 'dead_letter_replica'
    payload = TransportPayload(SignalType.SYNC, 'CQRS_ID', {}, None)
    payload.is_dead_letter = True

    routing_key = PublicRabbitMQTransport.get_produced_message_routing_key(
        payload)

    assert routing_key == 'cqrs.dead_letter_replica.CQRS_ID'
Exemplo n.º 3
0
    def _consume_message(cls, body, message):
        try:
            dct = ujson.loads(body)
            for key in ('signal_type', 'cqrs_id', 'instance_data'):
                if key not in dct:
                    raise ValueError

            if 'instance_pk' not in dct:
                logger.warning('CQRS deprecated package structure.')

        except ValueError:
            logger.error("CQRS couldn't be parsed: {}.".format(body))
            message.reject()
            return

        payload = TransportPayload(
            dct['signal_type'], dct['cqrs_id'], dct['instance_data'], dct.get('instance_pk'),
            previous_data=dct.get('previous_data'),
        )

        cls.log_consumed(payload)
        instance = consumer.consume(payload)

        if instance:
            message.ack()
            cls.log_consumed_accepted(payload)
        else:
            message.reject()
            cls.log_consumed_denied(payload)
Exemplo n.º 4
0
def test_message_without_retry_dead_letter(settings, mocker, caplog):
    settings.CQRS['replica']['CQRS_MAX_RETRIES'] = 1
    produce_message = mocker.patch(
        'dj_cqrs.transport.rabbit_mq.RabbitMQTransport._produce_message', )

    channel = mocker.MagicMock()
    payload = TransportPayload(
        SignalType.SAVE,
        'basic',
        {'id': 1},
        1,
        correlation_id='abc',
        retries=2,
    )
    delay_queue = DelayQueue()

    PublicRabbitMQTransport.fail_message(channel, 1, payload, None,
                                         delay_queue)

    assert delay_queue.qsize() == 0
    assert channel.basic_nack.call_count == 1

    assert produce_message.call_count == 1

    produce_payload = produce_message.call_args[0][2]
    assert produce_payload is payload
    assert getattr(produce_message, 'is_dead_letter', False)

    assert 'CQRS is failed: pk = 1 (basic), correlation_id = abc, retries = 2.' in caplog.text
    assert (
        'CQRS is added to dead letter queue: pk = 1 (basic), correlation_id = abc'
        in caplog.text)
Exemplo n.º 5
0
def test_produce_message_ok(mocker):
    channel = mocker.MagicMock()
    payload = TransportPayload(
        SignalType.SAVE,
        'cqrs_id',
        {},
        'id',
        previous_data={'e': 'f'},
    )
    exchange = PublicKombuTransport.create_exchange('exchange')

    PublicKombuTransport.produce_message(channel, exchange, payload)
    assert channel.basic_publish.call_count == 1

    prepare_message_args = channel.prepare_message.call_args[0]
    basic_publish_kwargs = channel.basic_publish.call_args[1]

    assert ujson.loads(prepare_message_args[0]) == \
        {
            'signal_type': SignalType.SAVE,
            'cqrs_id': 'cqrs_id',
            'instance_data': {},
            'instance_pk': 'id',
            'previous_data': {'e': 'f'},
        }

    assert prepare_message_args[2] == 'text/plain'
    assert prepare_message_args[5]['delivery_mode'] == 2

    assert basic_publish_kwargs['exchange'] == 'exchange'
    assert basic_publish_kwargs['mandatory']
    assert basic_publish_kwargs['routing_key'] == 'cqrs_id'
Exemplo n.º 6
0
    def _consume_message(cls, body, message):
        try:
            dct = ujson.loads(body)
        except ValueError:
            logger.error("CQRS couldn't be parsed: {0}.".format(body))
            message.reject()
            return

        required_keys = {'instance_pk', 'signal_type', 'cqrs_id', 'instance_data'}
        for key in required_keys:
            if key not in dct:
                msg = "CQRS couldn't proceed, %s isn't found in body: %s."
                logger.error(msg, key, body)
                message.reject()
                return

        payload = TransportPayload(
            dct['signal_type'],
            dct['cqrs_id'],
            dct['instance_data'],
            dct.get('instance_pk'),
            previous_data=dct.get('previous_data'),
            correlation_id=dct.get('correlation_id'),
        )

        cls.log_consumed(payload)
        instance = consumer.consume(payload)

        if instance:
            message.ack()
            cls.log_consumed_accepted(payload)
        else:
            message.reject()
            cls.log_consumed_denied(payload)
Exemplo n.º 7
0
    def _consume_message(cls, ch, method, properties, body):
        try:
            dct = ujson.loads(body)
            for key in ('signal_type', 'cqrs_id', 'instance_data'):
                if key not in dct:
                    raise ValueError

            if 'instance_pk' not in dct:
                logger.warning('CQRS deprecated package structure.')

        except ValueError:
            logger.error("CQRS couldn't be parsed: {}.".format(body))
            ch.basic_reject(delivery_tag=method.delivery_tag, requeue=False)
            return

        payload = TransportPayload(
            dct['signal_type'], dct['cqrs_id'], dct['instance_data'], dct.get('instance_pk'),
            previous_data=dct.get('previous_data'),
        )

        cls.log_consumed(payload)

        instance = None
        try:
            instance = consumer.consume(payload)
        except Exception:
            logger.error('CQRS service exception', exc_info=True)

        if instance:
            ch.basic_ack(delivery_tag=method.delivery_tag)
            cls.log_consumed_accepted(payload)
        else:
            ch.basic_nack(delivery_tag=method.delivery_tag)
            cls.log_consumed_denied(payload)
Exemplo n.º 8
0
    def post_delete(cls, sender, **kwargs):
        """
        :param dj_cqrs.mixins.MasterMixin sender: Class or instance inherited from CQRS MasterMixin.
        """
        if not sender.CQRS_PRODUCE:
            return

        instance = kwargs['instance']
        if not instance.is_sync_instance():
            return

        instance_data = {
            'id': instance.pk,
            'cqrs_revision': instance.cqrs_revision + 1,
            'cqrs_updated': str(now()),
        }

        data = instance.get_custom_cqrs_delete_data()
        if data:
            instance_data['custom'] = data

        signal_type = SignalType.DELETE

        payload = TransportPayload(signal_type, sender.CQRS_ID, instance_data,
                                   instance.pk)
        # Delete is always in transaction!
        transaction.on_commit(lambda: producer.produce(payload))
Exemplo n.º 9
0
def test_consumer(mocker):
    factory_mock = mocker.patch(
        'dj_cqrs.controller.consumer.route_signal_to_replica_model')
    consume(TransportPayload('a', 'b', {}, 'c', previous_data={'e': 'f'}))

    factory_mock.assert_called_once_with('a',
                                         'b', {},
                                         previous_data={'e': 'f'})
Exemplo n.º 10
0
def test_produce_connection_error(rabbit_transport, mocker, caplog):
    mocker.patch.object(RabbitMQTransport, '_get_producer_rmq_objects', side_effect=amqp_error)

    rabbit_transport.produce(
        TransportPayload(
            SignalType.SAVE, 'CQRS_ID', {'id': 1}, 1,
        ),
    )
    assert "CQRS couldn't be published: pk = 1 (CQRS_ID)." in caplog.text
Exemplo n.º 11
0
def test_transport_payload_infinite_expires():
    payload = TransportPayload.from_message({
        'signal_type': SignalType.SYNC,
        'cqrs_id': 'cqrs_id',
        'instance_data': {},
        'instance_pk': 'id',
        'expires': None,
    })

    assert payload.expires is None
Exemplo n.º 12
0
def test_delay_message_with_requeue(mocker, caplog):
    channel = mocker.MagicMock()
    requeue_message = mocker.patch(
        'dj_cqrs.transport.rabbit_mq.RabbitMQTransport._requeue_message', )

    delay_messages = []
    for delay in (2, 1, 3):
        payload = TransportPayload(SignalType.SAVE, 'CQRS_ID', {'id': delay},
                                   delay)
        eta = datetime.now(tz=timezone.utc) + timedelta(hours=delay)
        delay_message = DelayMessage(delivery_tag=delay,
                                     payload=payload,
                                     eta=eta)
        delay_messages.append(delay_message)

    delay_queue = DelayQueue(max_size=3)
    for delay_message in delay_messages:
        delay_queue.put(delay_message)

    exceeding_delay = 0
    exceeding_payload = TransportPayload(SignalType.SAVE, 'CQRS_ID', {'id': 4},
                                         4)
    PublicRabbitMQTransport.delay_message(
        channel,
        4,
        exceeding_payload,
        exceeding_delay,
        delay_queue,
    )

    assert delay_queue.qsize() == 3
    assert delay_queue.get().payload is exceeding_payload
    assert (
        'CQRS is delayed: pk = 4 (CQRS_ID), correlation_id = None, delay = 0 sec'
        in caplog.text)

    assert requeue_message.call_count == 1

    requeue_payload = requeue_message.call_args[0][2]
    min_eta_delay_message = sorted(delay_messages, key=lambda x: x.eta)[0]
    assert requeue_payload is min_eta_delay_message.payload
Exemplo n.º 13
0
def test_fail_message_with_retry(mocker):
    payload = TransportPayload(SignalType.SAVE, 'basic', {'id': 1}, 1)
    delay_queue = DelayQueue()

    PublicRabbitMQTransport.fail_message(mocker.MagicMock(), 100, payload,
                                         None, delay_queue)

    assert delay_queue.qsize() == 1

    delay_message = delay_queue.get()
    assert delay_message.delivery_tag == 100
    assert delay_message.payload is payload
Exemplo n.º 14
0
def test_produce_publish_error(rabbit_transport, mocker, caplog):
    mocker.patch.object(
        RabbitMQTransport, '_get_producer_rmq_objects', return_value=(mocker.MagicMock(), None),
    )
    mocker.patch.object(RabbitMQTransport, '_produce_message', side_effect=amqp_error)

    rabbit_transport.produce(
        TransportPayload(
            SignalType.SAVE, 'CQRS_ID', {'id': 1}, 1,
        ),
    )
    assert "CQRS couldn't be published: pk = 1 (CQRS_ID)." in caplog.text
Exemplo n.º 15
0
def test_produce_ok(rabbit_transport, mocker, caplog):
    caplog.set_level(logging.INFO)
    mocker.patch.object(
        RabbitMQTransport, '_get_producer_rmq_objects', return_value=(mocker.MagicMock(), None),
    )
    mocker.patch.object(RabbitMQTransport, '_produce_message', return_value=True)

    rabbit_transport.produce(
        TransportPayload(
            SignalType.SAVE, 'CQRS_ID', {'id': 1}, 1,
        ),
    )
    assert 'CQRS is published: pk = 1 (CQRS_ID).' in caplog.text
Exemplo n.º 16
0
def test_transport_payload_without_expires(mocker, settings):
    fake_now = datetime(2020, 1, 1, second=0, tzinfo=timezone.utc)
    mocker.patch('django.utils.timezone.now', return_value=fake_now)

    settings.CQRS['master']['CQRS_MESSAGE_TTL'] = 10
    expected_expires = datetime(2020, 1, 1, second=10, tzinfo=timezone.utc)

    payload = TransportPayload.from_message({
        'signal_type': SignalType.SYNC,
        'cqrs_id': 'cqrs_id',
        'instance_data': {},
        'instance_pk': 'id',
    })

    assert payload.expires == expected_expires
Exemplo n.º 17
0
def test_produce_sync_message_queue(mocker):
    channel = mocker.MagicMock()
    payload = TransportPayload(SignalType.SYNC, 'cqrs_id', {}, 'id', 'queue')

    PublicRabbitMQTransport.produce_message(channel, 'exchange', payload)

    basic_publish_kwargs = channel.basic_publish.call_args[1]
    assert ujson.loads(basic_publish_kwargs['body']) == \
        {
            'signal_type': SignalType.SYNC,
            'cqrs_id': 'cqrs_id',
            'instance_data': {},
            'instance_pk': 'id',
            'previous_data': None,
        }
    assert basic_publish_kwargs['routing_key'] == 'cqrs.queue.cqrs_id'
Exemplo n.º 18
0
def test_producer(mocker):
    transport_mock = mocker.patch('tests.dj.transport.TransportStub.produce')
    produce(TransportPayload('a', 'b', {}, 'c', previous_data={'e': 'f'}))

    assert transport_mock.call_count == 1
    assert transport_mock.call_args[0][0].to_dict() == {
        'signal_type': 'a',
        'cqrs_id': 'b',
        'instance_data': {},
        'instance_pk': 'c',
        'previous_data': {
            'e': 'f'
        },
        'correlation_id': None,
        'expires': None,
        'retries': 0,
    }
Exemplo n.º 19
0
    def post_save(cls, sender, **kwargs):
        """
        :param dj_cqrs.mixins.MasterMixin sender: Class or instance inherited from CQRS MasterMixin.
        """
        if not sender.CQRS_PRODUCE:
            return

        update_fields = kwargs.get('update_fields')
        if update_fields and ('cqrs_revision' not in update_fields):
            return

        instance = kwargs['instance']
        if not instance.is_sync_instance():
            return

        using = kwargs['using']

        sync = kwargs.get('sync', False)
        queue = kwargs.get('queue', None)

        connection = transaction.get_connection(using)
        if not connection.in_atomic_block:
            instance.reset_cqrs_saves_count()
            instance_data = instance.to_cqrs_dict(using, sync=sync)
            previous_data = instance.get_tracked_fields_data()
            signal_type = SignalType.SYNC if sync else SignalType.SAVE
            payload = TransportPayload(
                signal_type,
                sender.CQRS_ID,
                instance_data,
                instance.pk,
                queue,
                previous_data,
                expires=get_expires_datetime(),
            )
            producer.produce(payload)

        elif instance.is_initial_cqrs_save:
            transaction.on_commit(
                lambda: MasterSignals.post_save(
                    sender,
                    instance=instance,
                    using=using,
                    sync=sync,
                    queue=queue,
                ), )
Exemplo n.º 20
0
    def _consume_message(cls, ch, method, properties, body, delay_queue):
        try:
            dct = ujson.loads(body)
        except ValueError:
            logger.error("CQRS couldn't be parsed: {0}.".format(body))
            ch.basic_reject(delivery_tag=method.delivery_tag, requeue=False)
            return

        required_keys = {
            'instance_pk', 'signal_type', 'cqrs_id', 'instance_data'
        }
        for key in required_keys:
            if key not in dct:
                msg = "CQRS couldn't proceed, %s isn't found in body: %s."
                logger.error(msg, key, body)
                ch.basic_reject(delivery_tag=method.delivery_tag,
                                requeue=False)
                return

        payload = TransportPayload.from_message(dct)
        cls.log_consumed(payload)

        delivery_tag = method.delivery_tag
        if payload.is_expired():
            cls._add_to_dead_letter_queue(ch, payload)
            cls._nack(ch, delivery_tag)
            return

        instance, exception = None, None
        try:
            instance = consumer.consume(payload)
        except Exception as e:
            exception = e
            logger.error("CQRS service exception", exc_info=True)

        if instance and exception is None:
            cls._ack(ch, delivery_tag, payload)
        else:
            cls._fail_message(
                ch,
                delivery_tag,
                payload,
                exception,
                delay_queue,
            )
Exemplo n.º 21
0
def test_produce_sync_message_no_queue(mocker):
    channel = mocker.MagicMock()
    payload = TransportPayload(SignalType.SYNC, 'cqrs_id', {}, None)

    exchange = PublicKombuTransport.create_exchange('exchange')

    PublicKombuTransport.produce_message(channel, exchange, payload)

    prepare_message_args = channel.prepare_message.call_args[0]
    basic_publish_kwargs = channel.basic_publish.call_args[1]

    assert ujson.loads(prepare_message_args[0]) == \
        {
            'signal_type': SignalType.SYNC,
            'cqrs_id': 'cqrs_id',
            'instance_data': {},
            'instance_pk': None,
            'previous_data': None,
        }
    assert basic_publish_kwargs['routing_key'] == 'cqrs_id'
Exemplo n.º 22
0
    def handle_retry(self, channel, consumer_generator, dead_letters_count):
        self.stdout.write("Total dead letters: {}".format(dead_letters_count))
        for i in range(1, dead_letters_count + 1):
            self.stdout.write("Retrying: {}/{}".format(i, dead_letters_count))
            method_frame, properties, body = next(consumer_generator)

            dct = ujson.loads(body)
            dct['retries'] = 0
            if dct.get('expires'):
                # Message could expire already
                expires = get_expires_datetime()
                dct['expires'] = expires.replace(microsecond=0).isoformat()
            payload = TransportPayload.from_message(dct)
            payload.is_requeue = True

            RabbitMQTransportService.produce(payload)
            message = ujson.dumps(dct)
            self.stdout.write(message)

            RabbitMQTransportService.nack(channel, method_frame.delivery_tag)
Exemplo n.º 23
0
def test_fail_message_invalid_model(mocker, caplog):
    nack = mocker.patch(
        'dj_cqrs.transport.rabbit_mq.RabbitMQTransport._nack', )
    payload = TransportPayload(SignalType.SAVE, 'not_existing', {'id': 1}, 1)
    delay_queue = DelayQueue()

    delivery_tag = 101
    PublicRabbitMQTransport.fail_message(
        mocker.MagicMock(),
        delivery_tag,
        payload,
        None,
        delay_queue,
    )

    assert delay_queue.qsize() == 0
    assert nack.call_count == 1
    assert nack.call_args[0][1] == delivery_tag

    assert 'Model for cqrs_id not_existing is not found.' in caplog.text
Exemplo n.º 24
0
def test_changed_payload_data_during_consume(mocker):
    def change_data(*args, **kwargs):
        instance_data = args[2]
        instance_data['instance_key'] = 'changed instance'
        kwargs['previous_data']['previous_key'] = 'changed previous'

    factory_mock = mocker.patch(
        'dj_cqrs.controller.consumer.route_signal_to_replica_model',
        side_effect=change_data,
    )

    payload = TransportPayload(
        SignalType.SAVE,
        cqrs_id='b',
        instance_data={'instance_key': 'initial instance'},
        instance_pk='c',
        previous_data={'previous_key': 'initial previous'},
    )
    consume(payload)

    assert factory_mock.call_count == 1
    assert payload.instance_data == {'instance_key': 'initial instance'}
    assert payload.previous_data == {'previous_key': 'initial previous'}
Exemplo n.º 25
0
def test_produce_message_ok(mocker):
    expires = datetime(2100, 1, 1, tzinfo=timezone.utc)
    expected_expires = '2100-01-01T00:00:00+00:00'

    channel = mocker.MagicMock()
    payload = TransportPayload(
        SignalType.SAVE,
        cqrs_id='cqrs_id',
        instance_data={},
        instance_pk='id',
        previous_data={'e': 'f'},
        expires=expires,
        retries=2,
    )

    PublicRabbitMQTransport.produce_message(channel, 'exchange', payload)

    assert channel.basic_publish.call_count == 1

    basic_publish_kwargs = channel.basic_publish.call_args[1]
    assert ujson.loads(basic_publish_kwargs['body']) == {
        'signal_type': SignalType.SAVE,
        'cqrs_id': 'cqrs_id',
        'instance_data': {},
        'instance_pk': 'id',
        'previous_data': {
            'e': 'f'
        },
        'correlation_id': None,
        'expires': expected_expires,
        'retries': 2,
    }
    assert basic_publish_kwargs['exchange'] == 'exchange'
    assert basic_publish_kwargs['mandatory']
    assert basic_publish_kwargs['routing_key'] == 'cqrs_id'
    assert basic_publish_kwargs['properties'].content_type == 'text/plain'
    assert basic_publish_kwargs['properties'].delivery_mode == 2
Exemplo n.º 26
0
def test_produce_message_ok(mocker):
    channel = mocker.MagicMock()
    payload = TransportPayload(
        SignalType.SAVE, 'cqrs_id', {}, 'id', previous_data={'e': 'f'},
    )

    PublicRabbitMQTransport.produce_message(channel, 'exchange', payload)

    assert channel.basic_publish.call_count == 1

    basic_publish_kwargs = channel.basic_publish.call_args[1]
    assert ujson.loads(basic_publish_kwargs['body']) == \
        {
            'signal_type': SignalType.SAVE,
            'cqrs_id': 'cqrs_id',
            'instance_data': {},
            'instance_pk': 'id',
            'previous_data': {'e': 'f'},
        }
    assert basic_publish_kwargs['exchange'] == 'exchange'
    assert basic_publish_kwargs['mandatory']
    assert basic_publish_kwargs['routing_key'] == 'cqrs_id'
    assert basic_publish_kwargs['properties'].content_type == 'text/plain'
    assert basic_publish_kwargs['properties'].delivery_mode == 2
Exemplo n.º 27
0
def test_process_delay_messages(mocker, caplog):
    channel = mocker.MagicMock()
    produce = mocker.patch(
        'dj_cqrs.transport.rabbit_mq.RabbitMQTransport.produce')

    payload = TransportPayload(SignalType.SAVE, 'CQRS_ID', {'id': 1}, 1)
    delay_queue = DelayQueue()
    delay_queue.put(
        DelayMessage(delivery_tag=1,
                     payload=payload,
                     eta=datetime.now(tz=timezone.utc)), )

    PublicRabbitMQTransport.process_delay_messages(channel, delay_queue)

    assert delay_queue.qsize() == 0
    assert channel.basic_nack.call_count == 1
    assert produce.call_count == 1

    produce_payload = produce.call_args[0][0]
    assert produce_payload is payload
    assert produce_payload.retries == 1
    assert getattr(produce_payload, 'is_requeue', False)

    assert 'CQRS is requeued: pk = 1 (CQRS_ID)' in caplog.text