def test_create_work_description(self, work_mock, time_mock):
     time_mock.return_value = '12'
     persistence = MagicMock()
     wd.create_new_work_description(
         persistence,
         message_id='aaa-aaa',
         outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED,
         workflow=workflow.SYNC)
     work_mock.assert_called_with(
         persistence,
         wd.build_store_data(
             'aaa-aaa',
             '12',
             workflow.SYNC,
             outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED))
Example #2
0
    async def handle_unsolicited_inbound_message(self, message_id: str,
                                                 correlation_id: str,
                                                 payload: str,
                                                 attachments: list):
        logger.info(
            '0005',
            'Entered async forward reliable workflow to handle unsolicited inbound message'
        )
        logger.audit('0101',
                     'Unsolicited inbound {WorkflowName} workflow invoked.',
                     {'WorkflowName': self.workflow_name})
        work_description = wd.create_new_work_description(
            self.persistence_store, message_id, self.workflow_name,
            wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_RECEIVED)
        await work_description.publish()

        for retry_num in range(self.inbound_queue_max_retries + 1):
            try:
                await self._put_message_onto_queue_with(
                    message_id,
                    correlation_id,
                    payload,
                    attachments=attachments)
                break
            except Exception as e:
                logger.warning(
                    '0006',
                    'Failed to put unsolicited message onto inbound queue due to {Exception}',
                    {'Exception': e})
                if retry_num >= self.inbound_queue_max_retries:
                    logger.error(
                        "0020",
                        "Exceeded the maximum number of retries, {max_retries} retries, when putting "
                        "unsolicited message onto inbound queue",
                        {"max_retries": self.inbound_queue_max_retries})
                    await work_description.set_inbound_status(
                        wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_FAILED)
                    raise MaxRetriesExceeded(
                        'The max number of retries to put a message onto the inbound queue has '
                        'been exceeded') from e

                logger.info(
                    "0021",
                    "Waiting for {retry_delay} seconds before retrying putting unsolicited message "
                    "onto inbound queue",
                    {"retry_delay": self.inbound_queue_retry_delay})
                await asyncio.sleep(self.inbound_queue_retry_delay)

        logger.audit(
            '0022',
            '{WorkflowName} workflow invoked for inbound unsolicited request. '
            'Attempted to place message onto inbound queue with {Acknowledgement}.',
            {
                'Acknowledgement': wd.MessageStatus.
                UNSOLICITED_INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED,
                'WorkflowName': self.workflow_name
            })
        await work_description.set_inbound_status(
            wd.MessageStatus.
            UNSOLICITED_INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED)
Example #3
0
 async def _create_new_work_description_if_required(self, message_id: str,
                                                    wdo: wd.WorkDescription,
                                                    workflow_name: str):
     if not wdo:
         wdo = wd.create_new_work_description(
             self.persistence_store,
             message_id,
             workflow_name,
             outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED)
         await wdo.publish()
     return wdo
Example #4
0
 def test_create_work_description(self, work_mock, time_mock):
     time_mock.return_value = '12'
     persistence = MagicMock()
     wd.create_new_work_description(
         persistence,
         key='aaa-aaa',
         outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED,
         workflow=workflow.SYNC)
     work_mock.assert_called_with(
         persistence, {
             wd.DATA_KEY: 'aaa-aaa',
             wd.DATA: {
                 wd.CREATED_TIMESTAMP: '12',
                 wd.LATEST_TIMESTAMP: '12',
                 wd.INBOUND_STATUS: None,
                 wd.OUTBOUND_STATUS:
                 wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED,
                 wd.VERSION_KEY: 1,
                 wd.WORKFLOW: workflow.SYNC
             }
         })
    def test_create_wd_null_parameters(self):
        persistence = MagicMock()
        with self.subTest('Null key'):
            with self.assertRaises(ValueError):
                wd.create_new_work_description(
                    persistence,
                    message_id=None,
                    outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED,
                    workflow=workflow.SYNC)
        with self.subTest('Null persistence'):
            with self.assertRaises(ValueError):
                wd.create_new_work_description(
                    None,
                    message_id='aaa',
                    outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED,
                    workflow=workflow.SYNC)
        with self.subTest('Null workflow'):
            with self.assertRaises(ValueError):
                wd.create_new_work_description(
                    persistence,
                    message_id='aaa',
                    outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED,
                    workflow=None)
        with self.subTest('Null statuses'):
            with self.assertRaises(ValueError):
                wd.create_new_work_description(persistence,
                                               message_id='aaa',
                                               outbound_status=None,
                                               inbound_status=None,
                                               workflow=workflow.SYNC_ASYNC)

        with self.subTest('Single null status should not raise error'):
            wd.create_new_work_description(
                persistence,
                message_id='aaa',
                outbound_status=wd.MessageStatus.INBOUND_RESPONSE_FAILED,
                inbound_status=None,
                workflow=workflow.SYNC_ASYNC)
