Example #1
0
    def process_message(self, body, message):
        """
        gets called back once a message arrives in the work queue

            1. Sends email notifiactions once msg goes to dead letter queue

        :param body: message payload
        :param message: queued message with headers and other metadata
        """
        logger = logging.getLogger(self.__class__.__name__)
        job = EasyJob.create_from_dict(message.headers)

        try:
            response = job.notify_error(body, self.get_config().async_timeout)

            if response.status_code != 200:
                logger.error("Failed to notify with status code: {} with message: {}".format(response.status_code,
                                                                                             response.message))
                self.log_to_file(response, job, body)
        except Exception as e:
            traceback.print_exc()
            err_msg = "Notification Failed for job {}".format(body)
            logger.error(err_msg)

        message.ack()
    def test__shoveller(self, easy_job_mock, produce_to_queue_mock):
        job_mock = Mock()
        job_mock.tag = "unknown"
        easy_job_mock.create_from_dict.return_value = job_mock
        body = json.dumps({"body": "work body"})
        message = Mock()
        api = "http://test.api.com/test_dest"
        api_request_headers = {"title": "Yippi"}
        job = EasyJob.create(api, constants.API_REMOTE, api_request_headers=api_request_headers)
        headers = {}
        headers.update(job.to_dict())
        message.headers = headers

        self.retry_consumer._shoveller(body, message)

        produce_to_queue_mock.assert_called_with(constants.BUFFER_QUEUE, body, job_mock)
Example #3
0
    def __push_raw_msg_to_dlq(self, body, message, err_msg):
        """
        pushes the raw message to dead letter queue for manual intervension and notification

        :param body: body of the message
        :param message: kombu amqp message object with headers and other metadata
        :param error_mesg: what error caused this push to error queue
        """
        logger = logging.getLogger(self.__class__.__name__)

        try:
            logger.info("Moving raw item to DLQ for notification and manual intervention")
            job = EasyJob.create_dummy_clone_from_dict(message.headers)
            job.add_error(EasyResponse(400, err_msg).__dict__)
            self.produce_to_queue(constants.DEAD_LETTER_QUEUE, body, job)

        except Exception as e:
            traceback.print_exc()
            logger.error("Error moving the raw-error to dead-letter-queue: {err}".format(err=str(e)))
    def test__push_msg_to_dlq(self, easy_job_mock, produce_to_queue):
        job_mock = Mock()
        job_mock.tag = "unknown"
        job_mock.no_of_retries = 1
        easy_job_mock.create_from_dict.return_value = job_mock

        body = {"body": "work body"}
        message = Mock()
        api_request_headers = {"title": "Yippi"}
        job = EasyJob.create("test_api",
                             constants.API_REMOTE,
                             api_request_headers=api_request_headers)
        headers = {}
        headers.update(job.to_dict())
        message.headers = headers

        self.work_queue_con._push_msg_to_dlq(body, message, job)

        produce_to_queue.assert_called_with(constants.DEAD_LETTER_QUEUE, body,
                                            job)
    def test_process_message(self, easy_job_mock, produce_to_queue_mock):
        # mock the job to be created in the process_message call
        job_mock = Mock()
        job_mock.tag = "unknown"
        job_mock.no_of_retries = 1
        easy_job_mock.create_from_dict.return_value = job_mock

        body = json.dumps({"body": "work body"})
        message = Mock()
        api = "http://test.api.com/test_dest"
        api_request_headers = {"title": "Yippi"}
        job = EasyJob.create(api, constants.API_REMOTE, api_request_headers=api_request_headers)
        headers = {}
        headers.update(job.to_dict())
        message.headers = headers

        # when no of retires is less than max then add back to work queue
        self.retry_consumer.process_message(body, message)
        produce_to_queue_mock.assert_called_with(constants.WORK_QUEUE, body, job_mock)
        message.ack.assert_called()
        message.reset_mock()

        # test exception
        produce_to_queue_mock.side_effect = Exception()
        self.retry_consumer.process_message(body, message)
        message.assert_not_called()

        message.reset_mock()

        # when no of retries is more than max retries then add to dead letter queue
        produce_to_queue_mock.side_effect = None
        job_mock.no_of_retries = constants.DEFAULT_MAX_JOB_RETRIES + 1
        self.retry_consumer.process_message(body, message)
        produce_to_queue_mock.assert_called_with(constants.DEAD_LETTER_QUEUE, body, job_mock)
        message.ack.assert_called()

        # test exception
        message.reset_mock()
        produce_to_queue_mock.side_effect = Exception()
        self.retry_consumer.process_message(body, message)
        message.assert_not_called()
