Esempio n. 1
0
    async def run(self, loop, config, callback):  # pylint: disable=too-many-locals
        """Run the main application loop for the service.

        This runs the main top level service functions for working with the Queue.
        """
        self.service = ServiceWorker(loop=loop,
                                     cb_handler=callback,
                                     config=config)
        self.probe = Probes(components=[self.service], loop=loop)

        try:
            await self.probe.start()
            await self.service.connect()

            # register the signal handler
            for sig in ('SIGINT', 'SIGTERM'):
                loop.add_signal_handler(
                    getattr(signal, sig),
                    functools.partial(signal_handler,
                                      sig_loop=loop,
                                      task=self.close))

        except Exception as e:  # pylint: disable=broad-except # noqa B902
            # TODO tighten this error and decide when to bail on the infinite reconnect
            logger.error(e)
Esempio n. 2
0
    async def connect(self):
        """Connect the service worker to th3e NATS/STAN Queue.

        Also handles reconnecting when the network has dropped the connection.
        Both the NATS and the STAN clients need to be reinstantiated to work correctly.

        """
        if not self.config:
            logger.error('missing configuration object.')
            raise AttributeError('missing configuration object.')

        logger.info('Connecting...')
        if self.nc:
            try:
                logger.debug('close old NATS client')
                await self.nc.close()
            except asyncio.CancelledError as err:
                logger.debug('closing stale connection err:%s', err)
            finally:
                self.nc = None

        self.nc = NATS()
        self.sc = STAN()

        nats_connection_options = {
            **self.config.NATS_CONNECTION_OPTIONS,
            **{
                'loop': self._loop,
                'error_cb': error_cb
            },
            **self.nats_connection_options
        }

        stan_connection_options = {
            **self.config.STAN_CONNECTION_OPTIONS,
            **{
                'nats': self.nc,
                'conn_lost_cb': self._stan_conn_lost_cb,
                'loop': self._loop
            },
            **self.stan_connection_options
        }

        subscription_options = {
            **self.config.SUBSCRIPTION_OPTIONS,
            **{
                'cb': self.cb_handler
            },
            **self.subscription_options
        }

        await self.nc.connect(**nats_connection_options)
        await self.sc.connect(**stan_connection_options)
        await self.sc.subscribe(**subscription_options)

        logger.info(
            'Subscribe the callback: %s to the queue: %s.',
            subscription_options.get('cb').__name__
            if subscription_options.get('cb') else 'no_call_back',
            subscription_options.get('queue'))
Esempio n. 3
0
def send_to_solr_add(nr: RequestDAO):
    """Send json payload to add names to solr for NR."""
    # pylint: disable=no-member
    name_states = [NameState.APPROVED.value, NameState.CONDITION.value]
    names = find_name_by_name_states(nr.id, name_states)
    jur = nr.xproJurisdiction if nr.xproJurisdiction else 'BC'
    payload_dict = construct_payload_dict(nr, names, jur)
    resp = post_to_solr_feeder(payload_dict)
    if resp.status_code != 200:
        logger.error(
            'failed to add names to solr for %s, status code: %i, error reason: %s, error details: %s',
            nr.nrNum, resp.status_code, resp.reason, resp.text)
Esempio n. 4
0
 async def conn_lost_cb(error):
     logger.info('Connection lost:%s', error)
     for i in range(0, 100):
         try:
             logger.info('Reconnecting, attempt=%i...', i)
             await self.connect()
         except Exception as e:  # pylint: disable=broad-except # noqa B902
             # catch all errors from client framework
             logger.error('Error %s',
                          e.with_traceback(),
                          stack_info=True)
             continue
         break
