コード例 #1
0
ファイル: loader.py プロジェクト: thread/routemaster
def _load_gate(path: Path, yaml_state: Yaml, feed_names: List[str]) -> Gate:
    yaml_exit_condition = yaml_state['exit_condition']

    if yaml_exit_condition is True:
        str_exit_condition = 'true'
    elif yaml_exit_condition is False:
        str_exit_condition = 'false'
    else:
        str_exit_condition = str(yaml_exit_condition).strip()

    exit_condition = ExitConditionProgram(str_exit_condition)
    _validate_context_lookups(
        path + ['exit_condition'],
        exit_condition.accessed_variables(),
        feed_names,
    )

    return Gate(
        name=yaml_state['gate'],
        exit_condition=exit_condition,
        triggers=[
            _load_trigger(path + ['triggers', str(idx)], yaml_trigger)
            for idx, yaml_trigger in enumerate(yaml_state.get('triggers', []))
        ],
        next_states=_load_next_states(
            path + ['next'],
            yaml_state.get('next'),
            feed_names,
        ),
    )
コード例 #2
0
ファイル: test_validation.py プロジェクト: thread/routemaster
def test_nonexistent_node_destination_invalid(app):
    state_machine = StateMachine(
        name='example',
        feeds=[],
        webhooks=[],
        states=[
            Gate(
                name='start',
                triggers=[],
                next_states=ContextNextStates(
                    path='foo.bar',
                    destinations=[
                        ContextNextStatesOption(
                            state='nonexistent',
                            value='1',
                        ),
                        ContextNextStatesOption(
                            state='end',
                            value='2',
                        ),
                    ],
                    default='end',
                ),
                exit_condition=ExitConditionProgram('false'),
            ),
            Gate(
                name='end',
                triggers=[],
                next_states=NoNextStates(),
                exit_condition=ExitConditionProgram('false'),
            ),
        ],
    )
    with pytest.raises(ValidationError):
        _validate_state_machine(app, state_machine)
コード例 #3
0
ファイル: test_validation.py プロジェクト: thread/routemaster
def test_non_unique_states_invalid(app):
    state_machine = StateMachine(
        name='example',
        feeds=[],
        webhooks=[],
        states=[
            Gate(
                name='start',
                triggers=[],
                next_states=ConstantNextState('gate1'),
                exit_condition=ExitConditionProgram('false'),
            ),
            Gate(
                name='gate1',
                triggers=[],
                next_states=ConstantNextState('start'),
                exit_condition=ExitConditionProgram('false'),
            ),
            Gate(
                name='gate1',
                triggers=[],
                next_states=NoNextStates(),
                exit_condition=ExitConditionProgram('false'),
            ),
        ],
    )
    with pytest.raises(ValidationError):
        _validate_state_machine(app, state_machine)
コード例 #4
0
def test_evaluate(program, expected, variables, make_context):
    program = ExitConditionProgram(program)
    context = make_context(
        label='label1',
        metadata=VARIABLES,
        now=NOW,
        current_history_entry=HISTORY_ENTRY,
        accessed_variables=program.accessed_variables(),
    )
    assert program.run(context) == expected
コード例 #5
0
def test_errors(source, error, make_context):
    with pytest.raises(ValueError) as compile_error:
        program = ExitConditionProgram(source)
        context = make_context(
            label='label1',
            metadata=VARIABLES,
            now=NOW,
            current_history_entry=HISTORY_ENTRY,
            accessed_variables=program.accessed_variables(),
        )
        program.run(context)

    message = str(compile_error.value)

    assert textwrap.dedent(message).strip() == textwrap.dedent(error).strip()
