예제 #1
0
def test_actions_do_not_advance_state_on_fail(app, create_label, mock_webhook,
                                              assert_history):
    state_machine = app.config.state_machines['test_machine']

    # First get the label into the action state by failing the automatic
    # progression through the machine.
    with mock_webhook(WebhookResult.FAIL):
        create_label('foo', state_machine.name, {'should_progress': True})

    with mock_webhook(WebhookResult.FAIL) as webhook:
        process_cron(
            process_action,
            functools.partial(labels_in_state, app),
            app,
            state_machine,
            state_machine.states[1],
        )

    webhook.assert_called_once_with(
        'http://localhost/hook/test_machine/foo',
        'application/json',
        b'{"label": "foo", "metadata": {"should_progress": true}}',
        mock.ANY,
        mock.ANY,
    )

    assert_history([
        (None, 'start'),
        ('start', 'perform_action'),
    ])
예제 #2
0
def process_job(
    *,
    # Bound at the cron thread level
    is_terminating: IsTerminating,
    # Bound at the state scheduling level
    app: App,
    state: State,
    state_machine: StateMachine,
    # Bound when scheduling a specific job for a state
    fn: LabelStateProcessor,
    label_provider: LabelProvider,
):
    """Process a single instance of a single cron job."""

    def _iter_labels_until_terminating(
        state_machine: StateMachine,
        state: State,
    ) -> Iterable[str]:
        return itertools.takewhile(
            lambda _: not is_terminating(),
            label_provider(app, state_machine, state),
        )

    try:
        with app.logger.process_cron(state_machine, state, fn.__name__):
            process_cron(
                process=fn,
                get_labels=_iter_labels_until_terminating,
                app=app,
                state=state,
                state_machine=state_machine,
            )
    except Exception:  # noqa: B902
        return
예제 #3
0
def test_action_retry_trigger_continues_as_far_as_possible(
        app, create_label, mock_webhook, assert_history):
    state_machine = app.config.state_machines['test_machine']

    # First get the label into the action state by failing the automatic
    # progression through the machine.
    with mock_webhook(WebhookResult.FAIL):
        label = create_label(
            'foo',
            state_machine.name,
            {'should_progress': True},
        )

    # Now attempt to process the retry of the action, succeeding.
    with mock_webhook(WebhookResult.SUCCESS) as webhook:
        with mock.patch('routemaster.state_machine.api.process_transitions',
                        ) as mock_process_transitions:
            process_cron(
                process_action,
                functools.partial(labels_in_state, app),
                app,
                state_machine,
                state_machine.states[1],
            )

    mock_process_transitions.assert_called_once_with(
        app,
        label,
    )

    webhook.assert_called_once_with(
        'http://localhost/hook/test_machine/foo',
        'application/json',
        b'{"label": "foo", "metadata": {"should_progress": true}}',
        mock.ANY,
        mock.ANY,
    )

    assert_history([
        (None, 'start'),
        ('start', 'perform_action'),
        ('perform_action', 'end'),
    ])
예제 #4
0
def test_actions_do_not_advance_if_label_in_wrong_state(
        app, create_label, mock_webhook, assert_history):
    state_machine = app.config.state_machines['test_machine']

    # Set should_progress=False so that the label will remain in start.
    label = create_label('foo', state_machine.name, {'should_progress': False})

    with mock_webhook(WebhookResult.SUCCESS) as webhook:
        process_cron(
            process_action,
            # Return our target label for processing even though it shouldn't
            # be here. Simulates changes to labels where we aren't expecting.
            lambda x, y: [label.name],
            app,
            state_machine,
            state_machine.states[1],
        )

    webhook.assert_not_called()
    assert_history([
        (None, 'start'),
    ])
예제 #5
0
def test_process_action_fails_retry_works(app, create_label, mock_webhook,
                                          assert_history):
    state_machine = app.config.state_machines['test_machine']
    action = state_machine.states[1]

    # First get the label into the action state by failing the automatic
    # progression through the machine.
    with mock_webhook(WebhookResult.FAIL):
        create_label(
            'foo',
            state_machine.name,
            {'should_progress': True},
        )

    # State machine should not have progressed
    assert_history([
        (None, 'start'),
        ('start', 'perform_action'),
    ])

    # Now retry with succeeding webhook
    with mock_webhook(WebhookResult.SUCCESS) as webhook:
        process_cron(
            process_action,
            functools.partial(labels_in_state, app),
            app,
            state_machine,
            action,
        )
        webhook.assert_called_once()

    assert_history([
        (None, 'start'),
        ('start', 'perform_action'),
        ('perform_action', 'end'),
    ])
예제 #6
0
def test_handles_label_state_change_race_condition(app, create_deleted_label):
    test_machine = app.config.state_machines['test_machine']
    state = test_machine.states[1]

    # Create a label which is not in the expected state. Doing this and then
    # returning the affected label from the `get_labels` call is easier and
    # equivalent to having the state of the label change between the return of
    # that call and when the label is used.
    label = create_deleted_label('foo', 'test_machine')

    mock_processor = mock.Mock()
    mock_get_labels = mock.Mock(return_value=[label.name])

    with mock.patch('routemaster.state_machine.api.suppress_exceptions', ):
        state_machine.process_cron(
            mock_processor,
            mock_get_labels,
            app,
            test_machine,
            state,
        )

    # Assert no attempt to process the label
    mock_processor.assert_not_called()
예제 #7
0
def test_actions_retries_use_same_idempotency_token(app, create_label,
                                                    mock_webhook,
                                                    assert_history):
    state_machine = app.config.state_machines['test_machine']

    expected = {'token': None}

    def persist_token(url, content_type, data, token, logger):
        expected['token'] = token
        return WebhookResult.FAIL

    # First get the label into the action state by failing the automatic
    # progression through the machine.
    with mock.patch(
            'routemaster.webhooks.RequestsWebhookRunner.__call__',
            side_effect=persist_token,
    ) as webhook:
        create_label('foo', state_machine.name, {'should_progress': True})

    assert expected['token'] is not None

    webhook.assert_called_once_with(
        'http://localhost/hook/test_machine/foo',
        'application/json',
        b'{"label": "foo", "metadata": {"should_progress": true}}',
        expected['token'],
        mock.ANY,
    )

    with mock_webhook(WebhookResult.FAIL) as webhook:
        process_cron(
            process_action,
            functools.partial(labels_in_state, app),
            app,
            state_machine,
            state_machine.states[1],
        )

    webhook.assert_called_once_with(
        'http://localhost/hook/test_machine/foo',
        'application/json',
        b'{"label": "foo", "metadata": {"should_progress": true}}',
        expected['token'],
        mock.ANY,
    )

    with mock_webhook(WebhookResult.SUCCESS) as webhook:
        process_cron(
            process_action,
            functools.partial(labels_in_state, app),
            app,
            state_machine,
            state_machine.states[1],
        )

    webhook.assert_called_once_with(
        'http://localhost/hook/test_machine/foo',
        'application/json',
        b'{"label": "foo", "metadata": {"should_progress": true}}',
        expected['token'],
        mock.ANY,
    )