Esempio n. 5
0
def send_to_solr_delete(nr: RequestDAO):
    """Send json payload to delete names from solr for NR."""
    delete_ids = get_nr_ids_to_delete_from_solr(nr)
    payload_dict = {
        'solr_core': 'names',
        'request': {
            'delete': delete_ids,
            'commit': {}
        }
    }
    request_str = json.dumps(payload_dict['request'])
    payload_dict['request'] = request_str
    resp = post_to_solr_feeder(payload_dict)
    if resp.status_code != 200:
        logger.error(
            'failed to delete names from solr for %s, status code: %i, error reason: %s, error details: %s',
            nr.nrNum, resp.status_code, resp.reason, resp.text)
Esempio n. 6
0
async def cb_subscription_handler(msg: nats.aio.client.Msg):
    """Use Callback to process Queue Msg objects.

    This is the callback handler that gets called when a message is placed on the queue.
    If an exception is thrown and not handled, the message is not marked as consumed
    on the queue. It eventually times out and another worker can grab it.

    In some cases we want to consume the message and capture our failure on Sentry
    to be handled manually by staff.
    """
    try:
        logger.info('Received raw message seq:%s, data=  %s', msg.sequence,
                    msg.data.decode())
        nr_state_change_msg = json.loads(msg.data.decode('utf-8'))
        logger.debug('Extracted nr event msg: %s', nr_state_change_msg)

        if is_processable(nr_state_change_msg):
            logger.debug('Begin process_nr_state_change for nr_event_msg: %s',
                         nr_state_change_msg)
            await process_names_event_message(nr_state_change_msg, FLASK_APP)
            logger.debug(
                'Completed process_nr_state_change for nr_event_msg: %s',
                nr_state_change_msg)
        else:
            # Skip processing of message as it isn't a message type this queue listener processes
            logger.debug(
                'Skipping processing of nr event message as message type is not supported: %s',
                nr_state_change_msg)
    except OperationalError as err:  # message goes back on the queue
        logger.error('Queue Blocked - Database Issue: %s',
                     json.dumps(nr_state_change_msg),
                     exc_info=True)
        raise err  # We don't want to handle the error, as a DB down would drain the queue
    except (RequestException,
            NewConnectionError) as err:  # message goes back on the queue
        logger.error('Queue Blocked - HTTP Connection Issue: %s',
                     json.dumps(nr_state_change_msg),
                     exc_info=True)
        raise err  # We don't want to handle the error, as a http connection error would drain the queue
    except (QueueException, KeyError, Exception):  # pylint: disable=broad-except # noqa B902
        # Catch Exception so that any error is still caught and the message is removed from the queue
        capture_message('Queue Error:' + json.dumps(nr_state_change_msg),
                        level='error')
        logger.error('Queue Error: %s',
                     json.dumps(nr_state_change_msg),
                     exc_info=True)
Esempio n. 7
0
async def run(loop, payload_values):  # pylint: disable=too-many-locals
    """Run the main application loop for the service.

    This runs the main top level service functions for working with the Queue.
    """
    # NATS client connections
    nc = NATS()
    sc = STAN()

    async def close():
        """Close the stream and nats connections."""
        await sc.close()
        await nc.close()

    # Connection and Queue configuration.
    def nats_connection_options():
        return {
            'servers': os.getenv('NATS_SERVERS', 'nats://127.0.0.1:4222').split(','),
            'io_loop': loop,
            'error_cb': error_cb,
            'name': os.getenv('NATS_CLIENT_NAME', 'namex.solr.names.updater.tester')
        }

    def stan_connection_options():
        return {
            'cluster_id': os.getenv('NATS_CLUSTER_ID', 'test-cluster'),
            'client_id': str(random.SystemRandom().getrandbits(0x58)),
            'nats': nc
        }

    def subscription_options():
        return {
            'subject': os.getenv('NATS_SUBJECT', 'namerequest.state'),
            'queue': os.getenv('NATS_QUEUE', 'namerequest-processor'),
            'durable_name': os.getenv('NATS_QUEUE', 'namerequest-processor') + '_durable'
        }

    try:
        # Connect to the NATS server, and then use that for the streaming connection.
        await nc.connect(**nats_connection_options())
        await sc.connect(**stan_connection_options())

        # register the signal handler
        for sig in ('SIGINT', 'SIGTERM'):
            loop.add_signal_handler(getattr(signal, sig),
                                    functools.partial(signal_handler, sig_loop=loop, sig_nc=nc, task=close)
                                    )

        msg_id = str(uuid.uuid4())
        nr_num = payload_values.get('nr_num')
        source = f'/requests/{nr_num}'

        payload = {
            'specversion': '1.0.1',
            'type': 'bc.registry.names.events',
            'source': source,
            'id': msg_id,
            'time': '',
            'datacontenttype': 'application/json',
            'identifier': nr_num,
            'data': {
                'request': {
                    'nrNum': nr_num,
                    'newState': payload_values.get('new_state'),
                    'previousState': payload_values.get('prev_state')
                }
            }
        }
        await sc.publish(subject=subscription_options().get('subject'),
                         payload=json.dumps(payload).encode('utf-8'))

    except Exception as e:  # pylint: disable=broad-except
        # TODO tighten this error and decide when to bail on the infinite reconnect
        logger.error(e)
