Repository navigation
fix: a failed novostoic run was reported as completed with no results - #171
matthewberry wants to merge 2 commits into
Conversation
The novostoic commands in chart/values.yaml exit 0 and write a `fail` marker when the tool fails, and nothing read that marker. postjob.py now fails the Job on `fail` after the upload, the same way it does on `error`, so the watcher marks the job 'error' and the logs still reach MinIO.
matthewberry
left a comment
There was a problem hiding this comment.
The fail check itself looks correct. I didn't reproduce any of the points below; they come from reading the code at ba3c959.
Checked and cleared
- No re-run on failure:
backoffLimit: 0(kubejob_service.py:892) andrestartPolicy: Never, so the forced exit 1 doesn't run the novostoic tool again. git grepfor the quoted"fail"and/failforms overapp/andchart/finds the marker written only by the four novostoic commands inchart/values.yaml.LOG_ARTIFACTSmentions it but doesn't use it to decide anything.- The watcher's
_derive_phaseturns a failed pod intoerror.
Files outside this diff
- The PR body is wrong about the email (
app/services/kubejob_service.py:319-322). It sayssend_notification_emailbuilds the same results link for every terminal phase. TheERRORbranch sends only "An error occurred in computing the result for your … job." with noresults_url, so users can't reach theoutput.log/error.logthis change keeps on purpose. - The in-repo app configs lack the marker. The novostoic commands in
app/cfg/config.yaml(~L345) andlocal.config.yamldon't writefail, so the new comment inpostjob.pyis true only ofchart/values.yaml. Where those configs are used, a failed run exits non-zero in the init container, post-job never runs, and the logs never reach MinIO. - Inconsistent artifact listing (
app/routers/v1/jobs.py:45).LOG_ARTIFACTShidesfailbut noterror, so/v1/jobs/{id}/artifactslists an emptyerrorfile for one marker and hides the other. - Nothing at the write site points to the reader. Nothing in
chart/values.yaml, where the commands that write the marker live, says thatpostjob.pyreads the names('error', 'fail'). The next command that adds or renames a marker will be written there.
| log.error(f'Job[{bucket_name}]: {job_id} wrote an error sentinel; failing the Job') | ||
| sys.exit(1) | ||
| # The novostoic commands in chart/values.yaml write `fail` instead of `error`. | ||
| for sentinel in ('error', 'fail'): |
There was a problem hiding this comment.
This check now applies to every job type, but only the novostoic commands write fail. Any other tool that leaves a top-level file named fail in JOB_OUTPUT_DIR (its own marker, or a user-named file copied through) would have a successful run marked error and get a failure email. The evidence that no other type does this is a sample of 100 jobs per type. Narrower options: check fail only when JOB_TYPE starts with novostoic-, or change the four commands to touch error so the shared code needs no second marker.
| if os.path.exists(os.path.join(job_output_dir, 'error')): | ||
| log.error(f'Job[{bucket_name}]: {job_id} wrote an error sentinel; failing the Job') | ||
| sys.exit(1) | ||
| # The novostoic commands in chart/values.yaml write `fail` instead of `error`. |
There was a problem hiding this comment.
The PR body says a search of app/ and chart/ finds touch …/fail only in these four commands, but it doesn't show the search. That pattern also misses other ways of creating the file (> fail, or open('fail', 'w') inside a tool image). Could you put the command next to the claim?
There was a problem hiding this comment.
This check is gone from postjob.py now. The description now shows the searches next to the claim: git grep -n 'fail\b' -- app chart, plus a grep of novostoic-frontend's src. The prod scan was of artifact names, so it would catch a fail file however it was created. A tool image that writes one internally is still possible and not checked, but nothing reads fail any more.
| assert calls == [{"local_path": str(job_env), "bucket_name": "clean", "minio_prefix": "job1/out"}] | ||
|
|
||
|
|
||
| NOVOSTOIC_TYPES = ["novostoic-dgpredictor", "novostoic-enzrank", "novostoic-optstoic", "novostoic-pathways"] |
There was a problem hiding this comment.
Because this list is hard-coded, a fifth novostoic job, or one whose command lacks the marker, isn't covered: the test stays green and the original bug comes back for that type. Building it from [k for k in values['config']['kubernetes_jobs'] if k.startswith('novostoic-')] would catch it.
There was a problem hiding this comment.
Done in 5a1f922. The new tests/test_novostoic_job_exit_code.py builds the job type list from chart/values.yaml, and test_every_novostoic_type_is_covered pins it, so adding a fifth type also changes a test.
| out.mkdir() | ||
| env = {**os.environ, "PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}", "JOB_OUTPUT_DIR": str(out)} | ||
|
|
||
| subprocess.run(["bash", "-c", command], cwd=tmp_path, env=env, check=True) |
There was a problem hiding this comment.
This runs the bare command, but production wraps it in job.tpl.yaml as ls -al ${JOB_INPUT_DIR} && {{ command }} && ls -al ${JOB_OUTPUT_DIR} && touch finished. In the wrapped form, a failing ls skips python, $? holds ls's exit code, and fail is written with no error.log. A change to the wrapper that alters how $? reaches the if wouldn't be caught either. Rendering the command through the wrapper would test what actually runs.
There was a problem hiding this comment.
Done in 5a1f922. The new test renders each command through job.tpl.yaml with create_job and runs the job init container's script, the same way test_clean_job_exit_code.py does. With fail restored, all 8 failure cases fail.
…st-job `fail` Review on #171: checking `fail` in postjob.py applied it to every job type, though only the novostoic commands write it. The four commands now write `error`, which postjob.py already fails the Job on, so postjob.py is back to #161's version. The new test renders each novostoic command from the chart through the real job template, and builds the job type list from the chart.
|
Thanks for the review. Changes are in 5a1f922, and the description is rewritten.
|
Stacked on #161 (itself stacked on #160). It relies on the
postjob.pycheck that #161 adds, which fails the Job whenerrorexists. Merge #160 and #161 first.Problem
The four novostoic commands in
chart/values.yamlended:The command exits 0 either way, and nothing reads
fail. A failed run therefore looks like a successful Job:completed;novostoicResultPostProcessreturnsNonewhen there is nooutput.json.On prod, 119 of the 670 completed novostoic jobs have
failand nooutput.json:I found these by listing each completed job's artifacts with
GET /v1/jobs/{id}/artifacts?include=logs.Change
errorinstead offail. fix: a failed CLEAN run was reported as completed with no results #161'spostjob.pycheck uploads the logs, then fails the Job onerror, so the job is markederror, as it is for CLEAN, CRISPR-Copies and Mutagenesis.postjob.pyis not changed.output.loganderror.log, for the init-container reason given in fix: a failed CLEAN run was reported as completed with no results #161.values.yamlstates this rule and names the test that checks it.The first version of this PR taught
postjob.pyto also fail onfail. Review pointed out that this applied the new marker to every job type, though only novostoic writes it, so this version switches the commands to the existing marker instead.Why this is safe
failandoutput.json. Some failed pathways runs leaveoutput.intermediate.jsonorraw_solution.txt, but the results route reads onlyoutput.json.fail:git grep -n 'fail\b' -- app chartfinds it only in the four commands and inLOG_ARTIFACTS(app/routers/v1/jobs.py:45), which only hides it from the default artifact listing.761404e),grep -rn "'fail'\|\"fail\"\|/fail\b" srcfinds nothing.error:JobResult.statusResponse$stops polling on any phase other than queued or processing, and the loading component shows its error state onJobStatus.Error.app/cfg/config.yaml(chart/templates/deployment.yaml:81), and no values overlay overrides the novostoic commands. The novostoic commands inapp/cfg/config.yamlandapp/cfg/local.config.yaml, used only locally, write no marker. That is unchanged.Testing
tests/test_novostoic_job_exit_code.py: for each novostoic job type inchart/values.yaml, it renders that command through the realjob.tpl.yamlwithcreate_job, then runs thejobcontainer's script with a stand-inpython.error,error.log,finishedandoutput.log.successand noerror.failmarker restored, all 8 failure cases fail.Dockerfile.testimage: 643 passed.Deploy note
This needs a chart deploy, and
:mainmust already have #161'spostjob.pycheck, becausepost-jobalways runsmoleculemaker/mmli-backend:main. If the chart is deployed first, a failed run is still markedcompleted, as it is today, so deploying in the wrong order loses nothing. After both are in place:error;Not changed
ERRORbranch ofsend_notification_email(app/services/kubejob_service.py:319) sends "An error occurred in computing the result for your … job." with no link. That is true for every job type, so users can't reach the logs this change keeps. The first version of this description wrongly said the email was the same for every phase.successand nooutput.json. The one log I read says "No solution file found", and that run exited 0. Whether "no pathway found" should be an empty result or an error is a separate decision.completedare left as they are.LOG_ARTIFACTSstill hidesfail, for those past jobs. Hidingerroris fix: hide the job template's log and marker files from the v1 artifact listing #163.