コード例 #6
0
ファイル: test_cron.py プロジェクト: thread/routemaster
def test_gate_at_fixed_time_with_specific_timezone(custom_app):
    gate = Gate(
        'fixed_time_gate',
        next_states=NoNextStates(),
        exit_condition=ExitConditionProgram('false'),
        triggers=[TimezoneAwareTrigger(
            datetime.time(12, 1),
            timezone='Europe/London',
        )],
    )
    app = create_app(custom_app, [gate])

    def processor(*, state, **kwargs):
        assert state == gate
        processor.called = True

    processor.called = False

    scheduler = schedule.Scheduler()
    configure_schedule(app, scheduler, processor)

    assert len(scheduler.jobs) == 1, "Should have scheduled a single job"
    job, = scheduler.jobs

    assert job.next_run == datetime.datetime(2018, 1, 1, 12, 1)
    assert processor.called is False

    with freezegun.freeze_time(job.next_run):
        job.run()

    assert processor.called is True
    assert job.next_run == datetime.datetime(2018, 1, 1, 12, 2)
コード例 #7
0
ファイル: test_cron.py プロジェクト: thread/routemaster
def test_cron_job_gracefully_exit_signalling(custom_app):
    gate = Gate(
        'gate',
        next_states=NoNextStates(),
        exit_condition=ExitConditionProgram('false'),
        triggers=[SystemTimeTrigger(datetime.time(12, 0))],
    )
    app = create_app(custom_app, [gate])
    state_machine = app.config.state_machines['test_machine']

    items_to_process = ['one', 'two', 'should_not_process']

    def is_terminating():
        return len(items_to_process) == 1

    def processor(app, state, state_machine, label):
        for item in items_to_process:
            items_to_process.pop(0)

    with mock.patch(
        'routemaster.state_machine.api.get_current_state',
        return_value=gate,
    ), mock.patch('routemaster.state_machine.api.lock_label'):
        process_job(
            app=app,
            is_terminating=is_terminating,
            fn=processor,
            label_provider=lambda x, y, z: items_to_process,
            state=gate,
            state_machine=state_machine,
        )

    assert items_to_process == ['should_not_process']
コード例 #8
0
ファイル: test_cron.py プロジェクト: thread/routemaster
def test_gate_metadata_retry(custom_app):
    gate = Gate(
        'fixed_time_gate',
        next_states=NoNextStates(),
        exit_condition=ExitConditionProgram('false'),
        triggers=[MetadataTrigger(metadata_path='foo.bar')],
    )
    app = create_app(custom_app, [gate])

    def processor(*, state, **kwargs):
        assert state == gate
        processor.called = True

    processor.called = False

    scheduler = schedule.Scheduler()
    configure_schedule(app, scheduler, processor)

    assert len(scheduler.jobs) == 1, "Should have scheduled a single job"
    job, = scheduler.jobs

    assert job.next_run == datetime.datetime(2018, 1, 1, 12, 1)
    assert processor.called is False

    with freezegun.freeze_time(job.next_run):
        job.run()

    assert processor.called is True
    assert job.next_run == datetime.datetime(2018, 1, 1, 12, 2)
コード例 #9
0
ファイル: test_loading.py プロジェクト: thread/routemaster
def test_trivial_config():
    data = yaml_data('trivial')
    expected = Config(
        state_machines={
            'example':
            StateMachine(
                name='example',
                feeds=[],
                webhooks=[],
                states=[
                    Gate(
                        name='start',
                        triggers=[],
                        next_states=NoNextStates(),
                        exit_condition=ExitConditionProgram('false'),
                    ),
                ],
            ),
        },
        database=DatabaseConfig(
            host='localhost',
            port=5432,
            name='routemaster',
            username='******',
            password='',
        ),
        logging_plugins=[],
    )
    with reset_environment():
        assert load_config(data) == expected
コード例 #10
0
ファイル: test_validation.py プロジェクト: thread/routemaster
def test_disconnected_state_machine_invalid(app):
    state_machine = StateMachine(
        name='example',
        feeds=[],
        webhooks=[],
        states=[
            Gate(
                name='start',
                triggers=[],
                next_states=NoNextStates(),
                exit_condition=ExitConditionProgram('false'),
            ),
            Gate(
                name='end',
                triggers=[],
                next_states=NoNextStates(),
                exit_condition=ExitConditionProgram('false'),
            ),
        ],
    )
    with pytest.raises(ValidationError):
        _validate_state_machine(app, state_machine)
