""" SWF Utilities ============= Utility methods for Amazon simple workflow """ import datetime import json import time from processing_accounting.util import logging from processing_accounting.util import setting RE_RUN_TAGS = ['re-run'] def wait_for_workflow_to_complete(workflow_executions, max_wait_time=-1): """Wait for workflows to finish. @todo(pkuong): add custom exception modules building list of custom exception classes. Args: workflow_executions (list): list of workflow execution objects. max_wait_time (int): optional wait time. If exceeded, it will throw a timeout exception. Default is -1 for waiting forever. Raise: Exception: workflow ends unexpectedly or wait time has exceeded. """ elapsed_time = 0 interval = setting.CHECK_WORKFLOW_STATUS_TIME_INTERVAL while True: all_status = [] for workflow_execution in workflow_executions: execution_info = workflow_execution.describe().get( 'executionInfo') status = execution_info.get('closeStatus') workflow_id = execution_info.get('execution').get('workflowId') logging.logger.info( 'Waiting workflows {} to be completed....'.format( workflow_id)) all_status.append(status == 'COMPLETED') # If any workflow has an invalid state, stop the whole thing. if status in ['CANCELED', 'FAILED', 'TERMINATED']: raise Exception('Workflow ends unexpectedly.') if all(all_status): return # If max_wait_time is set, throw an exception if wait time exceeds. if elapsed_time > max_wait_time > -1: raise Exception('Wait time has exceeded.') time.sleep(interval) elapsed_time += interval def get_swf(session): """Get SWF client. Args: session (boto3.session.Session): AWS session Returns: botocore.client.SWF: AWS SWF client """ return session.client('swf') def get_latest_failed_executions( domain, latest_date, session, days_range=1, **extra): """Get the list of failed workflow executions. Args: domain (str): SWF domain latest_date (datetime.datetime): the latest date to filter days_range (int): num days to go back (latest_date - days_range) session (boto3.session.Session): AWS session object extra (dict): extra keyword arguments for swf.list_closed_workflow_executions function Returns: list: list with execution metadata (dict) """ swf = get_swf(session) oldest_date = latest_date - datetime.timedelta(days=days_range) main_args = { 'domain': domain, 'startTimeFilter': { 'oldestDate': oldest_date, 'latestDate': latest_date}, 'closeStatusFilter': {'status': 'FAILED'} } list_kwargs = {} list_kwargs.update(extra) list_kwargs.update(main_args) next_page_token = True failed_executions = [] while next_page_token: resp = swf.list_closed_workflow_executions(**list_kwargs) failed_executions.extend(resp.get('executionInfos')) next_page_token = resp.get('nextPageToken') list_kwargs['nextPageToken'] = next_page_token return failed_executions def get_execution_history(domain, execution, session): """Get execution history (events, input, etc). Args: domain (str): SWF domain execution (dict): execution details (workflowId, runId) session (boto3.session.Session): AWS session Returns: dict: execution history (list of events) """ swf = get_swf(session) resp = swf.get_workflow_execution_history( domain=domain, execution=execution) return resp def get_execution_input(execution_history): """Get workflow execution input (context). Args: execution_history (dict): SWF get workflow execution history response Returns: dict: workflow execution input """ events = execution_history.get('events') if not events: return {} initial_event = events[0] event_attributes = initial_event.get( 'workflowExecutionStartedEventAttributes') if not event_attributes: return {} workflow_input = json.loads(event_attributes['input']) return workflow_input def get_execution_description(execution, domain_name, session): """Get workflow execution description. Args: execution (dict): execution - workflowId and runId domain_name (str): execution domain session (boto3.session.Session): AWS session Returns: dict: AWS response """ swf = get_swf(session) resp = swf.describe_workflow_execution( domain=domain_name, execution=execution) return resp def re_run_execution(execution, execution_input, domain_name, session): """Re-run workflow execution Args: execution (dict): execution metadata execution_input (dict): execution input domain_name (str): execution domain session (boto3.session.Session): AWS session Returns: dict: SWF start execution response """ swf = get_swf(session) execution_description = get_execution_description( execution=execution['execution'], domain_name=domain_name, session=session) execution_configuration = execution_description.get( 'executionConfiguration', {}) task_list = execution_configuration.get('taskList') start_to_close_timeout = execution_configuration.get( 'taskStartToCloseTimeout') execution_start_to_close_timeout = execution_configuration.get( 'executionStartToCloseTimeout') child_policy = execution_configuration.get('childPolicy', 'TERMINATE') try: resp = swf.start_workflow_execution( domain=domain_name, workflowId=execution['execution']['workflowId'], workflowType=execution['workflowType'], input=json.dumps(execution_input), taskList=task_list, taskStartToCloseTimeout=start_to_close_timeout, executionStartToCloseTimeout=execution_start_to_close_timeout, childPolicy=child_policy, tagList=RE_RUN_TAGS) except swf.exceptions.ClientError as e: resp = { 'ResponseMetadata': { 'HTTPStatusCode': 304, 'Message': str(e)}} return resp