diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d880e60..5f67c9e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,11 @@ This file keeps track of all notable changes to the Cluster Agent. Unreleased ---------- +* Modified the interface with Jobbergate-API to address the new data model on the back-end + +2.2.3 2023-07-02 +--------------------- + * Added support to set slurm restd version dynamically 2.2.2 2023-02-28 diff --git a/cluster_agent/jobbergate/api.py b/cluster_agent/jobbergate/api.py index da15ae2..d5d5c87 100644 --- a/cluster_agent/jobbergate/api.py +++ b/cluster_agent/jobbergate/api.py @@ -22,7 +22,7 @@ async def fetch_pending_submissions() -> List[PendingJobSubmission]: response = await backend_client.get("/jobbergate/job-submissions/agent/pending") response.raise_for_status() pending_job_submissions = [ - PendingJobSubmission(**pjs) for pjs in response.json() + PendingJobSubmission(**pjs) for pjs in response.json().get("items", []) ] logger.debug(f"Retrieved {len(pending_job_submissions)} pending job submissions") @@ -39,7 +39,9 @@ async def fetch_active_submissions() -> List[ActiveJobSubmission]: ): response = await backend_client.get("jobbergate/job-submissions/agent/active") response.raise_for_status() - active_job_submissions = [ActiveJobSubmission(**ajs) for ajs in response.json()] + active_job_submissions = [ + ActiveJobSubmission(**ajs) for ajs in response.json().get("items", []) + ] logger.debug(f"Retrieved {len(active_job_submissions)} active job submissions") return active_job_submissions @@ -60,7 +62,7 @@ async def mark_as_submitted(job_submission_id: int, slurm_job_id: int): response = await backend_client.put( f"jobbergate/job-submissions/agent/{job_submission_id}", json=dict( - new_status=JobSubmissionStatus.SUBMITTED, + status=JobSubmissionStatus.SUBMITTED, slurm_job_id=slurm_job_id, ), ) @@ -111,6 +113,6 @@ async def update_status( ): response = await backend_client.put( f"jobbergate/job-submissions/agent/{job_submission_id}", - json=dict(new_status=status, report_message=report_message), + json=dict(status=status, report_message=report_message), ) response.raise_for_status() diff --git a/cluster_agent/jobbergate/constants.py b/cluster_agent/jobbergate/constants.py index efab273..655c267 100644 --- a/cluster_agent/jobbergate/constants.py +++ b/cluster_agent/jobbergate/constants.py @@ -3,6 +3,13 @@ from typing import DefaultDict +class FileType(str, Enum): + """File type enum.""" + + ENTRYPOINT = "ENTRYPOINT" + SUPPORT = "SUPPORT" + + class JobSubmissionStatus(str, Enum): """ Enumeration of possible job_submission statuses. diff --git a/cluster_agent/jobbergate/schemas.py b/cluster_agent/jobbergate/schemas.py index 525d5d2..721365c 100644 --- a/cluster_agent/jobbergate/schemas.py +++ b/cluster_agent/jobbergate/schemas.py @@ -3,16 +3,26 @@ import pydantic -from cluster_agent.jobbergate.constants import JobSubmissionStatus, status_map +from cluster_agent.jobbergate.constants import FileType, JobSubmissionStatus, status_map -class JobScriptFiles(pydantic.BaseModel, extra=pydantic.Extra.ignore): - """ - Model containing job-script files. - """ +class JobScriptFile(pydantic.BaseModel, extra=pydantic.Extra.ignore): + """Model for the job_script_files field of the JobScript resource.""" + + parent_id: int + filename: str + file_type: FileType + parent_id: int + + @property + def path(self) -> str: + return f"/jobbergate/job-scripts/{self.parent_id}/upload/{self.filename}" + - main_file_path: Path - files: Dict[Path, str] +class JobScript(pydantic.BaseModel, extra=pydantic.Extra.ignore): + """Model to match database for the JobScript resource.""" + + files: List[JobScriptFile] class PendingJobSubmission(pydantic.BaseModel, extra=pydantic.Extra.ignore): @@ -22,13 +32,11 @@ class PendingJobSubmission(pydantic.BaseModel, extra=pydantic.Extra.ignore): """ id: int - job_submission_name: str - job_submission_owner_email: str + name: str + owner_email: str execution_directory: Optional[Path] execution_parameters: Dict[str, Any] = pydantic.Field(default_factory=dict) - job_script_name: str - application_name: str - job_script_files: JobScriptFiles + job_script: JobScript class ActiveJobSubmission(pydantic.BaseModel, extra=pydantic.Extra.ignore): diff --git a/cluster_agent/jobbergate/submit.py b/cluster_agent/jobbergate/submit.py index 32479a4..e824976 100644 --- a/cluster_agent/jobbergate/submit.py +++ b/cluster_agent/jobbergate/submit.py @@ -4,6 +4,7 @@ from buzz import handle_errors from loguru import logger +from cluster_agent.identity.cluster_api import backend_client from cluster_agent.identity.slurm_user.factory import manufacture from cluster_agent.identity.slurm_user.mappers import SlurmUserMapper from cluster_agent.identity.slurmrestd import backend_client as slurmrestd_client @@ -13,7 +14,7 @@ fetch_pending_submissions, mark_as_submitted, ) -from cluster_agent.jobbergate.constants import JobSubmissionStatus +from cluster_agent.jobbergate.constants import FileType, JobSubmissionStatus from cluster_agent.jobbergate.schemas import ( PendingJobSubmission, SlurmJobParams, @@ -30,23 +31,6 @@ from cluster_agent.utils.logging import log_error -def get_job_script(pending_job_submission: PendingJobSubmission) -> str: - """ - Get the job script from a PendingJobSubmission object. - Raise JobSubmissionError if no job script is found or if its empty. - """ - job_script = pending_job_submission.job_script_files.files.get( - pending_job_submission.job_script_files.main_file_path, "" - ) - - JobSubmissionError.require_condition( - bool(job_script), - "Could not find an executable script in retrieved job script data.", - ) - - return job_script - - def unpack_error_from_slurm_response(response: SlurmSubmitResponse) -> str: """ Unpack the error message from the response of a slurmrestd request. @@ -86,8 +70,8 @@ async def submit_job_script( raise_exc_class=JobSubmissionError, do_except=notify_submission_rejected.report_error, ): - email = pending_job_submission.job_submission_owner_email - name = pending_job_submission.application_name + email = pending_job_submission.owner_email + name = pending_job_submission.name mapper_class_name = user_mapper.__class__.__name__ logger.debug( f"Fetching username for email {email} with mapper {mapper_class_name}" @@ -95,19 +79,31 @@ async def submit_job_script( username = await user_mapper.find_username(email) logger.debug(f"Using local slurm user {username} for job submission") - job_script = get_job_script(pending_job_submission) - submit_dir = ( pending_job_submission.execution_directory or SETTINGS.DEFAULT_SLURM_WORK_DIR ) - for path, file_content in pending_job_submission.job_script_files.files.items(): - local_script_path = submit_dir / path + job_script = None + + for metadata in pending_job_submission.job_script.files: + local_script_path = submit_dir / metadata.filename local_script_path.parent.mkdir(parents=True, exist_ok=True) - local_script_path.write_text(file_content) + + response = await backend_client.get(metadata.path) + response.raise_for_status() + local_script_path.write_bytes(response.content) + + if metadata.file_type == FileType.ENTRYPOINT: + job_script = response.content.decode("utf-8") + logger.debug(f"Copied job script file to {local_script_path}") + JobSubmissionError.require_condition( + job_script, + "Could not find an executable script in retrieved job script data.", + ) + async with handle_errors_async( "Failed to extract Slurm parameters", raise_exc_class=SlurmParameterParserError, @@ -115,7 +111,7 @@ async def submit_job_script( ): job_parameters = get_job_parameters( pending_job_submission.execution_parameters, - name=pending_job_submission.application_name, + name=name, current_working_directory=submit_dir, standard_output=submit_dir / f"{name}.out", standard_error=submit_dir / f"{name}.err", diff --git a/setup.py b/setup.py index a860657..39db16b 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ here = dirname(__file__) -_VERSION = "2.2.2" +_VERSION = "2.2.3" setup( name="ovs-cluster-agent", diff --git a/tests/jobbergate/conftest.py b/tests/jobbergate/conftest.py index fe1e1df..a028037 100644 --- a/tests/jobbergate/conftest.py +++ b/tests/jobbergate/conftest.py @@ -25,25 +25,25 @@ def dummy_template_source(): @pytest.fixture -def dummy_job_script_files(dummy_template_source): - return { - "main_file_path": "application.sh", - "files": {"application.sh": dummy_template_source}, - } +def dummy_job_script_files(): + return [ + { + "parent_id": 1, + "filename": "application.sh", + "file_type": "ENTRYPOINT", + }, + ] @pytest.fixture -def dummy_pending_job_submission_data(dummy_job_script_files): +def dummy_pending_job_submission_data(dummy_job_script_files, tmp_path): """ Provide a fixture that returns a dict that is compatible with PendingJobSubmission. """ return dict( id=1, - job_submission_name="sub1", - job_submission_owner_email="email1@dummy.com", - job_script_id=11, - job_script_name="script1", - job_script_files=dummy_job_script_files, - application_name="app1", + name="sub1", + owner_email="email1@dummy.com", + job_script={"files": dummy_job_script_files}, slurm_job_id=13, ) diff --git a/tests/jobbergate/test_api.py b/tests/jobbergate/test_api.py index 3c5697f..6422489 100644 --- a/tests/jobbergate/test_api.py +++ b/tests/jobbergate/test_api.py @@ -28,38 +28,31 @@ async def test_fetch_pending_submissions__success(dummy_job_script_files): Test that the ``fetch_pending_submissions()`` function can successfully retrieve PendingJobSubmission objects from the API. """ - pending_job_submissions_data = [ - dict( - id=1, - job_submission_name="sub1", - job_submission_owner_email="email1@dummy.com", - job_script_id=11, - job_script_name="script1", - job_script_files=dummy_job_script_files, - application_name="app1", - slurm_job_id=111, - ), - dict( - id=2, - job_submission_name="sub2", - job_submission_owner_email="email2@dummy.com", - job_script_id=22, - job_script_name="script2", - job_script_files=dummy_job_script_files, - application_name="app2", - slurm_job_id=222, - ), - dict( - id=3, - job_submission_name="sub3", - job_submission_owner_email="email3@dummy.com", - job_script_id=33, - job_script_name="script3", - job_script_files=dummy_job_script_files, - application_name="app3", - slurm_job_id=333, - ), - ] + pending_job_submissions_data = { + "items": [ + dict( + id=1, + name="sub1", + owner_email="email1@dummy.com", + job_script={"files": dummy_job_script_files}, + slurm_job_id=111, + ), + dict( + id=2, + name="sub2", + owner_email="email2@dummy.com", + job_script={"files": dummy_job_script_files}, + slurm_job_id=222, + ), + dict( + id=3, + name="sub3", + owner_email="email3@dummy.com", + job_script={"files": dummy_job_script_files}, + slurm_job_id=333, + ), + ] + } async with respx.mock: respx.post( f"https://{SETTINGS.OIDC_DOMAIN}/protocol/openid-connect/token" @@ -79,7 +72,7 @@ async def test_fetch_pending_submissions__success(dummy_job_script_files): ) pending_job_submissions = await fetch_pending_submissions() - for (i, pending_job_submission) in enumerate(pending_job_submissions): + for i, pending_job_submission in enumerate(pending_job_submissions): assert isinstance(pending_job_submission, PendingJobSubmission) assert i + 1 == pending_job_submission.id @@ -144,23 +137,22 @@ async def test_fetch_active_submissions__success(): Test that the ``fetch_active_submissions()`` function can successfully retrieve ActiveJobSubmission objects from the API. """ - pending_job_submissions_data = [ - dict( - id=1, - job_submission_name="sub1", - slurm_job_id=11, - ), - dict( - id=2, - job_submission_name="sub2", - slurm_job_id=22, - ), - dict( - id=3, - job_submission_name="sub3", - slurm_job_id=33, - ), - ] + pending_job_submissions_data = { + "items": [ + dict( + id=1, + slurm_job_id=11, + ), + dict( + id=2, + slurm_job_id=22, + ), + dict( + id=3, + slurm_job_id=33, + ), + ] + } with respx.mock: respx.post( f"https://{SETTINGS.OIDC_DOMAIN}/protocol/openid-connect/token" @@ -180,7 +172,7 @@ async def test_fetch_active_submissions__success(): ) active_job_submissions = fetch_active_submissions() - for (i, active_job_submission) in enumerate(await active_job_submissions): + for i, active_job_submission in enumerate(await active_job_submissions): assert isinstance(active_job_submission, ActiveJobSubmission) assert i + 1 == active_job_submission.id assert (i + 1) * 11 == active_job_submission.slurm_job_id @@ -314,12 +306,12 @@ async def test_update_status__success(): await update_status(1, JobSubmissionStatus.COMPLETED) assert update_route.calls.last.request.content == json.dumps( - dict(new_status=JobSubmissionStatus.COMPLETED, report_message=None) + dict(status=JobSubmissionStatus.COMPLETED, report_message=None) ).encode("utf-8") await update_status(2, JobSubmissionStatus.FAILED) assert update_route.calls.last.request.content == json.dumps( - dict(new_status=JobSubmissionStatus.FAILED, report_message=None) + dict(status=JobSubmissionStatus.FAILED, report_message=None) ).encode("utf-8") assert update_route.call_count == 2 @@ -393,7 +385,7 @@ async def test_notify_submission_rejected(): assert update_route.call_count == 1 assert update_route.calls.last.request.content == json.dumps( dict( - new_status=JobSubmissionStatus.REJECTED, + status=JobSubmissionStatus.REJECTED, report_message=report_message, ), ).encode("utf-8") diff --git a/tests/jobbergate/test_finish.py b/tests/jobbergate/test_finish.py index b1d75d2..1c2e84e 100644 --- a/tests/jobbergate/test_finish.py +++ b/tests/jobbergate/test_finish.py @@ -90,13 +90,15 @@ async def test_finish_active_jobs(): retrieve the job state from slurm, map it to a ``JobSubmissionStatus``, and update the job submission status via the API. """ - active_job_submissions_data = [ - dict(id=1, slurm_job_id=11), # Will complete - dict(id=2, slurm_job_id=22), # Jobbergate API gives a 400 - dict(id=3, slurm_job_id=33), # Slurm REST API gives a 400 - dict(id=4, slurm_job_id=44), # Slurm has no matching job - dict(id=5, slurm_job_id=55), # Unmapped status - ] + active_job_submissions_data = { + "items": [ + dict(id=1, slurm_job_id=11), # Will complete + dict(id=2, slurm_job_id=22), # Jobbergate API gives a 400 + dict(id=3, slurm_job_id=33), # Slurm REST API gives a 400 + dict(id=4, slurm_job_id=44), # Slurm has no matching job + dict(id=5, slurm_job_id=55), # Unmapped status + ] + } async with respx.mock: respx.post( @@ -169,7 +171,7 @@ def _map_slurm_call(request: httpx.Request): def _map_update_call(request: httpx.Request): return ( int(request.url.path.split("/")[-1]), - json.loads(request.content.decode("utf-8"))["new_status"], + json.loads(request.content.decode("utf-8"))["status"], ) assert update_route.call_count == 2 diff --git a/tests/jobbergate/test_submit.py b/tests/jobbergate/test_submit.py index 8d40260..be4610d 100644 --- a/tests/jobbergate/test_submit.py +++ b/tests/jobbergate/test_submit.py @@ -19,7 +19,6 @@ ) from cluster_agent.jobbergate.submit import ( get_job_parameters, - get_job_script, submit_job_script, submit_pending_jobs, unpack_error_from_slurm_response, @@ -28,45 +27,6 @@ from cluster_agent.utils.exception import JobSubmissionError, SlurmrestdError -@pytest.mark.asyncio -async def test_get_job_script__success( - dummy_pending_job_submission_data, dummy_template_source -): - """ - Test if a job script is successfully recovered from a PendingJobSubmission. - """ - pending_job_submission = PendingJobSubmission(**dummy_pending_job_submission_data) - assert dummy_template_source == get_job_script(pending_job_submission) - - -@pytest.mark.asyncio -async def test_get_job_script__raises_exception_if_empty( - dummy_pending_job_submission_data, -): - """ - Test if JobSubmissionError is raised when a empty job script is - recovered from a PendingJobSubmission. - """ - pending_job_submission = PendingJobSubmission(**dummy_pending_job_submission_data) - pending_job_submission.job_script_files.files = {"application.sh": ""} - with pytest.raises(JobSubmissionError): - get_job_script(pending_job_submission) - - -@pytest.mark.asyncio -async def test_get_job_script__raises_exception_if_missing( - dummy_pending_job_submission_data, -): - """ - Test if JobSubmissionError is raised when a job script is not successfully - recovered from a PendingJobSubmission. - """ - pending_job_submission = PendingJobSubmission(**dummy_pending_job_submission_data) - pending_job_submission.job_script_files.files = {"application.sh": None} - with pytest.raises(JobSubmissionError): - get_job_script(pending_job_submission) - - @pytest.mark.asyncio async def test_submit_job_script__success( dummy_pending_job_submission_data, dummy_template_source, mocker @@ -82,7 +42,7 @@ async def test_submit_job_script__success( user_mapper.find_username.return_value = "dummy-user" pending_job_submission = PendingJobSubmission(**dummy_pending_job_submission_data) - name = pending_job_submission.application_name + name = pending_job_submission.name async with respx.mock: submit_route = respx.post(f"{SETTINGS.SLURM_RESTD_VERSIONED_URL}/job/submit") @@ -93,26 +53,37 @@ async def test_submit_job_script__success( ) ) + download_route = respx.get( + f"{SETTINGS.BASE_API_URL}/jobbergate/job-scripts/1/upload/application.sh" + ) + download_route.mock( + return_value=httpx.Response( + status_code=200, + content=dummy_template_source.encode("utf-8"), + ), + ) + slurm_job_id = await submit_job_script(pending_job_submission, user_mapper) assert slurm_job_id == 13 assert submit_route.call_count == 1 + assert download_route.call_count == 1 last_request = submit_route.calls.last.request assert last_request.method == "POST" assert last_request.headers["x-slurm-user-name"] == "dummy-user" assert last_request.headers["x-slurm-user-token"] == "default-dummy-token" - assert ( - last_request.content.decode("utf-8") - == SlurmJobSubmission( - script=dummy_template_source, - job=SlurmJobParams( - name=name, - current_working_directory=SETTINGS.DEFAULT_SLURM_WORK_DIR, - standard_output=SETTINGS.DEFAULT_SLURM_WORK_DIR / f"{name}.out", - standard_error=SETTINGS.DEFAULT_SLURM_WORK_DIR / f"{name}.err", - ), - ).json() - ) + actual_response = last_request.content.decode("utf-8") + expected_response = SlurmJobSubmission( + script=dummy_template_source, + job=SlurmJobParams( + name=name, + current_working_directory=SETTINGS.DEFAULT_SLURM_WORK_DIR, + standard_output=SETTINGS.DEFAULT_SLURM_WORK_DIR / f"{name}.out", + standard_error=SETTINGS.DEFAULT_SLURM_WORK_DIR / f"{name}.err", + ), + ).json() + + assert actual_response == expected_response @pytest.mark.asyncio @@ -141,11 +112,11 @@ async def test_submit_job_script__with_non_default_execution_directory( **dummy_pending_job_submission_data, execution_directory=exe_path, ) - name = pending_job_submission.application_name + name = pending_job_submission.name job_parameters = get_job_parameters( pending_job_submission.execution_parameters, - name=pending_job_submission.application_name, + name=name, current_working_directory=exe_path, standard_output=exe_path / f"{name}.out", standard_error=exe_path / f"{name}.err", @@ -160,10 +131,21 @@ async def test_submit_job_script__with_non_default_execution_directory( ) ) + download_route = respx.get( + f"{SETTINGS.BASE_API_URL}/jobbergate/job-scripts/1/upload/application.sh" + ) + download_route.mock( + return_value=httpx.Response( + status_code=200, + content=dummy_template_source.encode("utf-8"), + ), + ) + slurm_job_id = await submit_job_script(pending_job_submission, user_mapper) assert slurm_job_id == 13 assert submit_route.call_count == 1 + assert download_route.call_count == 1 last_request = submit_route.calls.last.request assert last_request.method == "POST" assert last_request.headers["x-slurm-user-name"] == "dummy-user" @@ -187,7 +169,7 @@ async def test_submit_job_script__raises_exception_if_no_executable_script_was_f and that the job submission status is updated to rejected. """ pending_job_submission = PendingJobSubmission(**dummy_pending_job_submission_data) - pending_job_submission.job_script_files.files = {"application.sh": ""} + pending_job_submission.job_script.files = [] async with respx.mock: respx.post(f"https://{SETTINGS.OIDC_DOMAIN}/oauth/token").mock( @@ -211,7 +193,7 @@ async def test_submit_job_script__raises_exception_if_no_executable_script_was_f @pytest.mark.asyncio async def test_submit_job_script__raises_exception_if_submit_call_response_is_not_200( - dummy_pending_job_submission_data, mocker + dummy_pending_job_submission_data, mocker, dummy_template_source ): """ Test that ``submit_job_script()`` raises an exception if the response from Slurm @@ -250,6 +232,16 @@ async def test_submit_job_script__raises_exception_if_submit_call_response_is_no ) ) + download_route = respx.get( + f"{SETTINGS.BASE_API_URL}/jobbergate/job-scripts/1/upload/application.sh" + ) + download_route.mock( + return_value=httpx.Response( + status_code=200, + content=dummy_template_source.encode("utf-8"), + ), + ) + with pytest.raises( SlurmrestdError, match="Failed to submit job to slurm", @@ -257,12 +249,14 @@ async def test_submit_job_script__raises_exception_if_submit_call_response_is_no await submit_job_script(pending_job_submission, user_mapper) assert update_route.call_count == 1 + assert download_route.call_count == 1 @pytest.mark.asyncio async def test_submit_job_script__raises_exception_if_response_cannot_be_unpacked( dummy_pending_job_submission_data, mocker, + dummy_template_source, ): """ Test that ``submit_job_script()`` raises an exception if the response from Slurm @@ -294,49 +288,56 @@ async def test_submit_job_script__raises_exception_if_response_cannot_be_unpacke ) ) + download_route = respx.get( + f"{SETTINGS.BASE_API_URL}/jobbergate/job-scripts/1/upload/application.sh" + ) + download_route.mock( + return_value=httpx.Response( + status_code=200, + content=dummy_template_source.encode("utf-8"), + ), + ) + with pytest.raises(SlurmrestdError, match="Failed to submit job to slurm"): await submit_job_script(pending_job_submission, user_mapper) assert update_route.call_count == 1 + assert download_route.call_count == 1 @pytest.mark.asyncio -async def test_submit_pending_jobs(dummy_job_script_files, tweak_settings): +async def test_submit_pending_jobs( + dummy_job_script_files, + tweak_settings, + dummy_template_source, +): """ Test that the ``submit_pending_jobs()`` function can fetch pending job submissions, submit each to slurm via the Slurm REST API, and update the job submission via the Jobbergate API. """ - pending_job_submissions_data = [ - dict( - id=1, - job_submission_name="sub1", - job_submission_owner_email="email1@dummy.com", - job_script_id=11, - job_script_name="script1", - job_script_files=dummy_job_script_files, - application_name="app1", - ), - dict( - id=2, - job_submission_name="sub2", - job_submission_owner_email="email2@dummy.com", - job_script_id=22, - job_script_name="script2", - job_script_files=dummy_job_script_files, - application_name="app2", - ), - dict( - id=3, - job_submission_name="sub3", - job_submission_owner_email="email3@dummy.com", - job_script_id=33, - job_script_name="script3", - job_script_files=dummy_job_script_files, - application_name="app3", - ), - ] - + pending_job_submissions_data = { + "items": [ + dict( + id=1, + name="sub1", + owner_email="email1@dummy.com", + job_script={"files": dummy_job_script_files}, + ), + dict( + id=2, + name="sub2", + owner_email="email2@dummy.com", + job_script={"files": dummy_job_script_files}, + ), + dict( + id=3, + name="sub3", + owner_email="email3@dummy.com", + job_script={"files": dummy_job_script_files}, + ), + ] + } async with respx.mock: respx.post( f"https://{SETTINGS.OIDC_DOMAIN}/protocol/openid-connect/token" @@ -369,11 +370,21 @@ async def test_submit_pending_jobs(dummy_job_script_files, tweak_settings): ) update_3_route.mock(return_value=httpx.Response(status_code=200)) + download_route = respx.get( + f"{SETTINGS.BASE_API_URL}/jobbergate/job-scripts/1/upload/application.sh" + ) + download_route.mock( + return_value=httpx.Response( + status_code=200, + content=dummy_template_source.encode("utf-8"), + ), + ) + def _submit_side_effect(request): req_data = request.content.decode("utf-8") name = json.loads(req_data)["job"]["name"] - fake_slurm_job_id = int(name.replace("app", "")) * 11 - if name == "app3": + fake_slurm_job_id = int(name.replace("sub", "")) * 11 + if name == "sub3": return httpx.Response( status_code=400, json=dict(errors=dict(error="BOOM!")), @@ -393,7 +404,7 @@ def _submit_side_effect(request): assert update_1_route.call_count == 1 assert update_1_route.calls.last.request.content == json.dumps( dict( - new_status=JobSubmissionStatus.SUBMITTED, + status=JobSubmissionStatus.SUBMITTED, slurm_job_id=11, ) ).encode("utf-8") @@ -401,12 +412,13 @@ def _submit_side_effect(request): assert update_2_route.call_count == 1 assert update_2_route.calls.last.request.content == json.dumps( dict( - new_status=JobSubmissionStatus.SUBMITTED, + status=JobSubmissionStatus.SUBMITTED, slurm_job_id=22, ) ).encode("utf-8") assert update_3_route.call_count == 1 # called to notify the job was rejected + assert download_route.call_count == len(pending_job_submissions_data["items"]) class TestGetJobParameters: