fix(apiserver): restart copies the named job, not the first stored one (bug-1898)
RestartJobByID looked the job up with a job_id CompCondition, which the store never evaluates, and restarted jqueryReply[0] -- the first stored job -- for any ID, including unknown ones. It now parses the ID with ParseIdentitySelection, fetches the rows by key and matches them with foundJob, as ControlJobByID does: an ordinary job or <array-id>[k] is restarted, an unknown job or element is not found, and a bare array identity (several elements) is refused. Restart goes through the generateJobID/submitJob seams so the handler fixture can test it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a1526164b5
commit
c73708b81f
2 changed files with 98 additions and 52 deletions
|
|
@ -204,6 +204,7 @@ func newHandlerFixture(t *testing.T) *handlerFixture {
|
|||
g.GET("/:jobids", jc.GetJobByIDs)
|
||||
g.POST("/create", jc.CreateJob)
|
||||
g.POST("/:jobids/control", jc.ControlJobByID)
|
||||
g.POST("/:jobids/restart", jc.RestartJobByID)
|
||||
g.POST("/control", jc.ControlJob)
|
||||
f.router = r
|
||||
return f
|
||||
|
|
@ -1429,3 +1430,80 @@ func TestJobHandler_QueryPagination(t *testing.T) {
|
|||
}
|
||||
assertIDs(t, "all pages", sortedIDs(got...), s.all())
|
||||
}
|
||||
|
||||
// ========== Restart by identity (bug-1898) ==========
|
||||
|
||||
// seedRestartJob seeds a DONE job with a distinctive command.
|
||||
func (f *handlerFixture) seedRestartJob(unique uint64, command string) {
|
||||
f.t.Helper()
|
||||
f.seedJob(models.JobInfo{
|
||||
Unique_JobID: int64(unique),
|
||||
JobID: int64(backend.ExtractArrayBaseID(unique)),
|
||||
ArrayIdx: backend.ExtractArrayIdx(unique),
|
||||
JobName: "r",
|
||||
Command: command,
|
||||
JobStatus: models.JOBSTAT["DONE"],
|
||||
})
|
||||
}
|
||||
|
||||
func (f *handlerFixture) restart(jobids string) *httptest.ResponseRecorder {
|
||||
f.t.Helper()
|
||||
return f.do(http.MethodPost, "/xce/v1/jobs/"+url.PathEscape(jobids)+"/restart", map[string]interface{}{})
|
||||
}
|
||||
|
||||
// bug-1898: RestartJobByID copied jqueryReply[0] of an unevaluated job_id
|
||||
// CompCondition -- any job, not the one named. It must restart exactly the
|
||||
// named job (ordinary job or <array-id>[k] element), report an unknown one as
|
||||
// not found, and refuse a bare array identity (several jobs).
|
||||
func TestJobHandler_RestartNamedJob(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
arr := handlerTestArrayBase + 30<<15
|
||||
ord := handlerTestOrdinaryBase + 31<<15
|
||||
// decoys first, so "the first row" is never the target by accident
|
||||
f.seedRestartJob(handlerTestOrdinaryBase+32<<15, "decoy-a")
|
||||
f.seedRestartJob(backend.MakeElementID(arr, 1), "elem-1")
|
||||
f.seedRestartJob(backend.MakeElementID(arr, 2), "elem-2")
|
||||
f.seedRestartJob(ord, "ordinary")
|
||||
|
||||
cases := []struct {
|
||||
name, jobids, wantCommand string
|
||||
}{
|
||||
{"ordinary job by decimal id", strconv.FormatUint(ord, 10), "ordinary"},
|
||||
{"array element by <array-id>[k]", strconv.FormatUint(arr, 10) + "[2]", "elem-2"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
before := len(f.submitted)
|
||||
w := f.restart(c.jobids)
|
||||
if w.Code != http.StatusOK && w.Code != http.StatusCreated {
|
||||
t.Errorf("%s: status %d; body %s", c.name, w.Code, w.Body.String())
|
||||
continue
|
||||
}
|
||||
if len(f.submitted) != before+1 {
|
||||
t.Errorf("%s: %d submits, want 1", c.name, len(f.submitted)-before)
|
||||
continue
|
||||
}
|
||||
if got := f.submitted[before].Job.Command; got != c.wantCommand {
|
||||
t.Errorf("%s: restarted a copy of %q, want %q", c.name, got, c.wantCommand)
|
||||
}
|
||||
}
|
||||
|
||||
// unknown job and missing element: not found, nothing submitted
|
||||
for _, jobids := range []string{
|
||||
strconv.FormatUint(handlerTestOrdinaryBase+40<<15, 10),
|
||||
strconv.FormatUint(arr, 10) + "[9]",
|
||||
} {
|
||||
before := len(f.submitted)
|
||||
w := f.restart(jobids)
|
||||
if w.Code == http.StatusOK || w.Code == http.StatusCreated || len(f.submitted) != before {
|
||||
t.Errorf("%s: status %d, %d submits; want not found and none", jobids, w.Code, len(f.submitted)-before)
|
||||
}
|
||||
}
|
||||
|
||||
// a bare array identity names several jobs: refused, nothing submitted
|
||||
before := len(f.submitted)
|
||||
w := f.restart(strconv.FormatUint(arr, 10))
|
||||
if w.Code != http.StatusBadRequest || len(f.submitted) != before {
|
||||
t.Errorf("bare array identity: status %d, %d submits; want 400 and none; body %s",
|
||||
w.Code, len(f.submitted)-before, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue