Esempio n. 1
0
 async def test():
     tmpl = TEMPLATES['task_timeout']
     wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
     wflow.run({'initial': 'data'})
     # The workflow is OK
     await wflow
     self.assertEqual(FutureState.get(wflow), FutureState.finished)
     # The task has timed out
     task = wflow._tasks_by_id.get('1')
     with self.assertRaises(asyncio.CancelledError):
         task.exception()
     self.assertEqual(FutureState.get(task), FutureState.timeout)
Esempio n. 2
0
 async def test():
     tmpl = TEMPLATES['task_timeout']
     wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
     wflow.run({'initial': 'data'})
     # The workflow is OK
     await wflow
     self.assertEqual(FutureState.get(wflow), FutureState.finished)
     # The task has timed out
     task = wflow._tasks_by_id.get('1')
     with self.assertRaises(asyncio.CancelledError):
         task.exception()
     self.assertEqual(FutureState.get(task), FutureState.timeout)
Esempio n. 3
0
 async def test():
     tmpl = TEMPLATES['ok']
     wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
     wflow.run({'initial': 'data'})
     await wflow
     # These tasks have finished
     for tid in tmpl['graph'].keys():
         task = wflow._tasks_by_id.get(tid)
         self.assertTrue(task.done())
         self.assertEqual(FutureState.get(task), FutureState.finished)
     # The workflow finished properly
     self.assertTrue(wflow.done())
     self.assertEqual(FutureState.get(wflow), FutureState.finished)
Esempio n. 4
0
 async def test():
     tmpl = TEMPLATES['ok']
     wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
     wflow.run({'initial': 'data'})
     await wflow
     # These tasks have finished
     for tid in tmpl['graph'].keys():
         task = wflow._tasks_by_id.get(tid)
         self.assertTrue(task.done())
         self.assertEqual(FutureState.get(task), FutureState.finished)
     # The workflow finished properly
     self.assertTrue(wflow.done())
     self.assertEqual(FutureState.get(wflow), FutureState.finished)
Esempio n. 5
0
 def report(self):
     """
     Creates and returns a complete execution report, including workflow and
     tasks templates and execution details.
     """
     report = self._template.as_dict()
     report['exec'] = {
         'id': self.uid,
         'start': self._start,
         'end': self._end,
         'state': FutureState.get(self).value
     }
     # Update task descriptions to add info about their execution.
     for task_dict in report['tasks']:
         try:
             task = self._tasks_by_id[task_dict['id']]
         except KeyError:
             task_dict['exec'] = None
             continue
         task_dict['exec'] = task.as_dict()
         # If the task is linked to a task holder, try to use its own report
         if hasattr(task.holder, 'report'):
             try:
                 task_dict['exec']['reporting'] = task.holder.report()
             except Exception as exc:
                 # Unexpected error from task reporting.
                 log.error('Exception on task reporting: %s', exc)
                 self._internal_exc = exc
                 self._try_mark_done()
     return report
Esempio n. 6
0
 def report(self):
     """
     Creates and returns a complete execution report, including workflow and
     tasks templates and execution details.
     """
     report = self._template.as_dict()
     report['exec'] = {
         'id': self.uid,
         'start': self._start,
         'end': self._end,
         'state': FutureState.get(self).value
     }
     # Update task descriptions to add info about their execution.
     for task_dict in report['tasks']:
         try:
             task = self._tasks_by_id[task_dict['id']]
         except KeyError:
             task_dict['exec'] = None
             continue
         task_dict['exec'] = task.as_dict()
         # If the task is linked to a task holder, try to use its own report
         try:
             task_dict['exec']['reporting'] = task.holder.report()
         except AttributeError:
             pass
     return report
Esempio n. 7
0
 async def test():
     tmpl = TEMPLATES['workflow_cancel']
     wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
     wflow.run({'initial': 'data'})
     # Workflow is cancelled
     with self.assertRaises(asyncio.CancelledError):
         await wflow
     self.assertEqual(FutureState.get(wflow), FutureState.cancelled)
     # This task was cancelled
     task = wflow._tasks_by_id.get('cancel')
     with self.assertRaises(asyncio.CancelledError):
         task.exception()
     self.assertEqual(FutureState.get(task), FutureState.cancelled)
     # These tasks were never started
     for tid in ('2', '3', '4'):
         task = wflow._tasks_by_id.get(tid)
         self.assertIs(task, None)
Esempio n. 8
0
 async def test():
     tmpl = TEMPLATES['workflow_cancel']
     wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
     wflow.run({'initial': 'data'})
     # Workflow is cancelled
     with self.assertRaises(asyncio.CancelledError):
         await wflow
     self.assertEqual(FutureState.get(wflow), FutureState.cancelled)
     # This task was cancelled
     task = wflow._tasks_by_id.get('cancel')
     with self.assertRaises(asyncio.CancelledError):
         task.exception()
     self.assertEqual(FutureState.get(task), FutureState.cancelled)
     # These tasks were never started
     for tid in ('2', '3', '4'):
         task = wflow._tasks_by_id.get(tid)
         self.assertIs(task, None)
