fix(apiserver): restart carries only the options a job gets (bug-1968)
RestartJobByID rebuilt the new job from the old job's row: - it accepted asked_hosts and copied asked hosts, I/O paths and out_dir, which no job gets (refused at submit since bug-1936); restart now refuses asked_hosts with 400 and no longer carries those over; - it read the JSONB columns chkpnt_info/extend_data as strings, but rows keep models.Jsonb, so env/limits were lost and a checkpointed job never restarted from its checkpoint; new rowJSON() reads both; - it read "res_req" (the column is "resreq"), losing the resource requirement, and never copied exec_home. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
801d26db06
commit
6a2c69f8bd
2 changed files with 158 additions and 45 deletions
|
|
@ -857,7 +857,7 @@ func (j JobController) RestartJobByID(ctx *gin.Context) {
|
|||
err = preCheckJobRestartRequest(&req)
|
||||
if err != nil {
|
||||
logger.Error("Failed to restart job", zap.Error(err))
|
||||
ReturnError(ctx, common.ERROR_CODE, err.Error(), nil)
|
||||
ReturnError(ctx, common.INVALID_PARAMS_CODE, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -918,18 +918,11 @@ func (j JobController) RestartJobByID(ctx *gin.Context) {
|
|||
JobStatus: models.JOBSTAT["PEND"],
|
||||
}
|
||||
|
||||
err = processJobRestartRequest(&newJob, &req)
|
||||
if err != nil {
|
||||
logger.Error("Failed to restart job", zap.Error(err))
|
||||
ReturnError(ctx, common.ERROR_CODE, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if v, ok := oldJob["job_name"].(string); ok {
|
||||
newJob.JobName = v
|
||||
}
|
||||
|
||||
if chkpntStr, ok := oldJob["chkpnt_info"].(string); ok && chkpntStr != "" {
|
||||
if chkpntStr := rowJSON(oldJob["chkpnt_info"]); chkpntStr != "" {
|
||||
var chkpntInfo map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(chkpntStr), &chkpntInfo); err == nil {
|
||||
if restartCmd, ok := chkpntInfo["restart_command"].(string); ok {
|
||||
|
|
@ -938,7 +931,7 @@ func (j JobController) RestartJobByID(ctx *gin.Context) {
|
|||
}
|
||||
if ckpt_pid, ok := chkpntInfo["ckpt_pid"].(float64); ok {
|
||||
extendData := map[string]interface{}{}
|
||||
if v, ok := oldJob["extend_data"].(string); ok && v != "" {
|
||||
if v := rowJSON(oldJob["extend_data"]); v != "" {
|
||||
_ = json.Unmarshal([]byte(v), &extendData)
|
||||
}
|
||||
if v, ok := extendData["sub_env_vars"]; ok && v != nil {
|
||||
|
|
@ -953,10 +946,6 @@ func (j JobController) RestartJobByID(ctx *gin.Context) {
|
|||
}
|
||||
extendDataBytes, _ := json.Marshal(extendData)
|
||||
newJob.ExtendData = models.Jsonb{String: string(extendDataBytes)}
|
||||
} else {
|
||||
if v, ok := oldJob["extend_data"].(string); ok && v != "" {
|
||||
newJob.ExtendData = models.Jsonb{String: v}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -984,7 +973,7 @@ func (j JobController) RestartJobByID(ctx *gin.Context) {
|
|||
if v, ok := oldJob["job_description"].(string); ok {
|
||||
newJob.JobDescription = v
|
||||
}
|
||||
if v, ok := oldJob["res_req"].(string); ok {
|
||||
if v, ok := oldJob["resreq"].(string); ok {
|
||||
newJob.ResReq = v
|
||||
}
|
||||
if v, ok := oldJob["rsv_id"].(string); ok {
|
||||
|
|
@ -999,17 +988,6 @@ func (j JobController) RestartJobByID(ctx *gin.Context) {
|
|||
if v, ok := oldJob["num_processors"].(int); ok {
|
||||
newJob.NumProcessors = v
|
||||
}
|
||||
if newJob.AskedHosts == "" {
|
||||
if v, ok := oldJob["asked_hosts"].(string); ok {
|
||||
newJob.AskedHosts = v
|
||||
}
|
||||
}
|
||||
if v, ok := oldJob["begin_time"].(time.Time); ok {
|
||||
newJob.BeginTime = models.CustomTime{Time: v}
|
||||
}
|
||||
if v, ok := oldJob["term_time"].(time.Time); ok {
|
||||
newJob.TermTime = models.CustomTime{Time: v}
|
||||
}
|
||||
if v, ok := oldJob["job_priority"].(int); ok {
|
||||
newJob.JobPriority = v
|
||||
}
|
||||
|
|
@ -1046,17 +1024,17 @@ func (j JobController) RestartJobByID(ctx *gin.Context) {
|
|||
if v, ok := oldJob["chkpnt_dir"].(string); ok {
|
||||
newJob.ChkpntDir = v
|
||||
}
|
||||
if v, ok := oldJob["out_dir"].(string); ok {
|
||||
newJob.OutDir = v
|
||||
// bug-1968: asked hosts, begin/term time, in/out/err files and out_dir
|
||||
// are not carried over: no job gets them (refused at submit since
|
||||
// bug-1936), so the restarted job would only show them. Extended data
|
||||
// (env, limits) and exec home are, checkpointed or not.
|
||||
if newJob.ExtendData.String == "" {
|
||||
if v := rowJSON(oldJob["extend_data"]); v != "" {
|
||||
newJob.ExtendData = models.Jsonb{String: v}
|
||||
}
|
||||
}
|
||||
if v, ok := oldJob["in_file"].(string); ok {
|
||||
newJob.InFile = v
|
||||
}
|
||||
if v, ok := oldJob["err_file"].(string); ok {
|
||||
newJob.ErrFile = v
|
||||
}
|
||||
if v, ok := oldJob["out_file"].(string); ok {
|
||||
newJob.OutFile = v
|
||||
if v, ok := oldJob["exec_home"].(string); ok {
|
||||
newJob.ExecHome = v
|
||||
}
|
||||
if v, ok := oldJob["login_shell"].(string); ok {
|
||||
newJob.LoginShell = v
|
||||
|
|
@ -1347,14 +1325,31 @@ func preCheckSubmitOptions(req *models.JobSubmitRequest) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// preCheckJobRestartRequest refuses a restart option the restarted job would
|
||||
// not get (bug-1968, as preCheckSubmitOptions does for submit, bug-1936):
|
||||
// asked hosts have no field in the controller's submit data.
|
||||
func preCheckJobRestartRequest(req *models.JobRestartRequest) error {
|
||||
logger.Debug("Enter preCheckJobRestartRequest")
|
||||
if err := validateAskedHosts(req.AskedHosts); err != nil {
|
||||
return err
|
||||
if strings.TrimSpace(req.AskedHosts) != "" {
|
||||
return fmt.Errorf("job restart option asked_hosts not supported: the job would not get it")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rowJSON returns a JSON column of a stored job row as a string: rows come
|
||||
// from store.StructToOrderMap, which keeps each field's Go type, so a JSONB
|
||||
// column (chkpnt_info, extend_data) is a models.Jsonb, not a string
|
||||
// (bug-1968).
|
||||
func rowJSON(v interface{}) string {
|
||||
switch j := v.(type) {
|
||||
case models.Jsonb:
|
||||
return j.String
|
||||
case string:
|
||||
return j
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// validateResreqSyntax is the resource-request syntax check used by
|
||||
// preCheckJobRequest; a variable so tests can substitute it (bug-1744).
|
||||
var validateResreqSyntax = backend.ValidateResreqSyntax
|
||||
|
|
@ -1909,13 +1904,6 @@ func parseTransferFiles(transferFileStr string) (localFile, operator, remoteFile
|
|||
return localFile, operator, remoteFile, nil
|
||||
}
|
||||
|
||||
func processJobRestartRequest(newJob *models.JobInfo, req *models.JobRestartRequest) error {
|
||||
logger.Debug("Enter processJobRestartRequest function", zap.Any("req", req))
|
||||
if req.AskedHosts != "" {
|
||||
newJob.AskedHosts = req.AskedHosts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func processJobRequest(ctx *gin.Context, req *models.JobSubmitRequest, defaultValues map[string]string) error {
|
||||
|
||||
if req.InFile != "" {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue