def test_update(tmp_work_dir):
    job = Job(id="foo123", action="foo")
    insert(job)
    job.action = "bar"
    update(job, update_fields=["action"])
    jobs = find_where(Job, id="foo123")
    assert jobs[0].action == "bar"
Example #2
0
def test_select_values(tmp_work_dir):
    insert(Job(id="foo123", state=State.PENDING))
    insert(Job(id="foo124", state=State.RUNNING))
    insert(Job(id="foo125", state=State.FAILED))
    values = select_values(Job, "id", state__in=[State.PENDING, State.FAILED])
    assert sorted(values) == ["foo123", "foo125"]
    values = select_values(Job, "state", id="foo124")
    assert values == [State.RUNNING]
Example #3
0
def test_update_excluding_a_field(tmp_work_dir):
    job = Job(id="foo123", action="foo", commit="commit-of-glory")
    insert(job)
    job.action = "bar"
    job.commit = "commit-of-doom"
    update(job, exclude_fields=["commit"])
    j = find_one(Job, id="foo123")
    assert j.action == "bar"
    assert j.commit == "commit-of-glory"
Example #4
0
def test_model_overrides(complete_continuous):
    # case #1: assure global overrides applied
    data = deepcopy(complete_continuous)
    data["bmr"] = {"type": "Std. Dev.", "value": 0.123}
    session = Job.build_session(data, data["datasets"][0])
    for model in session.models:
        assert model.overrides["bmr"] == 0.123

    # case #2: assure model overrides are applied after global overrides
    data = deepcopy(complete_continuous)
    data["bmr"] = {"type": "Std. Dev.", "value": 0.123}
    data["models"] = [{"name": "Linear", "settings": {"bmr": 0.456}}, {"name": "Linear"}]
    session = Job.build_session(data, data["datasets"][0])
    assert session.models[0].overrides["bmr"] == 0.456
    assert session.models[1].overrides["bmr"] == 0.123
def create_failed_job(job_request, exception):
    """
    Sometimes we want to say to the job-server (and the user): your JobRequest
    was broken so we weren't able to create any jobs for it. But the only way
    for the job-runner to communicate back to the job-server is by creating a
    job. So this function creates a single job with the special action name
    "__error__", which starts in the FAILED state and whose status_message
    contains the error we wish to communicate.

    This is a bit of a hack, but it keeps the sync protocol simple.
    """
    # Special case for the NothingToDoError which we treat as a success
    if isinstance(exception, NothingToDoError):
        state = State.SUCCEEDED
        status_message = "All actions have already run"
        action = job_request.requested_actions[0]
    else:
        state = State.FAILED
        status_message = f"{type(exception).__name__}: {exception}"
        action = "__error__"
    now = int(time.time())
    job = Job(
        job_request_id=job_request.id,
        state=state,
        repo_url=job_request.repo_url,
        commit=job_request.commit,
        workspace=job_request.workspace,
        action=action,
        status_message=status_message,
        created_at=now,
        started_at=now,
        updated_at=now,
        completed_at=now,
    )
    insert_into_database(job_request, [job])
Example #6
0
def test_formatting_filter():
    record = logging.makeLogRecord({})
    assert log_utils.formatting_filter(record)
    assert record.action == ""

    record = logging.makeLogRecord({"job": test_job})
    assert log_utils.formatting_filter(record)
    assert record.action == "action: "
    assert record.tags == "project=project action=action id=id"

    record = logging.makeLogRecord({"job": test_job, "status_code": "code"})
    assert log_utils.formatting_filter(record)
    assert record.tags == "status=code project=project action=action id=id"

    test_job2 = Job(id="id", action="action", repo_url=repo_url, status_code="code")
    record = logging.makeLogRecord({"job": test_job2})
    assert log_utils.formatting_filter(record)
    assert record.tags == "status=code project=project action=action id=id"

    record = logging.makeLogRecord({"job": test_job, "job_request": test_request})
    assert log_utils.formatting_filter(record)
    assert record.tags == "project=project action=action id=id req=request"

    record = logging.makeLogRecord({"status_code": ""})
    assert log_utils.formatting_filter(record)
    assert record.tags == ""
Example #7
0
def test_job_resource_weights(tmp_path):
    config = textwrap.dedent("""
        [my-workspace]
        some_action = 2.5
        pattern[\d]+ = 4
        """)
    config_file = tmp_path / "config.ini"
    config_file.write_text(config)
    weights = parse_job_resource_weights(config_file)
    job = Job(workspace="foo", action="bar")
    assert get_job_resource_weight(job, weights=weights) == 1
    job = Job(workspace="my-workspace", action="some_action")
    assert get_job_resource_weight(job, weights=weights) == 2.5
    job = Job(workspace="my-workspace", action="pattern315")
    assert get_job_resource_weight(job, weights=weights) == 4
    job = Job(workspace="my-workspace", action="pattern000no_match")
    assert get_job_resource_weight(job, weights=weights) == 1
Example #8
0
def job_factory(job_request=None, **kwargs):
    if job_request is None:
        job_request = job_request_factory()

    values = deepcopy(JOB_DEFAULTS)
    values.update(kwargs)
    values["job_request_id"] = job_request.id
    job = Job(**values)
    insert(job)
    return job
Example #9
0
def test_basic_roundtrip(tmp_work_dir):
    job = Job(
        id="foo123",
        job_request_id="bar123",
        state=State.RUNNING,
        output_spec={"hello": [1, 2, 3]},
    )
    insert(job)
    j = find_one(Job, job_request_id__in=["bar123", "baz123"])
    assert job.id == j.id
    assert job.output_spec == j.output_spec
Example #10
0
def test_model_overrides(complete_continuous):
    # case #1: assure global overrides applied
    data = deepcopy(complete_continuous)
    data['bmr'] = {'type': 'Std. Dev.', 'value': 0.123}
    session = Job.build_session(data, data['datasets'][0])
    for model in session.models:
        assert model.overrides['bmr'] == 0.123

    # case #2: assure model overrides are applied after global overrides
    data = deepcopy(complete_continuous)
    data['bmr'] = {'type': 'Std. Dev.', 'value': 0.123}
    data['models'] = [{
        'name': 'Linear',
        'settings': {
            'bmr': 0.456
        }
    }, {
        'name': 'Linear'
    }]
    session = Job.build_session(data, data['datasets'][0])
    assert session.models[0].overrides['bmr'] == 0.456
    assert session.models[1].overrides['bmr'] == 0.123
def recursively_build_jobs(jobs_by_action, job_request, pipeline_config, action):
    """
    Recursively populate the `jobs_by_action` dict with jobs

    Args:
        jobs_by_action: A dict mapping action ID strings to Job instances
        job_request: An instance of JobRequest representing the job request.
        pipeline_config: A Pipeline instance representing the pipeline configuration.
        action: The string ID of the action to be added as a job.
    """
    existing_job = jobs_by_action.get(action)
    if existing_job and not job_should_be_rerun(job_request, existing_job):
        return

    action_spec = get_action_specification(
        pipeline_config,
        action,
        using_dummy_data_backend=config.USING_DUMMY_DATA_BACKEND,
    )

    # Walk over the dependencies of this action, creating any necessary jobs,
    # and ensure that this job waits for its dependencies to finish before it
    # starts
    wait_for_job_ids = []
    for required_action in action_spec.needs:
        recursively_build_jobs(
            jobs_by_action, job_request, pipeline_config, required_action
        )
        required_job = jobs_by_action[required_action]
        if required_job.state in [State.PENDING, State.RUNNING]:
            wait_for_job_ids.append(required_job.id)

    job = Job(
        job_request_id=job_request.id,
        state=State.PENDING,
        repo_url=job_request.repo_url,
        commit=job_request.commit,
        workspace=job_request.workspace,
        database_name=job_request.database_name,
        action=action,
        wait_for_job_ids=wait_for_job_ids,
        requires_outputs_from=action_spec.needs,
        run_command=action_spec.run,
        output_spec=action_spec.outputs,
        created_at=int(time.time()),
        updated_at=int(time.time()),
    )

    # Add it to the dictionary of scheduled jobs
    jobs_by_action[action] = job
Example #12
0
 def job(job_id, action, state):
     spec = get_action_specification(
         project,
         action,
         using_dummy_data_backend=config.USING_DUMMY_DATA_BACKEND,
     )
     return Job(
         id=job_id,
         job_request_id="previous-request",
         state=state,
         status_message="",
         repo_url=str(project_dir),
         workspace=project_dir.name,
         database_name="a-database",
         action=action,
         wait_for_job_ids=[],
         requires_outputs_from=spec.needs,
         run_command=spec.run,
         output_spec=spec.outputs,
         created_at=int(time.time()),
         updated_at=int(time.time()),
         outputs={},
     )
Example #13
0
from datetime import datetime
import logging
import time

from jobrunner.models import Job, JobRequest
from jobrunner import log_utils, local_run


FROZEN_TIMESTAMP = 1608568119.1467905
FROZEN_TIMESTRING = datetime.utcfromtimestamp(FROZEN_TIMESTAMP).isoformat()

repo_url = "https://github.com/opensafely/project"
test_job = Job(id="id", action="action", repo_url=repo_url)
test_request = JobRequest(
    id="request",
    repo_url=repo_url,
    workspace="workspace",
    commit="commit",
    requested_actions=["action"],
    cancelled_actions=[],
    database_name="dummy",
)


def test_formatting_filter():
    record = logging.makeLogRecord({})
    assert log_utils.formatting_filter(record)
    assert record.action == ""

    record = logging.makeLogRecord({"job": test_job})
    assert log_utils.formatting_filter(record)
Example #14
0
def test_find_one_fails_if_there_is_more_than_one_result(tmp_work_dir):
    insert(Job(id="foo123", workspace="the-workspace"))
    insert(Job(id="foo456", workspace="the-workspace"))
    with pytest.raises(ValueError):
        find_one(Job, workspace="the-workspace")
Example #15
0
def test_find_one_returns_a_single_value(tmp_work_dir):
    insert(Job(id="foo123", workspace="the-workspace"))
    job = find_one(Job, id="foo123")
    assert job.workspace == "the-workspace"
Example #16
0
def test_update(tmp_work_dir):
    job = Job(id="foo123", action="foo")
    insert(job)
    job.action = "bar"
    update(job)
    assert find_one(Job, id="foo123").action == "bar"