fix(apiserver): send exdata, exec cwd and exec home with submitted jobs (bug-1936)

Cause: modelJobToSubmit() filled only cmdline, execuser, launch_type and
pre/post-exec of job_startinfo_t, so the extended data the submit handler
builds (env, *_limit incl. -W run limit, file transfers, interactive mode),
the cwd and the exec home never reached the controller or the worker.

Fix: map the start info in a pure submitStartInfo() (exdata from
ExtendData, execcwd from ExecCWD or CWD, exechome from ExecHome) that
modelJobToSubmit copies into the C struct; decode exdata back in
cJobToModel/cJobArrayToModels so the store keeps ExtendData and CWD.
Reject extend_data the worker's parse_exdata() cannot take (not a JSON
object, over EXDATA_LENGTH-1 bytes) and drop an empty transfer list
instead of sending a null.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-10 08:28:50 +00:00
parent 7a9d75d661
commit d01ced9bd0
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
5 changed files with 236 additions and 14 deletions

View file

@ -1920,7 +1920,9 @@ func processJobRequest(ctx *gin.Context, req *models.JobSubmitRequest, defaultVa
extendData[i] = entry
}
}
extendDataMap["transfer_files"] = extendData
if len(extendData) > 0 {
extendDataMap["transfer_files"] = extendData
}
}
// handle env into json format
@ -1990,6 +1992,12 @@ func processJobRequest(ctx *gin.Context, req *models.JobSubmitRequest, defaultVa
req.ExtendData = string(updatedExtendDataBytes)
}
// bug-1936: ExtendData is the job's exdata at the worker; reject what
// the worker could not parse instead of failing the job at launch.
if err := backend.ValidateExdata(req.ExtendData); err != nil {
return err
}
return nil
}

View file

@ -1519,3 +1519,60 @@ func TestJobHandler_RestartNamedJob(t *testing.T) {
w.Code, len(f.submitted)-before, w.Body.String())
}
}
// bug-1936: the job handed to the backend carries the cwd and the extended
// data under the key names and units the worker's parse_exdata()/
// parse_limits() read (run_limit in seconds, memory limits in MB,
// sub_env_vars as a string map); modelJobToSubmit copies them into the
// start info.
func TestJobHandler_SubmitCarriesCwdEnvLimitsInExdata(t *testing.T) {
f := newHandlerFixture(t)
f.seedQueue("normal")
w := f.submit(map[string]interface{}{
"command": "sleep 120", "queue_name": "normal", "cwd": "/tmp/work",
"env": []string{"A=1"}, "run_limit": "1", "mem_limit": "1 GB",
})
if w.Code != http.StatusOK {
t.Fatalf("status = %d, want 200; body: %s", w.Code, w.Body.String())
}
subs := f.submissions()
if len(subs) != 1 {
t.Fatalf("submitJob calls = %d, want 1", len(subs))
}
job := subs[0].Job
if job.CWD != "/tmp/work" {
t.Errorf("CWD = %q, want /tmp/work", job.CWD)
}
var ex struct {
Env map[string]string `json:"sub_env_vars"`
Limits map[string]float64 `json:"limits"`
}
if err := json.Unmarshal([]byte(job.ExtendData.String), &ex); err != nil {
t.Fatalf("ExtendData %q is not a JSON object: %v", job.ExtendData.String, err)
}
if ex.Env["A"] != "1" {
t.Errorf("sub_env_vars = %v, want A=1", ex.Env)
}
if ex.Limits["run_limit"] != 60 || ex.Limits["rss_limit"] != 1024 {
t.Errorf("limits = %v, want run_limit 60 (s) and rss_limit 1024 (MB)", ex.Limits)
}
}
// bug-1936: extend_data the worker cannot parse (not a JSON object) would
// fail the job at launch; the submission is rejected instead.
func TestJobHandler_SubmitRejectsNonObjectExtendData(t *testing.T) {
f := newHandlerFixture(t)
f.seedQueue("normal")
for _, ex := range []string{"abc", `[1,2]`} {
before := len(f.submissions())
w := f.submit(map[string]interface{}{
"command": "sleep 1", "queue_name": "normal", "extend_data": ex,
})
if w.Code == http.StatusOK || len(f.submissions()) != before {
t.Errorf("extend_data %q: status %d, %d submits; want an error and none; body %s",
ex, w.Code, len(f.submissions())-before, w.Body.String())
}
}
}