def test_maintains_updated_field_on_label(app, mock_test_feed):
    label = LabelRef('foo', 'test_machine')

    with mock_test_feed(), app.new_session():
        state_machine.create_label(
            app,
            label,
            {},
        )

        first_updated = app.session.query(Label.updated, ).filter_by(
            name=label.name,
            state_machine=label.state_machine,
        ).scalar()

    with mock_test_feed(), app.new_session():
        state_machine.update_metadata_for_label(
            app,
            label,
            {'foo': 'bar'},
        )

        second_updated = app.session.query(Label.updated, ).filter_by(
            name=label.name,
            state_machine=label.state_machine,
        ).scalar()

    assert second_updated > first_updated
def test_labels_needing_metadata_update_retry_in_gate(app, mock_test_feed,
                                                      create_label,
                                                      create_deleted_label,
                                                      current_state):
    label_unprocessed = create_label('label_unprocessed', 'test_machine', {})
    label_processed = create_label('label_processed', 'test_machine', {})
    label_deleted = create_deleted_label('label_deleted', 'test_machine')

    test_machine = app.config.state_machines['test_machine']
    gate = test_machine.states[0]

    with mock_test_feed(), app.new_session():
        with mock.patch(
                'routemaster.context.Context._pre_warm_feeds',
                side_effect=RequestException,
        ):
            state_machine.update_metadata_for_label(
                app,
                label_unprocessed,
                {'should_progress': True},
            )

    # Both should be in the start state...
    assert current_state(label_processed) == 'start'
    assert current_state(label_unprocessed) == 'start'
    assert current_state(label_deleted) is None

    # But only label_unprocessed should be pending a metadata update
    with app.new_session():
        assert utils.labels_needing_metadata_update_retry_in_gate(
            app,
            test_machine,
            gate,
        ) == [label_unprocessed.name]
def test_metadata_update_gate_evaluations_dont_race_processing_subsequent_metadata_triggered_gate(
        create_label, app, assert_history):
    test_machine_2 = app.config.state_machines['test_machine_2']
    gate_1 = test_machine_2.states[0]
    gate_2 = test_machine_2.states[1]

    label = create_label('foo', 'test_machine_2', {})

    with mock.patch(
            'routemaster.state_machine.api.needs_gate_evaluation_for_metadata_change',
            return_value=(True, gate_1),
    ), mock.patch(
            'routemaster.state_machine.api.get_current_state',
            return_value=gate_2,
    ), app.new_session():

        state_machine.update_metadata_for_label(
            app,
            label,
            {'should_progress': True},
        )

    # We should have no history entry 1->2 (as we mocked out the current state)
    # so the state machine should have considered us up-to-date and not moved.
    assert_history([
        (None, 'gate_1'),
    ])
def test_concurrent_metadata_update_gate_evaluations_dont_race(
        create_label, app, assert_history, current_state):
    test_machine_2 = app.config.state_machines['test_machine_2']
    gate_1 = test_machine_2.states[0]

    label = create_label('foo', 'test_machine_2', {})

    with app.new_session():
        state_machine.update_metadata_for_label(
            app,
            label,
            {'should_progress': True},
        )

    assert current_state(label) == 'gate_2'

    with mock.patch(
            'routemaster.state_machine.api.needs_gate_evaluation_for_metadata_change',
            return_value=(True, gate_1),
    ), app.new_session():
        state_machine.update_metadata_for_label(
            app,
            label,
            {'should_progress': True},
        )

    assert_history([
        (None, 'gate_1'),
        ('gate_1', 'gate_2'),
    ])
def test_stays_in_gate_if_gate_processing_fails(app, mock_test_feed,
                                                current_state):
    label = LabelRef('foo', 'test_machine')

    with mock_test_feed(), app.new_session():
        state_machine.create_label(
            app,
            label,
            {},
        )

    assert current_state(label) == 'start'

    with mock_test_feed(), mock.patch(
            'routemaster.context.Context._pre_warm_feeds',
            side_effect=RequestException,
    ), app.new_session():
        state_machine.update_metadata_for_label(
            app,
            label,
            {'should_progress': True},
        )

    assert metadata_triggers_processed(app, label) is False
    assert current_state(label) == 'start'
def test_state_machine_simple(app, mock_test_feed):
    label = LabelRef('foo', 'test_machine')

    with mock_test_feed(), app.new_session():
        state_machine.create_label(
            app,
            label,
            {},
        )
        state_machine.update_metadata_for_label(
            app,
            label,
            {'foo': 'bar'},
        )

    with app.new_session():
        assert state_machine.get_label_metadata(app, label) == {'foo': 'bar'}
def test_metadata_update_gate_evaluations_dont_process_subsequent_metadata_triggered_gate(
        create_label, app, assert_history, current_state):
    label = create_label('foo', 'test_machine_2', {})

    with app.new_session():
        state_machine.update_metadata_for_label(
            app,
            label,
            {'should_progress': True},
        )

    assert current_state(label) == 'gate_2'
    assert_history([
        (None, 'gate_1'),
        ('gate_1', 'gate_2'),
        # Note: has not progressed to end because there is no on entry trigger
        # on gate 2 and we were not processing a metadata trigger on gate 2,
        # only gate 1.
    ])
def test_state_machine_does_not_progress_when_not_eligible(
        app, mock_test_feed, current_state):
    label = LabelRef('foo', 'test_machine')

    with mock_test_feed(), app.new_session():
        state_machine.create_label(
            app,
            label,
            {},
        )

    assert current_state(label) == 'start'

    with mock_test_feed(), app.new_session():
        state_machine.update_metadata_for_label(
            app,
            label,
            {'should_progress': False},
        )

    assert current_state(label) == 'start'
def test_state_machine_progresses_on_update(app, mock_webhook, mock_test_feed,
                                            current_state):
    label = LabelRef('foo', 'test_machine')

    with mock_test_feed(), app.new_session():
        state_machine.create_label(
            app,
            label,
            {},
        )

    assert current_state(label) == 'start'

    with mock_webhook() as webhook, mock_test_feed(), app.new_session():
        state_machine.update_metadata_for_label(
            app,
            label,
            {'should_progress': True},
        )
        webhook.assert_called_once()

    assert metadata_triggers_processed(app, label) is True
    assert current_state(label) == 'end'
Beispiel #10
0
def update_label(state_machine_name, label_name):
    """
    Update a label in a state machine.

    Triggering progression if necessary according to the state machine
    configuration. Updates are _merged_ with existing metadata.

    Returns:
    - 200 Ok: if the label is successfully updated.
    - 400 Bad Request: if the request body is not a valid metadata.
    - 404 Not Found: if the state machine or label does not exist.
    - 410 Gone: if the label once existed but has since been deleted.

    Successful return codes return the full new metadata for a label.
    """
    app = server.config.app
    label = LabelRef(label_name, state_machine_name)

    try:
        patch_metadata = request.get_json()['metadata']
    except KeyError:
        abort(400, "No new metadata")

    try:
        new_metadata = state_machine.update_metadata_for_label(
            app,
            label,
            patch_metadata,
        )
        state = state_machine.get_label_state(app, label)
        return jsonify(metadata=new_metadata, state=state.name)
    except UnknownStateMachine:
        msg = f"State machine '{state_machine_name}' does not exist"
        abort(404, msg)
    except UnknownLabel as e:
        abort(
            410 if e.deleted else 404,
            f"Label {label_name} does not exist in state machine "
            f"'{state_machine_name}'.",
        )
def test_update_metadata_for_label_raises_for_unknown_state_machine(app):
    label = LabelRef('foo', 'nonexistent_machine')
    with pytest.raises(UnknownStateMachine), app.new_session():
        state_machine.update_metadata_for_label(app, label, {})