コード例 #11
0
ファイル: test_validation.py プロジェクト: thread/routemaster
def test_valid(app):
    _validate_state_machine(
        app,
        StateMachine(
            name='example',
            feeds=[],
            webhooks=[],
            states=[
                Gate(
                    name='start',
                    triggers=[],
                    next_states=ConstantNextState('end'),
                    exit_condition=ExitConditionProgram('false'),
                ),
                Gate(
                    name='end',
                    triggers=[],
                    next_states=NoNextStates(),
                    exit_condition=ExitConditionProgram('false'),
                ),
            ],
        ))
コード例 #12
0
def test_get_current_state_for_label_in_invalid_state(custom_app,
                                                      create_label):
    state_to_be_removed = Gate(
        name='start',
        triggers=[],
        next_states=ConstantNextState('end'),
        exit_condition=ExitConditionProgram('false'),
    )
    end_state = Gate(
        name='end',
        triggers=[],
        next_states=NoNextStates(),
        exit_condition=ExitConditionProgram('false'),
    )

    app = custom_app(
        state_machines={
            'test_machine':
            StateMachine(
                name='test_machine',
                states=[state_to_be_removed, end_state],
                feeds=[],
                webhooks=[],
            ),
        })

    label = create_label('foo', 'test_machine', {})
    state_machine = app.config.state_machines['test_machine']

    # Remove the state which we expect the label to be in from the state
    # machine; this is logically equivalent to loading a new config which does
    # not have the state
    del state_machine.states[0]

    with app.new_session():
        with pytest.raises(Exception):
            utils.get_current_state(app, label, state_machine)
コード例 #13
0
ファイル: test_validation.py プロジェクト: thread/routemaster
def test_label_in_deleted_state_invalid(app, create_label):
    create_label('foo', 'test_machine', {})  # Created in "start" implicitly
    state_machine = StateMachine(
        name='test_machine',
        feeds=[],
        webhooks=[],
        states=[
            # Note: state "start" from "test_machine" is gone.
            Gate(
                name='end',
                triggers=[],
                next_states=NoNextStates(),
                exit_condition=ExitConditionProgram('false'),
            ),
        ],
    )
    with pytest.raises(ValidationError):
        _validate_state_machine(app, state_machine)
コード例 #14
0
def test_feeds_for_state_machine():
    state_machine = StateMachine(
        name='example',
        feeds=[
            FeedConfig(name='test_feed', url='http://localhost/<label>'),
        ],
        webhooks=[],
        states=[
            Gate(
                name='start',
                triggers=[],
                next_states=NoNextStates(),
                exit_condition=ExitConditionProgram('false'),
            ),
        ],
    )

    feeds = feeds_for_state_machine(state_machine)

    assert 'test_feed' in feeds
    assert feeds['test_feed'].data is None
    assert feeds['test_feed'].url == 'http://localhost/<label>'
    assert feeds['test_feed'].state_machine == 'example'
コード例 #15
0
ファイル: test_validation.py プロジェクト: thread/routemaster
def test_label_in_deleted_state_on_per_state_machine_basis(
    app,
    create_label,
):
    create_label('foo', 'test_machine', {})  # Created in "start" implicitly
    state_machine = StateMachine(
        name='other_machine',
        feeds=[],
        webhooks=[],
        states=[
            # Note: state "start" is not present, but that we're in a different
            # state machine.
            Gate(
                name='end',
                triggers=[],
                next_states=NoNextStates(),
                exit_condition=ExitConditionProgram('false'),
            ),
        ],
    )

    # Should not care about our label as it is in a different state machine.
    _validate_state_machine(app, state_machine)
