fix(apiserver): reject submit options the job never gets; add exec_home (bug-1936)
Cause: CreateJob accepted in_file, out_file, err_file, out_dir, asked_hosts, begin_time, term_time and transfer_files and returned success, but neither the controller's submit data/start info nor the worker carries any of them (the worker never stages transferred files), so they were silently dropped. Exec home had no submit field at all, and a relative cwd would have been resolved against the worker unit's directory. Fix: new preCheckSubmitOptions() runs right after binding, before file uploads and ID allocation, and answers 400 naming every unsupported option that is set; cwd and the new exec_home field must be absolute and fit the worker's job_assignment_t buffers (1023 bytes). exec_home goes to JobInfo.ExecHome and from there (submitStartInfo) to the start info's exechome. num_files already reaches exdata as nofile_limit; a handler test now pins it. The array %J/%I submit-time substitution test (9.4) checks cwd instead of out_file/err_file, which are now refused. Swagger regenerated with `swag init -g cmd/main.go -o docs`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
725b1d23fd
commit
edad8c7385
6 changed files with 200 additions and 12 deletions
|
|
@ -694,15 +694,17 @@ func TestJobHandler_SubmitOrdinaryUnchanged(t *testing.T) {
|
|||
}
|
||||
|
||||
// TestJobHandler_SubmitArrayFileNameSubstitution: at submission a new
|
||||
// array's file names get the first element's values, %I = 1 and
|
||||
// %J = <array-id>[1] (9.4).
|
||||
// array's path names get the first element's values, %I = 1 and
|
||||
// %J = <array-id>[1] (9.4). Checked on cwd: out_file/err_file are refused
|
||||
// since bug-1936 (the worker never writes them), cwd takes the same
|
||||
// substitution.
|
||||
func TestJobHandler_SubmitArrayFileNameSubstitution(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
f.seedQueue("normal")
|
||||
|
||||
submitData(t, f.submit(map[string]interface{}{
|
||||
"job_name": "arr[1-3]", "command": "sleep 1", "queue_name": "normal",
|
||||
"out_file": "out.%J.%I", "err_file": "err.%J.%I"}))
|
||||
"cwd": "/w/out.%J.%I"}))
|
||||
|
||||
subs := f.submissions()
|
||||
if len(subs) != 1 {
|
||||
|
|
@ -713,9 +715,8 @@ func TestJobHandler_SubmitArrayFileNameSubstitution(t *testing.T) {
|
|||
if got := backend.FormatJobID(int64(backend.MakeElementID(handlerTestArrayBase, 1))); got != elem {
|
||||
t.Fatalf("first element formats as %q, want %q", got, elem)
|
||||
}
|
||||
wantOut, wantErr := "out."+elem+".1", "err."+elem+".1"
|
||||
if subs[0].Job.OutFile != wantOut || subs[0].Job.ErrFile != wantErr {
|
||||
t.Errorf("files = {%q, %q}, want {%q, %q}", subs[0].Job.OutFile, subs[0].Job.ErrFile, wantOut, wantErr)
|
||||
if want := "/w/out." + elem + ".1"; subs[0].Job.CWD != want {
|
||||
t.Errorf("cwd = %q, want %q", subs[0].Job.CWD, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1603,3 +1604,104 @@ func TestJobHandler_SubmitQueueRejectedByController(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bug-1936: a submit option the job never gets must be refused with a 400
|
||||
// naming it, not accepted and dropped. Neither the controller's submit data
|
||||
// nor the worker carries stdin/stdout/stderr paths, an output directory,
|
||||
// asked hosts or a begin/termination time, and the worker never stages
|
||||
// transferred files in or out.
|
||||
func TestJobHandler_SubmitRejectsUnsupportedOptions(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
field string
|
||||
value interface{}
|
||||
}{
|
||||
{"in_file", "/tmp/in.txt"},
|
||||
{"out_file", "/tmp/out.%J"},
|
||||
{"err_file", "/tmp/err.%J"},
|
||||
{"out_dir", "/tmp/outdir"},
|
||||
{"asked_hosts", "host1"},
|
||||
{"begin_time", "2030-01-02 03:04"},
|
||||
{"term_time", "2030-01-02 03:04"},
|
||||
{"transfer_files", []string{"/tmp/a.txt > a.txt"}},
|
||||
} {
|
||||
f := newHandlerFixture(t)
|
||||
f.seedQueue("normal")
|
||||
w := f.submit(map[string]interface{}{
|
||||
"command": "sleep 1", "queue_name": "normal", tc.field: tc.value,
|
||||
})
|
||||
assert400(t, tc.field, w, tc.field)
|
||||
assert400(t, tc.field, w, "not supported")
|
||||
f.assertNothingCreated()
|
||||
}
|
||||
}
|
||||
|
||||
// bug-1936: the job runs in cwd and with HOME set to exec_home on the
|
||||
// worker, so both must be absolute paths that fit the worker's
|
||||
// job_assignment_t buffers (PATH_LENGTH, 1024 bytes with the NUL).
|
||||
func TestJobHandler_SubmitRejectsBadCwdAndExecHome(t *testing.T) {
|
||||
long := "/" + strings.Repeat("d", 1023)
|
||||
for _, tc := range []struct {
|
||||
field, value, want string
|
||||
}{
|
||||
{"cwd", "relative/dir", "absolute"},
|
||||
{"cwd", long, "too long"},
|
||||
{"exec_home", "home/u", "absolute"},
|
||||
{"exec_home", long, "too long"},
|
||||
} {
|
||||
f := newHandlerFixture(t)
|
||||
f.seedQueue("normal")
|
||||
w := f.submit(map[string]interface{}{
|
||||
"command": "sleep 1", "queue_name": "normal", tc.field: tc.value,
|
||||
})
|
||||
assert400(t, tc.field+"="+tc.value[:6], w, tc.want)
|
||||
f.assertNothingCreated()
|
||||
}
|
||||
}
|
||||
|
||||
// bug-1936: exec_home is a submit field and reaches the submitted job (and
|
||||
// from there submitStartInfo's exechome), next to cwd.
|
||||
func TestJobHandler_SubmitCarriesCwdAndExecHome(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
f.seedQueue("normal")
|
||||
|
||||
submitData(t, f.submit(map[string]interface{}{
|
||||
"command": "sleep 1", "queue_name": "normal",
|
||||
"cwd": "/data/run/%J", "exec_home": "/home/alice",
|
||||
}))
|
||||
subs := f.submissions()
|
||||
if len(subs) != 1 {
|
||||
t.Fatalf("submitJob calls = %d, want 1", len(subs))
|
||||
}
|
||||
wantCwd := "/data/run/" + backend.FormatJobID(int64(handlerTestOrdinaryBase))
|
||||
if subs[0].Job.CWD != wantCwd || subs[0].Job.ExecHome != "/home/alice" {
|
||||
t.Errorf("cwd, exec_home = %q, %q; want %q, %q",
|
||||
subs[0].Job.CWD, subs[0].Job.ExecHome, wantCwd, "/home/alice")
|
||||
}
|
||||
}
|
||||
|
||||
// bug-1936: num_files (and shell_limit.num_files) is the job's open-file
|
||||
// limit; it lands in the exdata limits as nofile_limit, the key the
|
||||
// worker's parse_limits() reads.
|
||||
func TestJobHandler_SubmitNofileLimitInExdata(t *testing.T) {
|
||||
for _, body := range []map[string]interface{}{
|
||||
{"command": "sleep 1", "queue_name": "normal", "num_files": 2048},
|
||||
{"command": "sleep 1", "queue_name": "normal", "shell_limit": map[string]interface{}{"num_files": 2048}},
|
||||
} {
|
||||
f := newHandlerFixture(t)
|
||||
f.seedQueue("normal")
|
||||
submitData(t, f.submit(body))
|
||||
subs := f.submissions()
|
||||
if len(subs) != 1 {
|
||||
t.Fatalf("submitJob calls = %d, want 1", len(subs))
|
||||
}
|
||||
var ex struct {
|
||||
Limits map[string]float64 `json:"limits"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(subs[0].Job.ExtendData.String), &ex); err != nil {
|
||||
t.Fatalf("exdata %q: %v", subs[0].Job.ExtendData.String, err)
|
||||
}
|
||||
if ex.Limits["nofile_limit"] != 2048 {
|
||||
t.Errorf("exdata %q: nofile_limit = %v, want 2048", subs[0].Job.ExtendData.String, ex.Limits["nofile_limit"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue