"""Main module for account creation.""" import csv import json import sys import time import uuid from datetime import datetime import boto3 from src import config from src import constants from src.kafka_producer import kafka_producer GDA_ACCOUNT_CREATION_EXECUTION_STATUS_TABLE_NAME = \ f'{config.ENV.lower()}_gda-account-creation-success' # Optional per-message on_delivery handler (triggered by poll() or flush()) # when a message has been successfully delivered or # permanently failed delivery (after retries). def acked(err, msg): """Success/fail delivery of message.""" global delivered_records if err is not None: print(f'Failed to deliver message: {err}') sys.exit(-1) else: delivered_records += 1 print( f'Produced record to topic {msg.topic()} partition ' f'[{msg.partition()}] @ offset {msg.offset()}') def _produce_message(record_data, record_key, topic): kafka_producer.produce( topic, record_key, record_data, acked, ) def backfill_from_csv(csv_file_name, topic): """Backfill from a csv file.""" with open(csv_file_name, encoding='utf8') as csv_file: csv_reader = csv.DictReader(csv_file) column_names = csv_reader.fieldnames print('reading file ', csv_file_name) if set(column_names) != set(constants.BACKFILL_CSV_HEADERS): print( f'Csv headers do not match requirements. ' f'Expected {constants.BACKFILL_CSV_HEADERS} ' f'but received {column_names} instead.') sys.exit(-1) prevent_high_concurrency_counter = 0 for row in csv_reader: if prevent_high_concurrency_counter == 5: # sleep to prevent high lambda concurrency # after every 5 messages produced time.sleep(int(config.BACKFILL_THROTTLE_DELAY)) prevent_high_concurrency_counter = 0 row['send_email'] = True if row['send_email'] == 'TRUE' else False if topic == constants.SOURCE['awal_distro']: row.update({ 'currency': None, 'vendor_name': None, 'service_tier_uuid': None, 'roles': [] if not row['roles'] else [ x.strip() for x in row['roles'].split(',')] }) elif topic == constants.SOURCE['neighbouring_rights']: del row['roles'] row.update({ 'currency': None, 'vendor_name': None, 'service_tier_uuid': None, 'owner': config.OWNER, }) data = json.dumps(row) _produce_message(data, row['correlation_id'], topic) prevent_high_concurrency_counter += 1 def get_statuses_for_correlation_ids(csv_file_name): """Get statuses for correlation ids from DynamoDB.""" dynamodb_table = boto3.resource( 'dynamodb', region_name='us-east-1').Table( GDA_ACCOUNT_CREATION_EXECUTION_STATUS_TABLE_NAME) missing_correlation_ids = [] successful_correlation_ids = [] with open(csv_file_name, 'r') as f: reader = csv.DictReader(f) for row in reader: correlation_id = row['correlation_id'] response = dynamodb_table.get_item( Key={ 'correlation_id': correlation_id } ) try: successful_correlation_ids.append( response.get('Item').get('correlation_id')) print(f'correlation_id: {correlation_id} marked as success') except AttributeError: missing_correlation_ids.append(correlation_id) print(f'correlation_id: {correlation_id} not found in ' f'success table. Adding to retry list.') return { 'Failed CI': missing_correlation_ids, 'Successful CI': successful_correlation_ids } def save_to_next_retry_csv(failed_correlation_ids, attempt_number): """Save failed correlation ids to csv file.""" initial_csv_file_name = 'host_folder/backfill.csv' retry_csv_file_name = ( f'host_folder/backfill_retry_{attempt_number + 1}.csv') failed_correlation_ids = set(failed_correlation_ids) with open(initial_csv_file_name, 'r') as initial_file: with open(retry_csv_file_name, 'w') as attempt_file: reader = csv.DictReader(initial_file) writer = csv.DictWriter( attempt_file, constants.BACKFILL_CSV_HEADERS) writer.writeheader() for row in reader: if row['correlation_id'] in failed_correlation_ids: writer.writerow(row) if __name__ == '__main__': topic = constants.SOURCE[config.CUSTOMER_TYPE] delivered_records = 0 now = datetime.now() if config.BACKFILL_CSV == 'true': # 3 attempts to backfill initial_csv_file_name = 'host_folder/backfill.csv' for attempt_number in range(0, 3): if attempt_number == 0: csv_file_name = initial_csv_file_name else: csv_file_name = ( f'host_folder/backfill_retry_{attempt_number}.csv') print('Attempting backfill with csv file ', csv_file_name) backfill_from_csv(csv_file_name, topic) # wait for records to be processed time.sleep(int(config.BACKFILL_CHECK_DELAY)) statuses = get_statuses_for_correlation_ids(csv_file_name) if not statuses['Failed CI']: # if no failed records, exit print( 'All records from the backfill.csv ' 'were successfully processed.') break save_to_next_retry_csv(statuses['Failed CI'], attempt_number) if attempt_number == 2: print( 'Failed to backfill all records from ' 'the initial file. Exiting...') print('Failed CIs: {}'.format(','.join(statuses['Failed CI']))) # TODO: upload the final file with failed CIs to S3? sys.exit(-1) else: record_key = str(uuid.uuid4()) # TODO: review is_admin/master_contact logic for new account creation. data = { 'correlation_id': record_key, 'vendor_name': config.ACCOUNT_NAME, 'customer_type': config.CUSTOMER_TYPE, 'first_name': config.FIRST_NAME, 'last_name': config.LAST_NAME, 'email': config.EMAIL, 'source': topic, 'owner': config.OWNER, 'currency': config.PAYMENT_CURRENCY, 'service_tier_uuid': constants.SERVICE_TIER_UUID[config.SERVICE_TIER], 'vendor_id': None, 'is_admin': 'TRUE', 'master_contact': 'TRUE', 'send_email': True} if topic == constants.SOURCE['awal_distro']: data.update({ 'roles': []}) _produce_message(json.dumps(data), record_key, topic) print(f'{delivered_records} messages (including retries) ' f'were produced to topic {topic}')