コード例 #16
0
ファイル: test_cron.py プロジェクト: thread/routemaster
def test_cron_job_does_not_forward_exceptions(custom_app):
    gate = Gate(
        'gate',
        next_states=NoNextStates(),
        exit_condition=ExitConditionProgram('false'),
        triggers=[SystemTimeTrigger(datetime.time(12, 0))],
    )
    app = create_app(custom_app, [gate])
    state_machine = app.config.state_machines['test_machine']

    # To get aroung inability to change reference in this scope from
    # `raise_value_error`.
    raised = {'raised': False}

    def raise_value_error(*args):
        raised['raised'] = True
        raise ValueError()

    def processor(*args, **kwargs):
        pass

    with mock.patch(
        'routemaster.state_machine.api.get_current_state',
        return_value=gate,
    ), mock.patch('routemaster.state_machine.api.lock_label'):
        process_job(
            app=app,
            is_terminating=raise_value_error,
            fn=processor,
            label_provider=lambda x, y, z: [1],
            state=gate,
            state_machine=state_machine,
        )

    assert raised['raised'], \
        "Test did not trigger exception correctly in cron system"
コード例 #17
0
ファイル: test_loading.py プロジェクト: thread/routemaster
def test_realistic_config():
    data = yaml_data('realistic')
    expected = Config(
        state_machines={
            'example':
            StateMachine(
                name='example',
                feeds=[
                    FeedConfig(name='data_feed',
                               url='http://localhost/<label>'),
                ],
                webhooks=[
                    Webhook(
                        match=re.compile('.+\\.example\\.com'),
                        headers={
                            'x-api-key':
                            'Rahfew7eed1ierae0moa2sho3ieB1et3ohhum0Ei',
                        },
                    ),
                ],
                states=[
                    Gate(
                        name='start',
                        triggers=[
                            SystemTimeTrigger(time=datetime.time(18, 30)),
                            TimezoneAwareTrigger(
                                time=datetime.time(12, 25),
                                timezone='Europe/London',
                            ),
                            MetadataTimezoneAwareTrigger(
                                time=datetime.time(13, 37),
                                timezone_metadata_path=['timezone'],
                            ),
                            MetadataTrigger(metadata_path='foo.bar'),
                            IntervalTrigger(
                                interval=datetime.timedelta(hours=1), ),
                            OnEntryTrigger(),
                        ],
                        next_states=ConstantNextState(state='stage2'),
                        exit_condition=ExitConditionProgram('true'),
                    ),
                    Gate(
                        name='stage2',
                        triggers=[],
                        next_states=ContextNextStates(
                            path='metadata.foo.bar',
                            destinations=[
                                ContextNextStatesOption(
                                    state='stage3',
                                    value='1',
                                ),
                                ContextNextStatesOption(
                                    state='stage3',
                                    value='2',
                                ),
                            ],
                            default='end',
                        ),
                        exit_condition=ExitConditionProgram(
                            'metadata.foo.bar is defined', ),
                    ),
                    Action(
                        name='stage3',
                        webhook='https://localhost/hook',
                        next_states=ConstantNextState(state='end'),
                    ),
                    Gate(
                        name='end',
                        triggers=[],
                        exit_condition=ExitConditionProgram('false'),
                        next_states=NoNextStates(),
                    ),
                ],
            ),
        },
        database=DatabaseConfig(
            host='localhost',
            port=5432,
            name='routemaster',
            username='******',
            password='',
        ),
        logging_plugins=[
            LoggingPluginConfig(
                dotted_path='routemaster_prometheus:PrometheusLogger',
                kwargs={'prometheus_gateway': 'localhost'},
            ),
            LoggingPluginConfig(
                dotted_path='routemaster_sentry:SentryLogger',
                kwargs={'raven_dsn': 'nai8ioca4zeeb2ahgh4V'},
            ),
        ],
    )
    with reset_environment():
        assert load_config(data) == expected
コード例 #18
0
def test_accessed_variables(program, expected, variables):
    program = ExitConditionProgram(program)
    assert sorted(program.accessed_variables()) == sorted(variables)