"""sme_analytics_ingestion_flow_control.""" from datetime import datetime from time import sleep import config from constants import constants from constants import queries from ddex_ingester_common.constants.ddex_providers import ( SME_ANALYTICS_PROVIDER, ) from ddex_ingester_common.helpers.rds import run_rds_query import sfn logger = config.get_current_logger(config.app_logger) def handler(event, context): """Begin main script execution function.""" logger.info( f'Beginning ingestion flow control. Event: {event} Context: {context}') # The maximum number of state machines to have running at the same time max_sfn_count = event.get( 'MAX_STATE_MACHINES') or constants.MAX_STATE_MACHINES # noqa # The delay between each Lambda batch invocation sfn_start_delay = event.get('SFN_START_DELAY') or constants.SFN_START_DELAY # The number of executions to start for each sfn_start_delay sfn_batch_size = event.get('SFN_BATCH_SIZE') or constants.SFN_BATCH_SIZE process_confidential = event.get( 'PROCESS_CONFIDENTIAL') or constants.PROCESS_CONFIDENTIAL logger.info( f'MAX_STATE_MACHINES={max_sfn_count} ' f'SFN_START_DELAY={sfn_start_delay} ' f'SFN_BATCH_SIZE={sfn_batch_size}' f'PROCESS_CONFIDENTIAL={process_confidential}' ) # process only non-confidential products ingestion = [False] if process_confidential: logger.info( 'Processing both non-confidential and confidential products' ) ingestion = [True, False] for confidential_ingestion in ingestion: # We need to make sure that the (sfn_start_delay * max_sfn_count) time # is lower than the time between each flow control run to avoid having # more than one flow control run at a time # # Get the count of current executions current_sfn_count = sfn.get_full_execution_count_by_arn( config.SME_ANALYTICS_SFN_ARN, constants.EXECUTION_RUNNING_STATUS ) # Check if you can run more State machines (too many executions) nr_ingests = max_sfn_count - current_sfn_count if nr_ingests < 1: logger.info( f'Cannot run any more state machines. Currently running ' f'{current_sfn_count} with a limit of {max_sfn_count}' ) return logger.info( f'Currently running {current_sfn_count} executions with a limit of' f' {max_sfn_count}. Starting at most {nr_ingests} executions' ) # Get the name of current executions current_exec_name_list = [] # sfn.get_full_execution_name_list_by_arn( # config.SME_ANALYTICS_SFN_ARN, # constants.EXECUTION_RUNNING_STATUS # ) rows_to_ingest = get_ddex_delivery_to_ingest(nr_ingests, confidential_ingestion) if not rows_to_ingest: logger.info( f'No deliveries to ingest. Ending ingestion flow control ' f'for confidential_ingestion = {confidential_ingestion}.') continue for i, row in enumerate(rows_to_ingest): bucket = row.get('s3_bucket_name') key = row.get('s3_key_name') artwork_ingestion_only = row.get( 'ingestion_type') == constants.ARTWORK_INGESTION_ONLY_INGESTION if confidential_ingestion and not artwork_ingestion_only: check_is_confidential_project(row, logger) logger.info( f'Starting execution of ARN {config.SME_ANALYTICS_SFN_ARN}, ' f'with Bucket {bucket} and Key {key}' ) sfn.start_execution_by_arn( config.SME_ANALYTICS_SFN_ARN, bucket, key, SME_ANALYTICS_PROVIDER, row.get('ddex_delivery_to_ingest_id'), artwork_ingestion_only ) logger.info(f'Updating row status for key {key}') run_rds_query( logger, config.RDS_HOST, config.RDS_DB_NAME, config.RDS_RW_USER, config.RDS_PASSWORD, queries.UPDATE_ROW_STATUS, row.get('ddex_delivery_to_ingest_id'), ) if (i + 1) % sfn_batch_size == 0: # Sleeping to let sfn batch start. logger.info(f'Sleeping for {sfn_start_delay} milliseconds.') sleep(sfn_start_delay / 1000) # Get the count of current executions again after starting the new ones full_exec_name_list = sfn.get_full_execution_name_list_by_arn( config.SME_ANALYTICS_SFN_ARN, constants.EXECUTION_RUNNING_STATUS ) # Get the names of all the newly started executions new_exec_name_list = [ x for x in full_exec_name_list if x not in current_exec_name_list] for name in new_exec_name_list: logger.info(f'Execution {name} was started.') process_purged_releases(max_sfn_count, sfn_batch_size, sfn_start_delay) logger.info('End of ingestion flow control.') def get_ddex_delivery_to_ingest(nr_ingests, confidential_ingestion): """Get list of ddex delivers.""" query = _get_query(confidential_ingestion) rows_to_ingest = run_rds_query( logger, config.RDS_HOST, config.RDS_DB_NAME, config.RDS_RW_USER, config.RDS_PASSWORD, query, None ) return rows_to_ingest[:nr_ingests] def get_purged_ddex_delivery_to_ingest(nr_ingests): """Get list of purged ddex delivers.""" rows_to_ingest = run_rds_query( logger, config.RDS_HOST, config.RDS_DB_NAME, config.RDS_RW_USER, config.RDS_PASSWORD, queries.PURGED_DDEX_DELIVERIES, None ) return rows_to_ingest[:nr_ingests] def check_is_confidential_project(ddex_ingest, logger): """Doublecheck if processing for confidential product is started correctly. Args: ddex_ingest: mysql ddex_ingester.ddex_delivery_to_ingest row logger: """ if ddex_ingest.get('is_confidential') != 1: return original_release_datetime = ddex_ingest.get('original_release_datetime') if original_release_datetime > datetime.utcnow(): massage = f'Attempt to process confidential product ' \ f'BEFORE earliest_datetime , now = {datetime.utcnow()} ' \ f'original_release_datetime = {original_release_datetime} ' \ f's3 Bucket: {ddex_ingest.get("bucket")}, ' \ f's3 Key : {ddex_ingest.get("s3_key_name")}' logger.error( massage ) raise Exception(massage) def _get_query(confidential): if confidential: return queries.CONFIDENTIAL_DDEX_DELIVERIES else: return queries.NON_CONFIDENTIAL_DDEX_DELIVERIES def process_purged_releases(max_sfn_count, sfn_batch_size, sfn_start_delay): """Process purged releases.""" current_sfn_count = sfn.get_full_execution_count_by_arn( config.SME_ANALYTICS_SFN_ARN, constants.EXECUTION_RUNNING_STATUS ) # Check if you can run more State machines (too many executions) nr_ingests = max_sfn_count - current_sfn_count if nr_ingests < 1: logger.info( f'Cannot run any more state machines. Currently running ' f'{current_sfn_count} with a limit of {max_sfn_count}' ) return logger.info( f'Currently running {current_sfn_count} executions with a limit of' f' {max_sfn_count}. Starting at most {nr_ingests} executions' ) purged_ddex = get_purged_ddex_delivery_to_ingest(nr_ingests) if not purged_ddex: logger.info( 'No purged files to process. Ending ingestion flow control ' ) return for i, row in enumerate(purged_ddex): bucket = row.get('s3_bucket_name') key = row.get('s3_key_name') artwork_ingestion_only = False logger.info( f'Starting execution of ARN {config.SME_ANALYTICS_SFN_ARN}, ' f'with Bucket {bucket} and Key {key}' ) sfn.start_execution_by_arn( config.SME_ANALYTICS_SFN_ARN, bucket, key, SME_ANALYTICS_PROVIDER, row.get('ddex_delivery_to_ingest_id'), artwork_ingestion_only ) logger.info(f'Updating row status for key {key}') run_rds_query( logger, config.RDS_HOST, config.RDS_DB_NAME, config.RDS_RW_USER, config.RDS_PASSWORD, queries.UPDATE_ROW_STATUS, row.get('ddex_delivery_to_ingest_id'), ) if (i + 1) % sfn_batch_size == 0: # Sleeping to let sfn batch start. logger.info(f'Sleeping for {sfn_start_delay} milliseconds.') sleep(sfn_start_delay / 1000)