Example #6
0
    def enqueue_job(self,
                    api,
                    type,
                    tag=None,
                    remote_call_type=None,
                    data=None,
                    api_request_headers=None,
                    content_type=None,
                    notification_handler=None):
        """
        Enqueue a job to be processed.
        :param api: The api to be called when job is run
        :param type: The type of job (Remote/Local) when local then a python call is made and in remote an REST call is made.
        :param tag: a tag for the job to be run
        :param remote_call_type: is the call POST/GET/PUT
        :param data: a data payload to be passed along in the job
        :param api_request_headers: request headers to be passed along in a remote call
        :param content_type: content type to be used in remote call
        :param notification_handler: the api to be called when a job goes into dlq (type same as api)
        :return: A unique job id assigned to the job.
        """
        self.validate_init()

        # create the job
        job = EasyJob.create(api,
                             type,
                             tag=tag,
                             remote_call_type=remote_call_type,
                             data=data,
                             api_request_headers=api_request_headers,
                             content_type=content_type,
                             notification_handler=notification_handler)

        # enqueue
        enqueue(self._producer, constants.WORK_QUEUE, job, data)

        return job.id
    def test_process_message(self, remote_call_type_mock, push_dlq_mock,
                             push_retry_mock):
        post = Mock()
        response = Mock()
        response.status_code = 200
        post.return_value = response
        remote_call_type_mock.get.return_value = post

        # Test remote job flow
        body = json.dumps({"body": "work body"})
        message = Mock()
        api = "http://test.api.com/test_dest"
        api_request_headers = {"title": "Yippi"}
        job = EasyJob.create(api,
                             constants.API_REMOTE,
                             api_request_headers=api_request_headers)
        headers = {}
        headers.update(job.to_dict())
        message.headers = headers

        self.work_queue_con.process_message(body, message)

        data_body = {'tag': 'unknown', 'data': body, 'job_id': job.id}

        # when return in 200
        post.assert_called_with(api,
                                data=data_body,
                                timeout=constants.DEFAULT_ASYNC_TIMEOUT,
                                headers=api_request_headers)

        # when the status code is 410 (in the error list to be reported
        # then the job will be added to be dlq
        response.status_code = 410
        response.text = "big error"
        self.work_queue_con.process_message(body, message)
        push_dlq_mock.assert_called()

        # when the status code is 5XX then add to the error queue
        response.status_code = 520
        response.text = "big error"
        self.work_queue_con.process_message(body, message)
        push_retry_mock.assert_called()

        # test local flow

        sys.path += os.path.dirname(test_class.__file__)

        # test with module function
        api = test_class.dummy_function_external
        job = EasyJob.create(api, constants.API_LOCAL)

        headers = {}
        headers.update(job.to_dict())
        message.headers = headers
        test_class.TestClass.module_function_called = False
        self.work_queue_con.process_message(body, message)
        self.assertEqual(test_class.TestClass.module_function_called, True)

        # test with string function
        api = "tests.test_class.dummy_function_external"
        job = EasyJob.create(api, constants.API_LOCAL)

        headers = {}
        headers.update(job.to_dict())
        message.headers = headers
        test_class.TestClass.module_function_called = False
        self.work_queue_con.process_message(body, message)
        self.assertEqual(test_class.TestClass.module_function_called, True)

        # test with instance function
        test_cls = test_class.TestClass()
        api = test_cls.dummy_function_in_class
        job = EasyJob.create(api, constants.API_LOCAL)

        headers = {}
        headers.update(job.to_dict())
        message.headers = headers
        test_class.TestClass.class_function_called = False
        self.work_queue_con.process_message(body, message)
        self.assertEqual(test_class.TestClass.class_function_called, True)

        # test with instance class
        tst_class = test_class.TestClass()
        job = EasyJob.create(tst_class, constants.API_LOCAL)

        headers = {}
        headers.update(job.to_dict())
        message.headers = headers
        test_class.TestClass.class_instance_called = False
        self.work_queue_con.process_message(body, message)
        self.assertEqual(test_class.TestClass.class_instance_called, True)
Example #8
0
    def process_message(self, body, message):
        """
        gets called back once a message arrives in the work queue

            1. calls embedded api with the payload as its parameters when a message arrives

            2. if the call is successful, acks the message
            3. for remote call 
                a. in case the call fails with 4XX, just acks the message, no further action
                b. in case the call fails with a 5XX,
                  - adds the error to error-log header
                  - if num-retries are more than max_retries,
                    - puts the message in dead-letter-queue
                  - else
                    - increases num-retries by 1
                    - puts the message in error-queue
            4. for local call
                a. in case the call fails with a exception then adds the call to a dead letter queue

        :param body: message payload
        :param message: queued message with headers and other metadata (contains a EasyJob object in headers)
        """
        logger = logging.getLogger(self.__class__.__name__)

        try:
            job = EasyJob.create_from_dict(message.headers)
        except easyjoblite.exception.UnableToCreateJob as e:
            logger.error(e.message + " data: " + str(e.data))
            message.ack()
            self.__push_raw_msg_to_dlq(body=body,
                                       message=message,
                                       err_msg=e.message,
                                       )
            return
        try:
            api = job.api
            logger.debug("recieved api: " + str(api))

            response = job.execute(body, self.get_config().async_timeout)

            message.ack()

            if response.status_code >= 400:
                # todo: we should have booking id here in the log message
                logger.info("{status}: {resp}".format(status=response.status_code,
                                                      resp=response.message))

                if response.status_code >= 500:
                    # we have a retry-able failure
                    self._push_message_to_error_queue(body=body, message=message, job=job)

                else:
                    # push not retry-able error to dlq
                    self._push_msg_to_dlq(body=body,
                                          message=message,
                                          job=job
                                          )
        except (Exception, easyjoblite.exception.ApiTimeoutException) as e:
            traceback.print_exc()
            logger.error(str(e))
            message.ack()
            self._push_message_to_error_queue(body, message, job)