fix(apiserver): map pidinfo from job start into the job model (interactive_job_url)

The worker packs the interactive session URL (ttyd/XJOB) into the start
report's exdata as {"url": ...}; the controller stores it on
job->job.pidinfo->exdata and the capnp Job wire carries pidinfo (pidinfo@9,
JobPIDInfo.exdata@3) — but apiserver's cJobToModel never mapped pidinfo, so
models.JobInfo.InteractiveJobURL (declared only in the query-field whitelist
JobAdditionalFields) had no producer and interactive_job_url was permanently
empty in API responses. xcecli's waitForInteractiveURL/openBrowser flow could
never fire.

- models.JobInfo: add InteractiveJobURL (json interactive_job_url, matching
  the JobAdditionalFields whitelist name).
- convert_pure.go: rawPidInfo (pids/pgids/exdata) + interactiveURLFromExdata;
  rawJobToModel marshals pids/pgids into PIDInfo (pid_info is a JSONBJFields
  entry, parsed by processJobData) and promotes the exdata URL to
  InteractiveJobURL.
- convert.go: cJobToModel extracts job.pidinfo via pidinfo_pid/pidinfo_pgid
  cgo helpers (CGO cannot index the C arrays directly).

Cluster-verified end to end on t1 (fresh apiserver deployed): tty job
(xcecli -I --tty equivalent, exdata {"interactive":1}) now returns
interactive_job_url=http://<worker>:9000 and a populated pid_info; non-tty
jobs return ""/no pid_info. All daemons healthy, zero errors in window.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
lei.chen 2026-10-14 09:20:27 +00:00
parent f661987a60
commit 74121eb72e
4 changed files with 159 additions and 1 deletions

View file

@ -17,6 +17,10 @@ static xce_res_reason_t *job_res_reason(xce_job_preason_t *p, int i) { return p-
// Helper to index into xce_alloc_t** (same CGO limitation as above) -- one
// host's per-resource allocation list, scanned for the "slots" entry.
static xce_alloc_t *host_alloc_res(xce_host_alloc_t *h, int i) { return h->allocs[i]; }
// Helpers to index into job_pidinfo_t's pid/pgid arrays (same CGO limitation).
static unsigned int pidinfo_pid(job_pidinfo_t *p, int i) { return p->pids[i]; }
static unsigned int pidinfo_pgid(job_pidinfo_t *p, int i) { return p->pgids[i]; }
*/
import "C"
@ -198,6 +202,22 @@ func cJobToModel(job *C.xce_job_t) *models.JobInfo {
}
}
}
// Pidinfo: the worker's start report — pids/pgids plus exdata, whose
// {"url": ...} JSON is the interactive session URL (ttyd/XJOB). The
// capnp Job wire carries pidinfo (Job.pidinfo@9 / JobPIDInfo.exdata@3)
// but the conversion dropped it, so interactive_job_url never reached
// the API and xcecli's browser flow could never fire.
if job.pidinfo != nil {
pi := job.pidinfo
p := &rawPidInfo{exdata: goString(pi.exdata)}
for i := 0; i < int(pi.npids); i++ {
p.pids = append(p.pids, int(C.pidinfo_pid(pi, C.int(i))))
}
for i := 0; i < int(pi.npgids); i++ {
p.pgids = append(p.pgids, int(C.pidinfo_pgid(pi, C.int(i))))
}
r.pidInfo = p
}
return rawJobToModel(r)
}