Example #6
0
    async def handle_sync_async_outbound_message(self, from_asid: Optional[str], message_id: str, correlation_id: str,
                                                 interaction_details: dict,
                                                 payload: str,
                                                 async_workflow: common.CommonWorkflow
                                                 ) -> Tuple[int, str, wd.WorkDescription]:

        logger.info('Entered sync-async workflow to handle outbound message')
        wdo = wd.create_new_work_description(self.work_description_store, message_id,
                                             workflow.SYNC_ASYNC,
                                             outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED)
        await wdo.publish()

        status_code, response, _ = await async_workflow.handle_outbound_message(from_asid, message_id, correlation_id,
                                                                                interaction_details, payload, wdo)
        if not status_code == 202:
            logger.warning('No ACK received ')
            return status_code, response, wdo

        status_code, response = await self._retrieve_async_response(message_id, wdo)
        return status_code, response, wdo
Example #7
0
    async def handle_unsolicited_inbound_message(self, message_id: str, correlation_id: str, message_data: MessageData):
        logger.info('Entered async forward reliable workflow to handle unsolicited inbound message')
        logger.audit('Unsolicited inbound {WorkflowName} workflow invoked.',
                     fparams={'WorkflowName': self.workflow_name})
        work_description = wd.create_new_work_description(self.persistence_store, message_id, self.workflow_name,
                                                          wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_RECEIVED)
        await work_description.publish()

        try:
            await self._put_message_onto_queue_with(message_id, correlation_id, message_data)
        except Exception as e:
            await work_description.set_inbound_status(wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_FAILED)
            raise e

        logger.audit('{WorkflowName} workflow invoked for inbound unsolicited request. '
                     'Attempted to place message onto inbound queue with {Acknowledgement}.',
                     fparams={
                        'Acknowledgement': wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED,
                        'WorkflowName': self.workflow_name
                     })
        await work_description.set_inbound_status(wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED)
Example #8
0
    async def handle_outbound_message(self,
                                      from_asid: str,
                                      message_id: str,
                                      correlation_id: str,
                                      interaction_details: dict,
                                      payload: str,
                                      work_description_object: Optional[wd.WorkDescription]) \
            -> Tuple[int, str, Optional[wd.WorkDescription]]:
        logger.info('Entered sync workflow for outbound message')
        logger.audit('Outbound Synchronous workflow invoked.')

        wdo = wd.create_new_work_description(
            self.wd_store,
            message_id,
            workflow.SYNC,
            outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED)
        await wdo.publish()

        if not from_asid:
            return 400, '`from_asid` header field required for sync messages', None

        try:
            endpoint_details = await self._lookup_endpoint_details(
                interaction_details)
            url = endpoint_details[self.ENDPOINT_URL]
            to_asid = endpoint_details[self.ENDPOINT_TO_ASID]
        except Exception:
            await wdo.set_outbound_status(
                wd.MessageStatus.OUTBOUND_MESSAGE_PREPARATION_FAILED)
            return 500, 'Error obtaining outbound URL', None

        try:
            message_id, headers, message = await self._prepare_outbound_message(
                message_id,
                to_asid,
                from_asid=from_asid,
                interaction_details=interaction_details,
                message=payload)
        except Exception:
            logger.exception('Failed to prepare outbound message')
            await wdo.set_outbound_status(
                wd.MessageStatus.OUTBOUND_MESSAGE_PREPARATION_FAILED)
            return 500, 'Failed message preparation', None

        if len(message) > self.max_request_size:
            logger.error(
                'Request to send to Spine is too large after serialisation. {RequestSize} {MaxRequestSize}',
                fparams={
                    'RequestSize': len(message),
                    'MaxRequestSize': self.max_request_size
                })
            await wdo.set_outbound_status(
                wd.MessageStatus.OUTBOUND_MESSAGE_PREPARATION_FAILED)
            return 400, f'Request to send to Spine is too large. MaxRequestSize={self.max_request_size} '\
                        f'RequestSize={len(message)}', None

        logger.info('Outbound message prepared')
        try:
            response = await self.transmission.make_request(
                url, headers, message)
        except httpclient.HTTPClientError as e:
            code, error = await self._handle_http_exception(e, wdo)
            return code, error, wdo

        except Exception:
            logger.exception(
                'Error encountered whilst making outbound request.')
            await wdo.set_outbound_status(
                wd.MessageStatus.OUTBOUND_MESSAGE_TRANSMISSION_FAILED)
            return 500, 'Error making outbound request', None

        logger.audit(
            'Outbound Synchronous workflow completed. Message sent to Spine and {Acknowledgment} received.',
            fparams={
                'Acknowledgment':
                wd.MessageStatus.OUTBOUND_SYNC_MESSAGE_RESPONSE_RECEIVED
            })

        await wdo.set_outbound_status(
            wd.MessageStatus.OUTBOUND_SYNC_MESSAGE_RESPONSE_RECEIVED)
        return response.code, response.body.decode(), wdo