Esempio n. 8
0
                    
                    await furnish_receipt_message(qsm, payment)
                    
                else:
                    logger.debug('Queue Error: Unable to find payment record for :%s', pay_msg)
                    capture_message(f'Queue Error: Unable to find payment record for :{pay_msg}', level='error')
            else:
                logger.debug('Queue Error: Missing id :%s', pay_msg)
                capture_message(f'Queue Error: Missing id :{pay_msg}', level='error')

            return

        # if we're here and haven't been able to action it,
        # then we've received an unknown token
        # Capture it to the log and remove it rom the queue
        logger.error('Unknown payment token given: %s', payment_token)
        capture_message(
            f'Queue Error: Unknown paymentToken:{payment_token}',
            level='error')


qsm = QueueServiceManager()  # pylint: disable=invalid-name
APP_CONFIG = config.get_named_config(os.getenv('DEPLOYMENT_ENV', 'production'))
FLASK_APP = Flask(__name__)
FLASK_APP.config.from_object(APP_CONFIG)
db.init_app(FLASK_APP)


async def cb_subscription_handler(msg: nats.aio.client.Msg):
    """Use Callback to process Queue Msg objects.
Esempio n. 9
0
async def process_payment(pay_msg: dict, flask_app: Flask):
    """Render the payment status."""
    if not flask_app or not pay_msg:
        raise QueueException('Flask App or token not available.')

    with flask_app.app_context():
        logger.debug('entering process payment: %s', pay_msg)

        # capture_message(f'Queue Issue: Unable to find payment.id={payment_id} to place on email queue')
        # return

        if pay_msg.get(
                'paymentToken',
            {}).get('statusCode') == PaymentState.TRANSACTION_FAILED.value:
            # TODO: The customer has cancelled out of paying, so we could note this better
            # technically the payment for this service is still pending
            logger.debug('Failed transaction on queue:%s', pay_msg)
            return

        complete_payment_status = [
            PaymentState.COMPLETED.value, PaymentState.APPROVED.value
        ]
        if pay_msg.get('paymentToken',
                       {}).get('statusCode') in complete_payment_status:  # pylint: disable=R1702
            logger.debug('COMPLETED transaction on queue: %s', pay_msg)

            if payment_token := pay_msg.get('paymentToken', {}).get('id'):
                if payment := Payment.find_by_payment_token(payment_token):

                    if update_payment := await update_payment_record(payment):
                        payment = update_payment
                        # record event
                        nr = RequestDAO.find_by_id(payment.nrId)
                        # TODO: create a namex_pay user for this
                        user = User.find_by_username(
                            'name_request_service_account')
                        EventRecorder.record(
                            user, Event.NAMEX_PAY +
                            f' [payment completed] { payment.payment_action }',
                            nr, nr.json())
                        # try to update NRO otherwise send a sentry msg for OPS
                        if payment.payment_action in \
                                [payment.PaymentActions.UPGRADE.value, payment.PaymentActions.REAPPLY.value]:
                            change_flags = {
                                'is_changed__request': True,
                                'is_changed__previous_request': False,
                                'is_changed__applicant': False,
                                'is_changed__address': False,
                                'is_changed__name1': False,
                                'is_changed__name2': False,
                                'is_changed__name3': False,
                                'is_changed__nwpta_ab': False,
                                'is_changed__nwpta_sk': False,
                                'is_changed__request_state': False,
                                'is_changed_consent': False
                            }
                            warnings = nro.change_nr(nr, change_flags)
                            if warnings:
                                logger.error(
                                    'Queue Error: Unable to update NRO :%s',
                                    warnings)
                                capture_message(
                                    f'Queue Error: Unable to update NRO for {nr} {payment.payment_action} :{warnings}',
                                    level='error')

                    await furnish_receipt_message(qsm, payment)

                else:
Esempio n. 10
0
        try:
            await self.probe.start()
            await self.service.connect()

            # register the signal handler
            for sig in ('SIGINT', 'SIGTERM'):
                loop.add_signal_handler(
                    getattr(signal, sig),
                    functools.partial(signal_handler,
                                      sig_loop=loop,
                                      task=self.close))

        except Exception as e:  # pylint: disable=broad-except # noqa B902
            # TODO tighten this error and decide when to bail on the infinite reconnect
            logger.error(e)


if __name__ == '__main__':
    try:
        event_loop = asyncio.get_event_loop()
        event_loop.run_until_complete(run(event_loop))
        event_loop.run_forever()
    except Exception as err:  # pylint: disable=broad-except; Catching all errors from the frameworks
        logger.error('problem in running the service: %s',
                     err,
                     stack_info=True,
                     exc_info=True)
    finally:
        event_loop.close()
Esempio n. 11
0
async def run(loop, token):  # pylint: disable=too-many-locals
    """Run the main application loop for the service.

    This runs the main top level service functions for working with the Queue.
    """
    # NATS client connections
    nc = NATS()
    sc = STAN()

    async def close():
        """Close the stream and nats connections."""
        await sc.close()
        await nc.close()

    # Connection and Queue configuration.
    def nats_connection_options():
        return {
            'servers':
            os.getenv('NATS_SERVERS', 'nats://127.0.0.1:4222').split(','),
            'io_loop':
            loop,
            'error_cb':
            error_cb,
            'name':
            os.getenv('NATS_CLIENT_NAME', 'entity.filing.tester')
        }

    def stan_connection_options():
        return {
            'cluster_id': os.getenv('NATS_CLUSTER_ID', 'test-cluster'),
            'client_id': str(random.SystemRandom().getrandbits(0x58)),
            'nats': nc
        }

    def subscription_options():
        return {
            'subject': os.getenv('NATS_SUBJECT', 'entity.filings'),
            'queue': os.getenv('NATS_QUEUE', 'filing-worker'),
            'durable_name':
            os.getenv('NATS_QUEUE', 'filing-worker') + '_durable'
        }

    try:
        # Connect to the NATS server, and then use that for the streaming connection.
        await nc.connect(**nats_connection_options())
        await sc.connect(**stan_connection_options())

        # register the signal handler
        for sig in ('SIGINT', 'SIGTERM'):
            loop.add_signal_handler(
                getattr(signal, sig),
                functools.partial(signal_handler,
                                  sig_loop=loop,
                                  sig_nc=nc,
                                  task=close))

        payload = {'paymentToken': {'id': token, 'statusCode': 'COMPLETED'}}
        await sc.publish(subject=subscription_options().get('subject'),
                         payload=json.dumps(payload).encode('utf-8'))

    except Exception as e:  # pylint: disable=broad-except
        # TODO tighten this error and decide when to bail on the infinite reconnect
        logger.error(e)