def test_schedule_with_completed_activity(monkeypatch): """Test the scheduling of an activity. """ mock(monkeypatch) from tests.fixtures.flows import example monkeypatch.setattr(decider, 'schedule_activity_task', MagicMock()) decisions = MagicMock() schedule_context = decider.ScheduleContext() instance_state = activity.ActivityState('activity_1') instance_state.add_state(activity.ACTIVITY_COMPLETED) current_activity = example.activity_1 history = { current_activity.name: { 'workflow_name_activity_1-1-schedule_id': instance_state } } resp = decider.schedule(decisions, schedule_context, history, {}, 'schedule_id', current_activity) assert not decider.schedule_activity_task.called assert resp.get_last_state() == activity.ACTIVITY_COMPLETED assert schedule_context.completed resp.result.get('foo')
def test_schedule_requires_with_incomplete_activities(): """Test the scheduler. """ activity_state = activity.ActivityState('activity_name') with pytest.raises(activity.ActivityInstanceNotReadyException): decider.ensure_requirements([activity_state]) with pytest.raises(activity.ActivityInstanceNotReadyException): decider.ensure_requirements([None]) activity_state.add_state(activity.ACTIVITY_COMPLETED) decider.ensure_requirements(requires=[activity_state])
def test_activity_state(): """Test the creation of the activity state. """ activity_id = 'id' state = activity.ActivityState(activity_id) assert state.activity_id is activity_id assert not state.get_last_state() state.add_state(activity.ACTIVITY_FAILED) state.add_state(activity.ACTIVITY_COMPLETED) assert len(state.states) == 2 assert state.get_last_state() is activity.ACTIVITY_COMPLETED result = 'foobar' state.set_result(result) assert state.result == result with pytest.raises(Exception): state.set_result('shouldnt reset') assert state.result == result
def schedule(decisions, schedule_context, history, context, schedule_id, current_activity, requires=None, input=None, version='1.0'): """Schedule an activity. Scheduling an activity requires all the requirements to be completed (all activities should be marked as completed). The scheduler also mixes the input with the full execution context to send the data to the activity. Args: decisions (Layer1Decisions): the layer decision for swf. schedule_context (dict): information about the schedule. history (dict): history of the execution. context (dict): context of the execution. schedule_id (str): the id of the activity to schedule. current_activity (Activity): the activity to run. requires (list): list of all requirements. input (dict): additional input for the context. Throws: ActivityInstanceNotReadyException: if one of the activity in the requirements is not ready. Return: State: the state of the schedule (contains the response). """ ensure_requirements(requires) activity_completed = set() result = dict() instance_context = dict() instance_context.update(context or {}) instance_context.update(input or {}) for current in current_activity.instances(instance_context): current_id = '{}-{}'.format(current.id, schedule_id) states = history.get(current.activity_name, {}).get(current_id) if states: if states.get_last_state() == activity.ACTIVITY_COMPLETED: result.update(states.result or dict()) activity_completed.add(True) continue activity_completed.add(False) schedule_context.mark_uncompleted() if states.get_last_state() != activity.ACTIVITY_FAILED: continue elif (not current.retry or current.retry < activity.count_activity_failures(states)): raise Exception( 'The activity failures has exceeded its retry limit.') activity_completed.add(False) schedule_context.mark_uncompleted() schedule_activity_task(decisions, current, id=current_id, version=version) state = activity.ActivityState(current_activity.name) state.add_state(activity.ACTIVITY_SCHEDULED) if len(activity_completed) == 1 and True in activity_completed: state.add_state(activity.ACTIVITY_COMPLETED) state.set_result(result) return state
def activity_states_from_events(events): """Get activity states from a list of events. The workflow events contains the different states of our activities. This method consumes the logs, and regenerates a dictionnary with the list of all the activities and their states. Note: Please note: from the list of events, only activities that have been registered are accessible. For all the others that have not yet started, they won't be part of this list. Args: events (dict): list of all the events. Return: `dict`: the activities and their state. """ events = sorted(events, key=lambda item: item.get('eventId')) event_id_info = dict() activity_events = dict() for event in events: event_id = event.get('eventId') event_type = event.get('eventType') if event_type == 'ActivityTaskScheduled': activity_info = event.get('activityTaskScheduledEventAttributes') activity_id = activity_info.get('activityId') activity_name = activity_info.get('activityType').get('name') event_id_info.update({ event_id: { 'activity_name': activity_name, 'activity_id': activity_id } }) activity_events.setdefault(activity_name, {}).setdefault( activity_id, activity.ActivityState(activity_id)).add_state( activity.ACTIVITY_SCHEDULED) elif event_type == 'ActivityTaskFailed': activity_info = event.get('activityTaskFailedEventAttributes') activity_event = event_id_info.get( activity_info.get('scheduledEventId')) activity_id = activity_event.get('activity_id') activity_events.setdefault( activity_event.get('activity_name'), {}).setdefault(activity_id, activity.ActivityState(activity_id)).add_state( activity.ACTIVITY_FAILED) elif event_type == 'ActivityTaskCompleted': activity_info = event.get('activityTaskCompletedEventAttributes') activity_event = event_id_info.get( activity_info.get('scheduledEventId')) activity_id = activity_event.get('activity_id') activity_events.setdefault( activity_event.get('activity_name'), {}).setdefault(activity_id, activity.ActivityState(activity_id)).add_state( activity.ACTIVITY_COMPLETED) result = json.loads(activity_info.get('result') or '{}') activity_events.get(activity_event.get('activity_name')).get( activity_id).set_result(result) return activity_events