Esempio n. 9
0
        def browse(entry, parent=None):
            """
            Dive into the dag to:
                - ensure executed task contexts are restored.
                - find tasks that need to be executed.
            """
            t_template = next(
                t for t in report['tasks'] if t['id'] == entry.uid
            )
            t_report = t_template.get('exec')
            if not t_report:
                if not parent:
                    raise RescueError(self.uid, 'root task never been started')
                # No execution report found, the task needs to be executed
                resume.append((entry, None, parent))
                return

            t_state = FutureState(t_report['state'])
            if not t_state.done():
                # Pending, suspended or cancelled tasks need to be executed
                resume.append((entry, t_template, parent))
                return

            if t_state.done():
                # We won't manually create an asyncio task since it won't be
                # run (the task is 'done'). Yet, we need a memory print of its
                # execution (mainly for reporting purposes).
                t_shadow = type('TukioTaskShadow', (object,), {
                    'done': lambda: True,
                    'cancelled': lambda: False,
                    '_exception': None,
                    'as_dict': lambda: t_report
                })

                # Add this task to the tracking sets
                self.tasks.add(t_shadow)
                self._done_tasks.add(t_shadow)
                self._tasks_by_id[entry.uid] = t_shadow

                # Recursive browing
                for t_next in self._template.dag.successors(entry):
                    browse(t_next, t_template)
Esempio n. 10
0
        def browse(entry, parent=None):
            """
            Dive into the dag to:
                - ensure executed task contexts are restored.
                - find tasks that need to be executed.
            """
            t_template = next(t for t in report['tasks']
                              if t['id'] == entry.uid)
            t_report = t_template.get('exec')
            if not t_report:
                if not parent:
                    raise RescueError(self.uid, 'root task never been started')
                # No execution report found, the task needs to be executed
                resume.append((entry, None, parent))
                return

            t_state = FutureState(t_report['state'])
            if not t_state.done():
                # Pending, suspended or cancelled tasks need to be executed
                resume.append((entry, t_template, parent))
                return

            if t_state.done():
                # We won't manually create an asyncio task since it won't be
                # run (the task is 'done'). Yet, we need a memory print of its
                # execution (mainly for reporting purposes).
                t_shadow = type(
                    'TukioTaskShadow', (object, ), {
                        'done': lambda: True,
                        'cancelled': lambda: False,
                        '_exception': None,
                        'as_dict': lambda: t_report
                    })

                # Add this task to the tracking sets
                self.tasks.add(t_shadow)
                self._done_tasks.add(t_shadow)
                self._tasks_by_id[entry.uid] = t_shadow

                # Recursive browing
                for t_next in self._template.dag.successors(entry):
                    browse(t_next, t_template)
Esempio n. 11
0
 def as_dict(self):
     """
     Returns the execution informations of this task.
     """
     return {
         'id': self.uid,
         'start': self._start,
         'end': self._end,
         'state': FutureState.get(self).value,
         'inputs': self._inputs,
         'outputs': self._outputs
     }
Esempio n. 12
0
 def as_dict(self):
     """
     Returns the execution informations of this task.
     """
     return {
         'id': self.uid,
         'start': self._start,
         'end': self._end,
         'state': FutureState.get(self).value,
         'inputs': self._inputs,
         'outputs': self._outputs
     }
Esempio n. 13
0
        async def test():
            tmpl = TEMPLATES['crash_test']
            # Test crash at task __init__
            wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
            wflow.run({'initial': 'data'})
            await wflow
            # These tasks have finished
            for tid in ('1', '2'):
                task = wflow._tasks_by_id.get(tid)
                self.assertTrue(task.done())
                self.assertEqual(FutureState.get(task), FutureState.finished)
            # These tasks were never started
            for tid in ('crash', 'wont_run'):
                task = wflow._tasks_by_id.get(tid)
                self.assertIs(task, None)
            # The workflow finished properly
            self.assertTrue(wflow.done())
            self.assertEqual(FutureState.get(wflow), FutureState.finished)

            # Test crash inside a task
            tmpl['tasks'][0]['config'] = {'init_ok': None}
            wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
            wflow.run({'initial': 'data'})
            await wflow
            # These tasks have finished
            for tid in ('1', '2'):
                task = wflow._tasks_by_id.get(tid)
                self.assertTrue(task.done())
                self.assertEqual(FutureState.get(task), FutureState.finished)
            # This task crashed during execution
            task = wflow._tasks_by_id.get('crash')
            self.assertTrue(task.done())
            self.assertEqual(FutureState.get(task), FutureState.exception)
            # This task was never started
            task = wflow._tasks_by_id.get('wont_run')
            self.assertIs(task, None)
            # The workflow finished properly
            self.assertTrue(wflow.done())
            self.assertEqual(FutureState.get(wflow), FutureState.finished)
Esempio n. 14
0
        async def test():
            tmpl = TEMPLATES['crash_test']
            # Test crash at task __init__
            wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
            wflow.run({'initial': 'data'})
            await wflow
            # These tasks have finished
            for tid in ('1', '2'):
                task = wflow._tasks_by_id.get(tid)
                self.assertTrue(task.done())
                self.assertEqual(FutureState.get(task), FutureState.finished)
            # These tasks were never started
            for tid in ('crash', 'wont_run'):
                task = wflow._tasks_by_id.get(tid)
                self.assertIs(task, None)
            # The workflow finished properly
            self.assertTrue(wflow.done())
            self.assertEqual(FutureState.get(wflow), FutureState.finished)

            # Test crash inside a task
            tmpl['tasks'][0]['config'] = {'init_ok': None}
            wflow = Workflow(WorkflowTemplate.from_dict(tmpl))
            wflow.run({'initial': 'data'})
            await wflow
            # These tasks have finished
            for tid in ('1', '2'):
                task = wflow._tasks_by_id.get(tid)
                self.assertTrue(task.done())
                self.assertEqual(FutureState.get(task), FutureState.finished)
            # This task crashed during execution
            task = wflow._tasks_by_id.get('crash')
            self.assertTrue(task.done())
            self.assertEqual(FutureState.get(task), FutureState.exception)
            # This task was never started
            task = wflow._tasks_by_id.get('wont_run')
            self.assertIs(task, None)
            # The workflow finished properly
            self.assertTrue(wflow.done())
            self.assertEqual(FutureState.get(wflow), FutureState.finished)
Esempio n. 15
0
    def resume(self):
        """
        Resume the execution of this workflow allows new tasks to start.
        """
        if self._committed.is_set():
            log.error('Cannot resume a workflow that has not been suspended')
            return
        self._committed.set()

        # Next tasks are done waiting for '_committed' asyncio event
        # The 'suspended' ones needs to be re-executed
        for task in self._done_tasks:
            if FutureState.get(task) is not FutureState.suspended:
                continue
            event = Event(task.inputs, source=task.event_source)
            self._new_task(task.template, event)

        self._dispatch_exec_event(WorkflowExecState.resume)
        log.info('workflow %s has been resumed', self)
Esempio n. 16
0
    def resume(self):
        """
        Resume the execution of this workflow allows new tasks to start.
        """
        if self._committed.is_set():
            log.error('Cannot resume a workflow that has not been suspended')
            return
        self._committed.set()

        # Next tasks are done waiting for '_committed' asyncio event
        # The 'suspended' ones needs to be re-executed
        for task in self._done_tasks:
            if FutureState.get(task) is not FutureState.suspended:
                continue
            event = Event(task.inputs, source=task.event_source)
            self._new_task(task.template, event)

        self._dispatch_exec_event(WorkflowExecState.RESUME)
        log.info('workflow %s has been resumed', self)
Esempio n. 17
0
    async def get(self, request):
        """
        Filters:
            * `root` return only the root workflows
            * `full` return the full graph and details of all workflows
                * :warning: can be a huge amount of data
            * `since` return the workflows since this date
            * `state` return the workflows on this FutureState
            * `offset` return the worflows from this offset
            * `limit` return this amount of workflows
            * `order` order results following the Ordering enum values
            * `search` search templates with specific title
        """
        # Filter on start date
        since = request.GET.get('since')
        if since:
            try:
                since = from_isoformat(since)
            except ValueError:
                return Response(
                    status=400,
                    body={'error': "Could not parse date '{}'".format(since)})
        # Filter on state value
        state = request.GET.get('state')
        if state:
            try:
                state = FutureState(state)
            except ValueError:
                return Response(
                    status=400,
                    body={'error': "Unknown state '{}'".format(state)})
        # Skip first items
        offset = request.GET.get('offset')
        if offset:
            try:
                offset = int(offset)
            except ValueError:
                return Response(status=400,
                                body={'error': 'Offset must be an int'})
        # Limit max result
        limit = request.GET.get('limit')
        if limit:
            try:
                limit = int(limit)
            except ValueError:
                return Response(status=400,
                                body={'error': 'Limit must be an int'})
        order = request.GET.get('ordering')
        if order:
            try:
                order = Ordering[order].value
            except KeyError:
                return Response(status=400,
                                body={
                                    'error':
                                    'Ordering must be in {}'.format(
                                        Ordering.keys())
                                })

        try:
            count, history = await self.nyuki.storage.instances.get(
                root=(request.GET.get('root') == '1'),
                full=(request.GET.get('full') == '1'),
                search=request.GET.get('search'),
                order=order,
                offset=offset,
                limit=limit,
                since=since,
                state=state,
            )
        except AutoReconnect:
            return Response(status=503)

        data = {'count': count, 'data': history}
        return Response(data)