""" Snowflake aggregate load. To load data from Snowflake into dynamodb, the workflow needs to perform several operations: boost the throughput of the dynamodb table, unload data from snowflake to s3 down operations (bring back the dynamodb throughput to its normal level.) Params: config (str): name of the configuration to use for those tasks to complete. start_date (str): the aggregation start date. end_date (str): the aggregation end date. """ from garcon import activity from garcon import runner from garcon.contrib.aws import dynamodb from garcon.contrib.aws import s3 from job import config from job import environment from job import snowflake from job import tasks import raven class SnowflakeUnload: """Class representing a SWF workflow.""" def __init__(self): """Init flow.""" self.domain = 'prod_sf_aggregate_load' self.name = self.domain if environment.name == environment.DEV: self.domain = 'dev' self.name = 'sf_aggregate_load' self.version = '3.0' self.create = activity.create( self.domain, self.name, version=self.version, on_exception=self.on_exception) def on_exception(self, actor, exception): """Capture an exception that has occurred in the application. Args: actor (ActivityWorker, DeciderWorker): the actor that has received the exception. exception (Exception): the exception to capture. """ if config.sentry_dsn: client = raven.Client(dsn=config.sentry_dsn) client.captureException() def workflow_id(self, context): """Generate the workflow id. Args: context (dict): initial context of the workflow. """ return '%s-%s-%s' % ( context.get('config'), context.get('start_date'), context.get('end_date')) def decider(self, schedule, context=None): """Flow decider. Arg: schedule (callable): Call the scheduler. context (dict): Execution context. """ bootstrap = schedule( 'bootstrap', self.bootstrap_activity) unload_snowflake = schedule( 'unload_snowflake', self.unload_snowflake_activity, requires=[bootstrap]) setup_dynamodb = schedule( 'dynamodb', self.setup_dynamodb_activity, requires=[unload_snowflake]) process = schedule( 'process_data', self.process_activity, requires=[setup_dynamodb]) tasks = ( self.cleanup_s3_activity, self.cleanup_dynamodb_activity) for task in tasks: schedule( 'cleanup-{}'.format(task.name), task, requires=[process]) @property def bootstrap_activity(self): """Bootstrap the configuration. Get the initial values from the context and create the flow. """ return self.create( name='bootstrap_configuration', tasks=runner.Sync( tasks.bootstrap)) @property def setup_dynamodb_activity(self): """Setup dynamodb activity. First check if the table is already being updated by another process, and if not: update the throughput. """ return self.create( name='setup_dynamodb', retry=5, tasks=runner.Sync( # Sometimes the table is being updated by another process, we # need to wait until the update clears. dynamodb.wait_for_table_task_completion.fill( table_name='dynamodb.table_name'), dynamodb.set_table_throughput.fill( table_name='dynamodb.table_name', throughput_write='dynamodb.throughput_write', throughput_read='dynamodb.throughput_read'), dynamodb.wait_for_table_task_completion.fill( table_name='dynamodb.table_name'))) @property def unload_snowflake_activity(self): """Unload activity.""" return self.create( name='unload_snowflake', retry=2, tasks=runner.Sync( snowflake.run_unload.fill( dest='snowflake.dest', query='snowflake.query'))) @property def process_activity(self): """Process the data.""" return self.create( name='process', retry=2, tasks=runner.Sync( tasks.generate_load_files.fill( source='snowflake.dest', destination='dynamodb.dest'), tasks.wait_until_empty_s3_path.fill( s3_path='dynamodb.dest'))) @property def cleanup_s3_activity(self): """Remove unused files.""" return self.create( name='cleanup_s3', retry=5, tasks=runner.Async( s3.remove_files_from_path.fill( path='snowflake.dest'))) @property def cleanup_dynamodb_activity(self): """Cleanup dynamodb throughput.""" return self.create( name='cleanup_dynamodb', retry=2, tasks=runner.Async( dynamodb.set_table_throughput.fill( table_name='dynamodb.table_name', throughput_write='dynamodb.default_throughput_write', throughput_read='dynamodb.default_throughput_read')))