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)
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'))
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)
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
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)
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)
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)
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.
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:
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()
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)