forked from lei.chen/myxce
Collapses a four-step conversion chain (time_t → int64 → time.Time → RFC3339 string → string → time.Time) to a direct int64 pass-through: C time_t → rawJobData int64 → JobInfo int64 → JSON integer → QueryJob int64 → time.Unix() for display Changes: - apiserver/models/jobs.go: SubmitTime, QueueTime, StartTime, EndTime, ReserveTime in JobInfo and JobAdditionalFields: time.Time → int64 - apiserver/models/common.go: ActionControl.SubmitTime: time.Time → int64 - backend/convert_pure.go: remove time.Unix() calls; assign int64 directly - backend/convert_test.go: update time assertions to compare int64 values - xcecli/models/jobs.go: SubmitTime, StartTime, EndTime, ReserveTime in QueryJob: string/time.Time → int64; remove time import - xcecli/common/output/time.go: replace parseJobTime(string) with unixToTime(int64) and fmtUnixTime(int64) helpers - xcecli/common/output/page_print_jobs.go: replace parseJobTime() calls with unixToTime()/fmtUnixTime(); remove !startTime.IsZero() guard (unixToTime returns false for ts<=0, making it redundant) - xcecli/common/output/page_print_array_elements_jobs.go: replace string-split+time.Parse submit-time block with fmtUnixTime(); remove unused time import Side effect: eliminates the "0001-01-01T00:00:00Z" zero-time serialization issue at the source — zero timestamps are now 0 (integer) and unixToTime/fmtUnixTime return false/"-" for ts<=0 without special- casing needed in callers.
524 lines
18 KiB
Go
Executable file
524 lines
18 KiB
Go
Executable file
package models
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
const (
|
|
HEADER_JOB_JOBID = "JOBID"
|
|
HEADER_JOB_USER = "USER"
|
|
HEADER_JOB_STATUS = "STATUS"
|
|
HEADER_JOB_QUEUE = "QUEUE"
|
|
HEADER_JOB_FROM_HOST = "FROM_HOST"
|
|
HEADER_JOB_EXEC_HOST = "EXEC_HOST"
|
|
HEADER_JOB_COMMAND = "COMMAND"
|
|
HEADER_JOB_NAME = "JOB_NAME"
|
|
HEADER_JOB_SUBMIT_TIME = "SUBMIT_TIME"
|
|
HEADER_JOB_CORE_LIMIT = "CORE_LIMIT"
|
|
HEADER_JOB_MEM_LIMIT = "MEM_LIMIT"
|
|
HEADER_JOB_PID = "PID"
|
|
HEADER_JOB_PGID = "PGID"
|
|
HEADER_JOB_PROJECT_NAME = "PROJECT_NAME"
|
|
HEADER_JOB_START_TIME = "START_TIME"
|
|
HEADER_JOB_END_TIME = "END_TIME"
|
|
HEADER_JOB_CWD = "CWD"
|
|
HEADER_JOB_CPU_TIME = "CPU_TIME"
|
|
HEADER_JOB_RESREQ = "RESREQ"
|
|
//HEADER_JOB_PRIORITY = "PRIORITY"
|
|
HEADER_JOB_DESCRIPTION = "DESCRIPTION"
|
|
HEADER_JOB_LAUNCH_TYPE = "LAUNCH_TYPE"
|
|
HEADER_JOB_NUM_PROCESSORS = "NUM_PROCESSORS"
|
|
HEADER_JOB_INTERACTIVE_URL = "INTERACTIVE_URL"
|
|
HEADER_JOB_PENDING_REASON = "PENDING_REASON"
|
|
|
|
// second line
|
|
HEADER_JOB_R15S = "r15s"
|
|
HEADER_JOB_R1M = "r1m"
|
|
HEADER_JOB_R15M = "r15m"
|
|
HEADER_JOB_UT = "ut"
|
|
HEADER_JOB_PG = "pg"
|
|
HEADER_JOB_IO = "io"
|
|
HEADER_JOB_LS = "ls"
|
|
HEADER_JOB_IT = "it"
|
|
HEADER_JOB_TMP = "tmp"
|
|
HEADER_JOB_SWP = "swp"
|
|
HEADER_JOB_MEM = "mem"
|
|
)
|
|
|
|
var JobLoadSchedulingHeaders = []string{
|
|
HEADER_JOB_R15S,
|
|
HEADER_JOB_R1M,
|
|
HEADER_JOB_R15M,
|
|
HEADER_JOB_UT,
|
|
HEADER_JOB_PG,
|
|
HEADER_JOB_IO,
|
|
HEADER_JOB_LS,
|
|
HEADER_JOB_IT,
|
|
HEADER_JOB_TMP,
|
|
HEADER_JOB_SWP,
|
|
HEADER_JOB_MEM,
|
|
}
|
|
|
|
|
|
|
|
// for array jobs
|
|
const (
|
|
HEADER_ARRAY_JOB_JOBID = "JOBID"
|
|
HEADER_ARRAY_JOB_SPEC = "ARRAY_SPEC"
|
|
HEADER_ARRAY_JOB_OWNER = "OWNER"
|
|
HEADER_ARRAY_JOB_NJOBS = "NJOBS"
|
|
HEADER_ARRAY_JOB_PEND = "PEND"
|
|
HEADER_ARRAY_JOB_DONE = "DONE"
|
|
HEADER_ARRAY_JOB_RUN = "RUN"
|
|
HEADER_ARRAY_JOB_EXIT = "EXIT"
|
|
HEADER_ARRAY_JOB_SSUSP = "SSUSP"
|
|
HEADER_ARRAY_JOB_USUSP = "USUSP"
|
|
HEADER_ARRAY_JOB_PSUSP = "PSUSP"
|
|
)
|
|
|
|
type JobsResponse struct {
|
|
Code int `json:"code"`
|
|
Msg string `json:"msg"`
|
|
Data struct {
|
|
Count int `json:"count"`
|
|
Rows []QueryJob `json:"rows"`
|
|
Page_token string `json:"page_token"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
type Jsonb struct {
|
|
String string
|
|
}
|
|
|
|
// Job define the data structure in the job info
|
|
type QueryJob struct {
|
|
JobID int64 `json:"job_id"`
|
|
SubmitUser string `json:"submit_user"`
|
|
JobStatus string `json:"job_status"`
|
|
JobName string `json:"job_name"`
|
|
Command string `json:"command"`
|
|
QueueName string `json:"queue_name"`
|
|
ProjectName string `json:"project_name"`
|
|
UserGroup string `json:"user_group"`
|
|
LicenseProject string `json:"license_project"`
|
|
JobDescription string `json:"job_description"`
|
|
Resreq string `json:"resreq"`
|
|
RsvID string `json:"rsv_id"`
|
|
SLA string `json:"sla"`
|
|
MaxNumProcessors int `json:"maxnum_processors"`
|
|
NumProcessors int `json:"num_processors"`
|
|
AskedHosts string `json:"asked_hosts"`
|
|
BeginTime string `json:"begin_time"`
|
|
TermTime string `json:"term_time"`
|
|
UserPriority int `json:"user_priority"`
|
|
DependCond string `json:"depend_cond"`
|
|
SubmitTime int64 `json:"submit_time"`
|
|
ReserveTime int64 `json:"reserve_time"`
|
|
StartTime int64 `json:"start_time"`
|
|
EndTime int64 `json:"end_time"`
|
|
JobRusage string `json:"job_rusage"`
|
|
MaxMem float64 `json:"max_mem"`
|
|
AvgMem float64 `json:"avg_mem"`
|
|
UsedMem float64 `json:"used_mem"`
|
|
CPUTime int `json:"cpu_time"`
|
|
PeakCPUTime int `json:"peak_cpu_time"`
|
|
PeakCPUDuration float64 `json:"peak_cpu_duration"`
|
|
MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage
|
|
AvgCPUEfficiency float64 `json:"avg_cpu_efficiency"` // Average CPU efficiency in percentage
|
|
PeakCPUEfficiency float64 `json:"peak_cpu_efficiency"` // Peak CPU efficiency in percentage
|
|
NumHosts int `json:"num_hosts"` // Number of hosts used by the job
|
|
FirstHost string `json:"first_host"` // First host where the job ran
|
|
AllocSlot string `json:"alloc_slots"` // Allocated slot information
|
|
Duration int `json:"duration"`
|
|
Umask int `json:"umask"`
|
|
CWD string `json:"cwd"`
|
|
SubmitHomeDir string `json:"submit_home_dir"`
|
|
FromHost string `json:"from_host"`
|
|
ExecHosts ExecHosts `json:"exec_hosts"`
|
|
ExitStatus ExitStatusValue `json:"exit_status"`
|
|
ExecUID int `json:"exec_uid"`
|
|
ExecHome string `json:"exec_home"`
|
|
ExecCWD string `json:"exec_cwd"`
|
|
ExecUserName string `json:"exec_user_name"`
|
|
JobPriority int `json:"job_priority"`
|
|
ChargedSAAP string `json:"charged_SAAP"`
|
|
PreExecCmd string `json:"pre_exec_cmd"`
|
|
PostExecCmd string `json:"post_exec_cmd"`
|
|
RequeueErrorValues string `json:"requeue_error_values"`
|
|
NumLicenses int `json:"num_licenses"`
|
|
LicenseNames []string `json:"license_names"`
|
|
RunTime int `json:"run_time"`
|
|
RunTimeEstimation int `json:"run_time_estimation"`
|
|
MigThreshold int `json:"mig_threshold"`
|
|
ChkpntPeriod int `json:"chkpnt_period"`
|
|
ChkpntDir string `json:"chkpnt_dir"`
|
|
OutDir string `json:"out_dir"`
|
|
InFile string `json:"in_file"`
|
|
OutFile string `json:"out_file"`
|
|
ErrFile string `json:"err_file"`
|
|
MailUser string `json:"mail_user"`
|
|
LoginShell string `json:"login_shell"`
|
|
ExceptList string `json:"except_list"`
|
|
RLimits64 []int64 `json:"rlimits64"`
|
|
SubEnvVars string `json:"sub_env_vars"`
|
|
PendingReason string `json:"pending_reason"`
|
|
JobStartInfo string `json:"job_start_info"`
|
|
JobExecInfo string `json:"job_exec_info"`
|
|
CoreLimit float64 `json:"core_limit"`
|
|
CPULimit string `json:"cpu_limit"`
|
|
RunLimit string `json:"run_limit"`
|
|
FileLimit float64 `json:"file_limit"`
|
|
DataLimit float64 `json:"data_limit"`
|
|
StackLimit float64 `json:"stack_limit"`
|
|
MemLimit float64 `json:"mem_limit"`
|
|
SwapLimit float64 `json:"swap_limit"`
|
|
ProcessLimit float64 `json:"process_limit"`
|
|
ThreadLimit float64 `json:"thread_limit"`
|
|
NumFiles int `json:"num_files"`
|
|
ArrayIdx int `json:"array_idx"` //
|
|
UniqueJobID int64 `json:"unique_job_id"` //
|
|
ExtendData JobExtendData `json:"extend_data"`
|
|
InteractiveJobURL string `json:"interactive_job_url"`
|
|
PGIDs IntSlice `json:"pgids"`
|
|
PID IntSlice `json:"pids"`
|
|
LaunchType string `json:"launch_type"`
|
|
}
|
|
|
|
var JobFieldsMap = map[string]string{
|
|
HEADER_JOB_JOBID: "job_id",
|
|
HEADER_JOB_USER: "submit_user",
|
|
HEADER_JOB_STATUS: "job_status",
|
|
HEADER_JOB_QUEUE: "queue_name",
|
|
HEADER_JOB_COMMAND: "command",
|
|
HEADER_JOB_NAME: "job_name",
|
|
HEADER_JOB_SUBMIT_TIME: "submit_time",
|
|
HEADER_JOB_FROM_HOST: "from_host",
|
|
HEADER_JOB_EXEC_HOST: "exec_hosts",
|
|
HEADER_JOB_CORE_LIMIT: "core_limit",
|
|
HEADER_JOB_MEM_LIMIT: "mem_limit",
|
|
HEADER_JOB_PID: "pids",
|
|
HEADER_JOB_PGID: "pgids",
|
|
HEADER_JOB_PROJECT_NAME: "project_name",
|
|
HEADER_JOB_START_TIME: "start_time",
|
|
HEADER_JOB_END_TIME: "end_time",
|
|
HEADER_JOB_CWD: "cwd",
|
|
HEADER_JOB_CPU_TIME: "cpu_time",
|
|
HEADER_JOB_RESREQ: "resreq",
|
|
HEADER_JOB_DESCRIPTION: "job_description",
|
|
HEADER_JOB_LAUNCH_TYPE: "launch_type",
|
|
HEADER_JOB_NUM_PROCESSORS: "num_processors",
|
|
HEADER_JOB_INTERACTIVE_URL: "interactive_job_url",
|
|
HEADER_JOB_PENDING_REASON: "pending_reason",
|
|
//job_filter.HEADER_JOB_PRIORITY: "job_priority",
|
|
}
|
|
|
|
type IntSlice []int
|
|
|
|
func (s *IntSlice) UnmarshalJSON(data []byte) error {
|
|
// parse array
|
|
var arr []int
|
|
if err := json.Unmarshal(data, &arr); err == nil {
|
|
*s = arr
|
|
return nil
|
|
}
|
|
// parse {}
|
|
var obj map[string]interface{}
|
|
if err := json.Unmarshal(data, &obj); err == nil {
|
|
|
|
if len(obj) == 0 {
|
|
*s = []int{}
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("cannot unmarshal IntSlice %s", string(data))
|
|
}
|
|
|
|
// ExecHosts represents the execution hosts for a job
|
|
type ExecHosts struct {
|
|
HostSlots []HostSlot `json:"host_slots"`
|
|
NumHosts int `json:"num_hosts"`
|
|
}
|
|
|
|
// String returns the string representation of ExecHosts
|
|
// If HostSlots is empty, return "-"
|
|
// Otherwise, return the JSON formatted string
|
|
func (e ExecHosts) String() string {
|
|
if len(e.HostSlots) == 0 {
|
|
return "-"
|
|
}
|
|
|
|
// Use json.Marshal to convert the structure to a JSON string
|
|
data, err := json.Marshal(e)
|
|
if err != nil {
|
|
return "-"
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
// UnmarshalJSON customizes the JSON parsing method, compatible with both string "-" and structure formats
|
|
func (e *ExecHosts) UnmarshalJSON(data []byte) error {
|
|
// JSON null means no allocation — treat as empty
|
|
if string(data) == "null" {
|
|
e.HostSlots = []HostSlot{}
|
|
e.NumHosts = 0
|
|
return nil
|
|
}
|
|
|
|
// First try to parse as a string
|
|
var str string
|
|
if err := json.Unmarshal(data, &str); err == nil {
|
|
// Empty string or "-" both mean no hosts
|
|
if str == "" || str == "-" {
|
|
e.HostSlots = []HostSlot{}
|
|
e.NumHosts = 0
|
|
return nil
|
|
}
|
|
// Any other non-empty string is a single hostname
|
|
e.HostSlots = []HostSlot{{HostName: str, NumSlots: 1}}
|
|
e.NumHosts = 1
|
|
return nil
|
|
}
|
|
|
|
// If it's not a string, try to parse as an ExecHosts structure
|
|
type Alias ExecHosts
|
|
aux := &struct {
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(e),
|
|
}
|
|
if err := json.Unmarshal(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HostSlot represents a single host slot
|
|
type HostSlot struct {
|
|
HostName string `json:"host_name"`
|
|
NumSlots int `json:"num_slots"`
|
|
}
|
|
|
|
type JobSubmitRequest struct {
|
|
JobName string `json:"job_name,omitempty" binding:"required"`
|
|
Command string `json:"command" binding:"required"`
|
|
QueueName string `json:"queue_name,omitempty"` // set to DEFAULT_QUEUE in config
|
|
ProjectName string `json:"project_name,omitempty"` // default project: "default"
|
|
UserGroup string `json:"user_group,omitempty"`
|
|
LicenseProject string `json:"license_project,omitempty"`
|
|
JobDescription string `json:"job_description,omitempty"`
|
|
Resreq string `json:"resreq,omitempty"` // default: "type == local"
|
|
Gpureq string `json:"gpureq,omitempty"`
|
|
RsvID string `json:"rsv_id,omitempty"`
|
|
SLA string `json:"sla,omitempty"`
|
|
MaxNumProcessors int `json:"maxnum_processors,omitempty"`
|
|
NumProcessors int `json:"num_processors,omitempty"`
|
|
AskedHosts string `json:"asked_hosts,omitempty"`
|
|
BeginTime string `json:"begin_time,omitempty"`
|
|
TermTime string `json:"term_time,omitempty"`
|
|
UserPriority int `json:"user_priority,omitempty"`
|
|
DependCond string `json:"depend_cond,omitempty"`
|
|
JobGroup string `json:"job_group,omitempty"`
|
|
HostSpec string `json:"host_spec,omitempty"`
|
|
Env []string `json:"env,omitempty"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
Exclusive bool `json:"exclusive,omitempty"`
|
|
Rerunable bool `json:"rerunable,omitempty"`
|
|
Interactive int `json:"interactive,omitempty"`
|
|
WarningTimePeriod int `json:"warning_time_period,omitempty"`
|
|
WarningAction string `json:"warning_action,omitempty"`
|
|
DataSpecFile string `json:"data_spec_file,omitempty"`
|
|
TransferFiles []string `json:"transfer_files,omitempty"`
|
|
PreExecCmd string `json:"pre_exec_cmd,omitempty"`
|
|
PostExecCmd string `json:"post_exec_cmd,omitempty"`
|
|
RequeueErrorValues string `json:"requeue_error_values,omitempty"`
|
|
RunTimeEstimation int `json:"run_time_estimation,omitempty"`
|
|
MigThreshold int `json:"mig_threshold,omitempty"`
|
|
ChkpntPeriod int `json:"chkpnt_period,omitempty"`
|
|
ChkpntDir string `json:"chkpnt_dir,omitempty"`
|
|
InitChkpntPeriod int `json:"init_chkpnt_period,omitempty"`
|
|
CWD string `json:"cwd,omitempty"`
|
|
OutDir string `json:"out_dir,omitempty"`
|
|
InFile string `json:"in_file,omitempty"`
|
|
OutFile string `json:"out_file,omitempty"`
|
|
ErrFile string `json:"err_file,omitempty"`
|
|
Mailuser string `json:"mailuser,omitempty"`
|
|
LoginShell string `json:"login_shell,omitempty"`
|
|
ExceptList string `json:"except_list,omitempty"`
|
|
CPULimit string `json:"cpu_limit,omitempty"`
|
|
RunLimit string `json:"run_limit,omitempty"`
|
|
MemLimit string `json:"mem_limit,omitempty"`
|
|
SwapLimit string `json:"swap_limit,omitempty"`
|
|
CoreLimit string `json:"core_limit,omitempty"`
|
|
DataLimit string `json:"data_limit,omitempty"`
|
|
FileLimit string `json:"file_limit,omitempty"`
|
|
ProcessLimit string `json:"process_limit,omitempty"`
|
|
StackLimit string `json:"stack_limit,omitempty"`
|
|
ThreadLimit string `json:"thread_limit,omitempty"`
|
|
ShellLimit string `json:"shell_limit,omitempty"`
|
|
NumFiles int `json:"num_files,omitempty"`
|
|
LaunchType string `json:"launch_type,omitempty"`
|
|
}
|
|
|
|
// JobSubmitData define the data structure in the job submit response
|
|
type JobSubmitData struct {
|
|
JobID int64 `json:"jobid"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// JobSubmitResponse define the data structure in the job submit response
|
|
type JobSubmitResponse struct {
|
|
Code int `json:"code"`
|
|
Msg string `json:"msg"`
|
|
Data JobSubmitData `json:"data"`
|
|
Count int `json:"count"`
|
|
}
|
|
|
|
// UploadedFile represents the uploaded file
|
|
type UploadedFile struct {
|
|
DestFile string `json:"dest_file"` // dest file path
|
|
SrcFile string `json:"src_file"` // src file name
|
|
WebURL string `json:"web_url"` // file URL
|
|
}
|
|
|
|
// UploadResponse represents the file upload response
|
|
type UploadResponse struct {
|
|
Code int `json:"code"`
|
|
Msg string `json:"msg"`
|
|
Data struct {
|
|
UploadedFiles []UploadedFile `json:"accepted"`
|
|
Failed []FailedFile `json:"failed"`
|
|
Status string `json:"status"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
type FailedFile struct {
|
|
FileName string `json:"file_name"`
|
|
Msg string `json:"msg"`
|
|
}
|
|
|
|
// ------- job control -------------
|
|
type JobControlFilter struct {
|
|
JobName string `json:"job_name,omitempty"`
|
|
JobGroupName string `json:"job_group_name,omitempty"`
|
|
QueueName string `json:"queue_name,omitempty"`
|
|
Users string `json:"users,omitempty"`
|
|
ExecHosts string `json:"exec_hosts,omitempty"`
|
|
SLA string `json:"sla,omitempty"`
|
|
AllJobs bool `json:"all_jobs,omitempty"`
|
|
}
|
|
|
|
type JobControlRequest struct {
|
|
Filter JobControlFilter `json:"filter" binding:"required"`
|
|
Action string `json:"action" binding:"required"`
|
|
Signal string `json:"signal,omitempty"`
|
|
Comment string `json:"comment,omitempty"`
|
|
ForceKill bool `json:"force_kill,omitempty"`
|
|
}
|
|
|
|
type JobControlJobIDRequest struct {
|
|
Action string `json:"action" binding:"required"`
|
|
Signal string `json:"signal,omitempty"` // signal value
|
|
Comment string `json:"comment,omitempty"`
|
|
ForceKill bool `json:"force_kill,omitempty"`
|
|
}
|
|
|
|
type JobRestartRequest struct {
|
|
AskedHosts string `json:"asked_hosts,omitempty"`
|
|
}
|
|
|
|
type JobRestartResponse struct {
|
|
Code int `json:"code"`
|
|
Msg string `json:"msg"`
|
|
Data *JobRestartResponseData `json:"data"`
|
|
}
|
|
type JobRestartResponseData struct {
|
|
JobID int64 `json:"jobid"`
|
|
QueueName string `json:"queue_name"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type JobControlResponse struct {
|
|
Code int `json:"code"`
|
|
Msg string `json:"msg"`
|
|
Data *JobControlResponseData `json:"data"`
|
|
}
|
|
|
|
type JobControlResponseData struct {
|
|
Accepted []*JobControlResponseItem `json:"accepted"`
|
|
Failed []*JobControlResponseItem `json:"failed"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
type JobControlResponseItem struct {
|
|
JobID int64 `json:"job_id"`
|
|
ArrayIdx int `json:"array_idx,omitempty"`
|
|
Msg string `json:"msg"`
|
|
}
|
|
|
|
// job array statistics data structure
|
|
type ArrayJobStatsData struct {
|
|
JobID int64 `json:"job_id"`
|
|
ArrayJobName string `json:"array_job_name"`
|
|
SubmitUser string `json:"submit_user"`
|
|
NumJobs int `json:"num_jobs"`
|
|
NumDone int `json:"num_done"`
|
|
NumExit int `json:"num_exit"`
|
|
NumRun int `json:"num_run"`
|
|
NumPend int `json:"num_pend"`
|
|
NumSSusp int `json:"num_ssusp"`
|
|
NumUSusp int `json:"num_ususp"`
|
|
NumPSusp int `json:"num_psusp"`
|
|
}
|
|
|
|
// job array statistics response data structure
|
|
type ArrayJobsStatsResponse struct {
|
|
Code int `json:"code"`
|
|
Msg string `json:"msg"`
|
|
Data struct {
|
|
Count int `json:"count"`
|
|
Rows []ArrayJobStatsData `json:"rows"`
|
|
Page_token string `json:"page_token"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
type JobExtendData struct {
|
|
Interactive int `json:"interactive"`
|
|
InteractiveJobURL string `json:"interactive_job_url"`
|
|
Limits struct {
|
|
CoreLimit int `json:"core_limit"`
|
|
} `json:"limits"`
|
|
}
|
|
|
|
func (j *QueryJob) UnmarshalJSON(data []byte) error {
|
|
type Alias QueryJob
|
|
var a Alias
|
|
if err := UnmarshalOrderMapRow(data, &a); err != nil {
|
|
return err
|
|
}
|
|
*j = QueryJob(a)
|
|
return nil
|
|
}
|
|
|
|
// ExitStatusValue handles exit_status which can be null, a number, or a JSON string.
|
|
type ExitStatusValue string
|
|
|
|
func (e *ExitStatusValue) UnmarshalJSON(data []byte) error {
|
|
s := string(data)
|
|
if s == "null" || s == "" {
|
|
*e = ""
|
|
return nil
|
|
}
|
|
// strip quotes if it's a JSON string
|
|
if len(s) >= 2 && s[0] == '"' {
|
|
var str string
|
|
if err := json.Unmarshal(data, &str); err != nil {
|
|
return err
|
|
}
|
|
*e = ExitStatusValue(str)
|
|
return nil
|
|
}
|
|
// bare number or other value — store as-is
|
|
*e = ExitStatusValue(s)
|
|
return nil
|
|
}
|