Merge Go frontend (apiserver + xcecli) into meson build

This commit is contained in:
Rongsong Shen 2026-04-14 11:20:17 +08:00
parent 43dc12a36c
commit c9dc8c4676
205 changed files with 30793 additions and 328 deletions

View file

@ -0,0 +1,22 @@
package models
type ClusterResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Count int `json:"count"`
Rows []ClusterInfo `json:"rows"`
Page_token string `json:"page_token"`
} `json:"data"`
}
type ClusterInfo struct {
ClusterName string `json:"cluster_name"`
Version string `json:"version"`
Status string `json:"status"`
ControllerName string `json:"controller_name"`
Resources interface{} `json:"resources"`
HostTypes interface{} `json:"host_types"`
HostModels interface{} `json:"host_models"`
ClusterAdmins interface{} `json:"cluster_admins"`
}

View file

@ -0,0 +1,39 @@
package models
import (
"encoding/json"
"testing"
)
func TestQueryJob_ExitStatus_Null(t *testing.T) {
raw := `{"Fields":["job_id","exit_status"],"Data":{"job_id":1001,"exit_status":null}}`
var j QueryJob
if err := json.Unmarshal([]byte(raw), &j); err != nil {
t.Fatal(err)
}
if j.ExitStatus != "" {
t.Errorf("ExitStatus: got %q want empty", j.ExitStatus)
}
}
func TestQueryJob_ExitStatus_String(t *testing.T) {
raw := `{"Fields":["job_id","exit_status"],"Data":{"job_id":1001,"exit_status":"{\"signal\":9}"}}`
var j QueryJob
if err := json.Unmarshal([]byte(raw), &j); err != nil {
t.Fatal(err)
}
if j.ExitStatus == "" {
t.Error("ExitStatus should not be empty for non-null value")
}
}
func TestQueryJob_ExitStatus_Zero(t *testing.T) {
raw := `{"Fields":["job_id","exit_status"],"Data":{"job_id":1001,"exit_status":0}}`
var j QueryJob
if err := json.Unmarshal([]byte(raw), &j); err != nil {
t.Fatal(err)
}
if j.ExitStatus != "0" {
t.Errorf("ExitStatus: got %q want 0", j.ExitStatus)
}
}

View file

@ -0,0 +1,154 @@
package models
const (
HEADER_HOST_NAME = "HOST_NAME"
HEADER_HOST_STATUS = "STATUS"
HEADER_HOST_NCPUS = "NCPUS"
HEADER_HOST_MAX_SLOT = "MAX_SLOT"
HEADER_HOST_NUM_RUN = "NUM_RUN"
HEADER_HOST_NUM_SSUSP = "NUM_SSUSP"
HEADER_HOST_NUM_USUSP = "NUM_USUSP"
HEADER_HOST_HOST_TYPE = "HOST_TYPE"
// host details info
HEADER_HOST_CPUF = "CPUF"
HEADER_HOST_JLU = "JL/U"
HEADER_HOST_MAX = "MAX"
HEADER_HOST_NJOBS = "NJOBS"
HEADER_HOST_RUN = "RUN"
HEADER_HOST_SSUSP = "SSUSP"
HEADER_HOST_USUSP = "USUSP"
HEADER_HOST_RSV = "RSV"
HEADER_HOST_DISPT_WINDOW = "DISPATCH_WINDOW"
// second line
HEADER_HOST_R15S = "r15s"
HEADER_HOST_R1M = "r1m"
HEADER_HOST_R15M = "r15m"
HEADER_HOST_UT = "ut"
HEADER_HOST_PG = "pg"
HEADER_HOST_IO = "io"
HEADER_HOST_LS = "ls"
HEADER_HOST_IT = "it"
HEADER_HOST_TMP = "tmp"
HEADER_HOST_SWP = "swp"
HEADER_HOST_MEM = "mem"
HEADER_HOST_SLOTS = "slots"
)
var HostDetailHeaders = []string{
HEADER_HOST_STATUS,
HEADER_HOST_CPUF,
HEADER_HOST_JLU,
HEADER_HOST_MAX,
HEADER_HOST_NJOBS,
HEADER_HOST_RUN,
HEADER_HOST_SSUSP,
HEADER_HOST_USUSP,
HEADER_HOST_RSV,
HEADER_HOST_DISPT_WINDOW,
}
var HostLoadSchedulingHeaders = []string{
HEADER_HOST_R15S,
HEADER_HOST_R1M,
HEADER_HOST_R15M,
HEADER_HOST_UT,
HEADER_HOST_PG,
HEADER_HOST_IO,
HEADER_HOST_LS,
HEADER_HOST_IT,
HEADER_HOST_TMP,
HEADER_HOST_SWP,
HEADER_HOST_MEM,
HEADER_HOST_SLOTS,
}
// HostsResponse define the data structure in the host query response
type HostsResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Count int `json:"count"`
Rows []XCEHost `json:"rows"`
Page_token string `json:"page_token"`
} `json:"data"`
}
// Host define the data structure in the host info\
type XCEHost struct {
HostID int64 `json:"host_id"`
HostName string `json:"host_name"`
HostStatus string `json:"host_status"`
HostModel string `json:"host_model"`
HostType string `json:"host_type"`
HostGroup string `json:"host_group"`
Flag int `json:"flag"`
CPUFactor float64 `json:"cpu_factor"`
DisptWindow interface{} `json:"dispt_window"`
NumCPUs int `json:"num_cpus"`
MaxSlot int `json:"max_slot"`
MaxMem int `json:"max_mem"`
MaxSwap int `json:"max_swap"`
MaxTmp int `json:"max_tmp"`
NumDisk int `json:"num_disk"`
NumRun int `json:"num_run"`
NumSSusp int `json:"num_ssusp"`
NumUSusp int `json:"num_ususp"`
NumDone int `json:"num_done"`
AffTopo interface{} `json:"aff_topo"` // JSONB
GPUTopo interface{} `json:"gpu_topo"` // JSONB
SchedStop SchedStop `json:"sched_stop"` // JSONB
RealLoad interface{} `json:"real_load"`
}
var HostFieldsMap = map[string]string{
HEADER_HOST_NAME: "host_name",
HEADER_HOST_STATUS: "host_status",
HEADER_HOST_NCPUS: "num_cores",
HEADER_HOST_MAX_SLOT: "max_slot",
HEADER_HOST_NUM_RUN: "num_run",
HEADER_HOST_NUM_SSUSP: "num_ssusp",
HEADER_HOST_NUM_USUSP: "num_ususp",
HEADER_HOST_HOST_TYPE: "host_type",
}
type SchedStop struct {
Ls string `json:"ls"` // Load Sharing
Pg string `json:"pg"` // Page
R1m string `json:"r1m"` // Recent 1 minute
Tmp string `json:"tmp"` // Temporary
}
type HostControlRequest struct {
ControlAction string `json:"action" binding:"required"` //control action
Hostname []string `json:"host_name" binding:"required"` //host name
Comment string `json:"comment"` //comment
}
type HostControlResponse struct {
Data struct {
Accepted []*HostControlResponseItem `json:"accepted"`
Failed []*HostControlResponseItem `json:"failed"`
} `json:"data"`
}
type HostControlResponseItem struct {
HostName string `json:"host_name"`
Msg string `json:"msg"`
}
func (h *XCEHost) UnmarshalJSON(data []byte) error {
type Alias XCEHost
var a Alias
if err := UnmarshalOrderMapRow(data, &a); err != nil {
return err
}
*h = XCEHost(a)
return nil
}
type ResReqSelectRequest struct {
Select string `json:"select"`
Order string `json:"order,omitempty"`
Limit int `json:"limit,omitempty"`
PageToken string `json:"page_token,omitempty"`
}

View file

@ -0,0 +1,517 @@
package models
import (
"encoding/json"
"fmt"
"time"
)
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"
// 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 string `json:"submit_time"`
ReserveTime time.Time `json:"reserve_time"`
StartTime string `json:"start_time"`
EndTime string `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",
//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 {
// First try to parse as a string
var str string
if err := json.Unmarshal(data, &str); err == nil {
// If the string is "-", return an empty ExecHosts
if str == "-" {
e.HostSlots = []HostSlot{}
e.NumHosts = 0
return nil
}
// If it's any other string, treat it as 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
}

View file

@ -0,0 +1,19 @@
package models
// LogonData define the data structure in the logon response
type LogonData struct {
Token string `json:"token"`
Path string `json:"path"`
IP string `json:"ip"`
CreateTime int64 `json:"createtime"`
TokenExpireTime int64 `json:"token_expire_time"`
}
// LogonResponse define the data structure in the logon response
type LogonResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data LogonData `json:"data"`
Count int `json:"count"`
CACert string `json:"cacert"`
}

View file

@ -0,0 +1,19 @@
package models
import "encoding/json"
// UnmarshalOrderMapRow extracts the Data sub-object from the server's OrderMap wire format
// {"Fields":[...],"Data":{...}} and unmarshals it into dst.
// If the input is not an OrderMap wrapper, it falls back to direct unmarshal.
func UnmarshalOrderMapRow(data []byte, dst interface{}) error {
var wrapper struct {
Data json.RawMessage `json:"Data"`
}
if err := json.Unmarshal(data, &wrapper); err != nil {
return err
}
if wrapper.Data == nil {
return json.Unmarshal(data, dst)
}
return json.Unmarshal(wrapper.Data, dst)
}

View file

@ -0,0 +1,13 @@
package models
type PermsResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data []string `json:"data"`
}
type Permission struct {
PermissionName string `json:"permission_name" binding:"required"`
DomainType string `json:"domain_type" binding:"required"`
DomainValues []string `json:"domain_values"`
}

View file

@ -0,0 +1,131 @@
package models
const (
HEADER_QUEUE_NAME = "QUEUE_NAME"
HEADER_QUEUE_PRIORITY = "PRIORITY"
HEADER_QUEUE_STATUS = "QUEUE_STATUS"
HEADER_QUEUE_NUM_PEND = "NUM_PEND"
HEADER_QUEUE_NUM_RUN = "NUM_RUN"
HEADER_QUEUE_NUM_SSUSP = "NUM_SSUSP"
HEADER_QUEUE_NUM_USUSP = "NUM_USUSP"
HEADER_QUEUE_NUM_DONE = "NUM_DONE"
// queue detail info
HEADER_QUEUE_PRIO = "PRIO"
HEADER_QUEUE_NICE = "NICE"
HEADER_QUEUE_MAX = "MAX"
HEADER_QUEUE_JLU = "JL/U"
HEADER_QUEUE_JLP = "JL/P"
HEADER_QUEUE_JLH = "JL/H"
HEADER_QUEUE_NJOBS = "NJOBS"
HEADER_QUEUE_PEND = "PEND"
HEADER_QUEUE_RUN = "RUN"
HEADER_QUEUE_SSUSP = "SSUSP"
HEADER_QUEUE_USUSP = "USUSP"
HEADER_QUEUE_RSV = "RSV"
// second line
HEADER_QUEUE_R15S = "r15s"
HEADER_QUEUE_R1M = "r1m"
HEADER_QUEUE_R15M = "r15m"
HEADER_QUEUE_UT = "ut"
HEADER_QUEUE_PG = "pg"
HEADER_QUEUE_IO = "io"
HEADER_QUEUE_LS = "ls"
HEADER_QUEUE_IT = "it"
HEADER_QUEUE_TMP = "tmp"
HEADER_QUEUE_SWP = "swp"
HEADER_QUEUE_MEM = "mem"
)
var QueueDetailHeaders = []string{
HEADER_QUEUE_PRIO,
HEADER_QUEUE_NICE,
HEADER_QUEUE_STATUS,
HEADER_QUEUE_MAX,
HEADER_QUEUE_JLU,
HEADER_QUEUE_JLP,
HEADER_QUEUE_JLH,
HEADER_QUEUE_NJOBS,
HEADER_QUEUE_PEND,
HEADER_QUEUE_RUN,
HEADER_QUEUE_SSUSP,
HEADER_QUEUE_USUSP,
HEADER_QUEUE_RSV,
}
var QueueLoadSchedulingHeaders = []string{
HEADER_QUEUE_R15S,
HEADER_QUEUE_R1M,
HEADER_QUEUE_R15M,
HEADER_QUEUE_UT,
HEADER_QUEUE_PG,
HEADER_QUEUE_IO,
HEADER_QUEUE_LS,
HEADER_QUEUE_IT,
HEADER_QUEUE_TMP,
HEADER_QUEUE_SWP,
HEADER_QUEUE_MEM,
}
// QueuesResponse define the data structure in the queue query response
type QueuesResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Count int `json:"count"`
Rows []DBQueueInfo `json:"rows"`
Page_token string `json:"page_token"`
} `json:"data"`
}
var QueueFieldsMap = map[string]string{
HEADER_QUEUE_NAME: "queue_name",
HEADER_QUEUE_PRIORITY: "priority",
HEADER_QUEUE_STATUS: "queue_status",
HEADER_QUEUE_NUM_PEND: "num_pend",
HEADER_QUEUE_NUM_RUN: "num_run",
HEADER_QUEUE_NUM_SSUSP: "num_ssusp",
HEADER_QUEUE_NUM_USUSP: "num_ususp",
HEADER_QUEUE_NUM_DONE: "num_done",
}
// DBQueueInfo define the data structure in the queue info
type DBQueueInfo struct {
QueueName string `json:"queue_name"`
QueueStatus string `json:"queue_status"`
Priority int `json:"priority"`
Description string `json:"description"`
NumPend int `json:"num_pend"`
NumRun int `json:"num_run"`
NumSSusp int `json:"num_ssusp"`
NumUSusp int `json:"num_ususp"`
NumDone int `json:"num_done"`
FSInfo map[string]interface{} `json:"fs_info"` // JSONB
}
type QueueControlRequest struct {
ControlAction string `json:"action" binding:"required"` //control action
QueueName []string `json:"queue_name" binding:"required"` //queue name
Comment string `json:"comment"` //comment
}
type QueueControlResponse struct {
Data struct {
Accepted []*QueueControlResponseItem `json:"accepted"`
Failed []*QueueControlResponseItem `json:"failed"`
} `json:"data"`
}
type QueueControlResponseItem struct {
QueueName string `json:"queue_name"`
Msg string `json:"msg"`
}
func (q *DBQueueInfo) UnmarshalJSON(data []byte) error {
type Alias DBQueueInfo
var a Alias
if err := UnmarshalOrderMapRow(data, &a); err != nil {
return err
}
*q = DBQueueInfo(a)
return nil
}

View file

@ -0,0 +1,202 @@
package models
import (
"encoding/json"
"fmt"
"strings"
)
const (
HEADER_RESOURCE_NAME = "RESOURCE"
HEADER_RESOURCE_VALUE = "VALUE"
HEADER_RESOURCE_LOC = "LOCATION"
HEADER_RESOURCE_DEF_TYPE = "TYPE"
HEADER_RESOURCE_DEF_ORDER = "ORDER"
HEADER_RESOURCE_DEF_BUILTIN = "BUILTIN"
HEADER_RESOURCE_DEF_CONSUMABLE = "CONSUMABLE"
HEADER_RESOURCE_DEF_DYNAMIC = "DYNAMIC"
HEADER_RESOURCE_DEF_RELEASE = "RELEASE"
HEADER_RESOURCE_DEF_INTERVAL = "INTERVAL"
HEADER_RESOURCE_DEF_DESC = "DESCRIPTION"
)
var ResourceDistFieldsMap = map[string]string{
HEADER_RESOURCE_NAME: "res_name",
HEADER_RESOURCE_VALUE: "value",
HEADER_RESOURCE_LOC: "location",
}
// ResourceDefFieldsMap maps resource definition field headers to API field names
var ResourceDefFieldsMap = map[string]string{
HEADER_RESOURCE_NAME: "res_name",
HEADER_RESOURCE_DEF_DESC: "description",
HEADER_RESOURCE_DEF_TYPE: "type",
HEADER_RESOURCE_DEF_ORDER: "order",
HEADER_RESOURCE_DEF_BUILTIN: "builtin",
HEADER_RESOURCE_DEF_CONSUMABLE: "consumable",
HEADER_RESOURCE_DEF_DYNAMIC: "dynamic",
HEADER_RESOURCE_DEF_RELEASE: "release",
HEADER_RESOURCE_DEF_INTERVAL: "interval",
}
var ResourceDefDetailHeaders = []string{
HEADER_RESOURCE_DEF_TYPE,
HEADER_RESOURCE_DEF_ORDER,
HEADER_RESOURCE_DEF_INTERVAL,
HEADER_RESOURCE_DEF_BUILTIN,
HEADER_RESOURCE_DEF_DYNAMIC,
HEADER_RESOURCE_DEF_RELEASE,
HEADER_RESOURCE_DEF_CONSUMABLE,
}
// ResourceValue can handle both string and boolean values from the API
type ResourceValue struct {
Value string
}
// ResourceLocation can handle both string and array values from the API
type ResourceLocation struct {
Value string
}
// UnmarshalJSON customizes the JSON parsing method to handle string, boolean, and numeric values
func (rv *ResourceValue) UnmarshalJSON(data []byte) error {
// First try to parse as a string
var str string
if err := json.Unmarshal(data, &str); err == nil {
rv.Value = str
return nil
}
// If it's not a string, try to parse as a boolean
var boolVal bool
if err := json.Unmarshal(data, &boolVal); err == nil {
if boolVal {
rv.Value = "true"
} else {
rv.Value = "false"
}
return nil
}
// If it's not a boolean, try to parse as a number (int or float)
var num json.Number
if err := json.Unmarshal(data, &num); err == nil {
rv.Value = num.String()
return nil
}
return fmt.Errorf("cannot unmarshal ResourceValue %s", string(data))
}
// String returns the string representation of ResourceValue
func (rv ResourceValue) String() string {
return rv.Value
}
// UnmarshalJSON customizes the JSON parsing method to handle string and array values
func (rl *ResourceLocation) UnmarshalJSON(data []byte) error {
// First try to parse as a string
var str string
if err := json.Unmarshal(data, &str); err == nil {
rl.Value = str
return nil
}
// If it's not a string, try to parse as an array
var arr []interface{}
if err := json.Unmarshal(data, &arr); err == nil {
// Convert array to comma-separated string
var strValues []string
for _, item := range arr {
if strItem, ok := item.(string); ok {
strValues = append(strValues, strItem)
} else {
// Convert non-string items to string
jsonBytes, err := json.Marshal(item)
if err != nil {
return fmt.Errorf("cannot marshal location array item: %v", err)
}
strValues = append(strValues, string(jsonBytes))
}
}
rl.Value = strings.Join(strValues, ", ")
return nil
}
return fmt.Errorf("cannot unmarshal ResourceLocation %s", string(data))
}
// String returns the string representation of ResourceLocation
func (rl ResourceLocation) String() string {
return rl.Value
}
type ResourceDistResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Count int `json:"count"`
Rows []ResourceDist `json:"rows"`
Page_token string `json:"page_token"`
} `json:"data"`
}
type ResourceDist struct {
ResourceName string `json:"res_name"`
ResourceValue ResourceValue `json:"value"`
ResourceLoc ResourceLocation `json:"location"`
}
func (r *ResourceDist) UnmarshalJSON(data []byte) error {
type Alias ResourceDist
var a Alias
if err := UnmarshalOrderMapRow(data, &a); err != nil {
return err
}
*r = ResourceDist(a)
return nil
}
// ResourceDefResponse represents the API response for resource definition queries
type ResourceDefResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Count int `json:"count"`
Rows []ResourceDef `json:"rows"`
Page_token string `json:"page_token"`
} `json:"data"`
}
// ResourceDef represents a resource definition
type ResourceDef struct {
ResourceName string `json:"res_name"`
Description string `json:"description"`
ResourceType string `json:"-"` // populated from value_type int
OrderType string `json:"-"` // populated from order_type int
Flag map[string]interface{} `json:"flag"`
Interval int `json:"interval"`
}
func (r *ResourceDef) UnmarshalJSON(data []byte) error {
type Alias struct {
ResourceName string `json:"res_name"`
Description string `json:"description"`
ResourceType string `json:"value_type"`
OrderType string `json:"order_type"`
Flag map[string]interface{} `json:"flag"`
Interval int `json:"interval"`
}
var a Alias
if err := UnmarshalOrderMapRow(data, &a); err != nil {
return err
}
r.ResourceName = a.ResourceName
r.Description = a.Description
r.ResourceType = a.ResourceType
r.OrderType = a.OrderType
r.Flag = a.Flag
r.Interval = a.Interval
return nil
}

View file

@ -0,0 +1,150 @@
package models
import (
"encoding/json"
"testing"
)
// Wire format from actual server responses (OrderMap wrapper with string status fields)
func TestHostsResponse_FullUnmarshal(t *testing.T) {
raw := `{"code":200,"msg":"Success","data":{"count":1,"rows":[{"Fields":["host_name","host_status","num_cpus","max_slot","num_run","num_ssusp","num_ususp","host_type"],"Data":{"host_name":"host1","host_status":"OK","num_cpus":16,"max_slot":16,"num_run":3,"num_ssusp":0,"num_ususp":0,"host_type":"linux"}}],"page_token":""}}`
var resp HostsResponse
if err := json.Unmarshal([]byte(raw), &resp); err != nil {
t.Fatal(err)
}
if len(resp.Data.Rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(resp.Data.Rows))
}
h := resp.Data.Rows[0]
if h.HostName != "host1" {
t.Errorf("HostName: got %q want host1", h.HostName)
}
if h.HostStatus != "OK" {
t.Errorf("HostStatus: got %q want OK", h.HostStatus)
}
if h.NumCPUs != 16 {
t.Errorf("NumCPUs: got %d want 16", h.NumCPUs)
}
}
func TestQueuesResponse_FullUnmarshal(t *testing.T) {
raw := `{"code":200,"msg":"Success","data":{"count":1,"rows":[{"Fields":["queue_name","queue_status","priority","num_pend","num_run"],"Data":{"queue_name":"normal","queue_status":"open:active","priority":30,"num_pend":5,"num_run":3}}],"page_token":""}}`
var resp QueuesResponse
if err := json.Unmarshal([]byte(raw), &resp); err != nil {
t.Fatal(err)
}
if len(resp.Data.Rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(resp.Data.Rows))
}
q := resp.Data.Rows[0]
if q.QueueName != "normal" {
t.Errorf("QueueName: got %q want normal", q.QueueName)
}
if q.QueueStatus != "open:active" {
t.Errorf("QueueStatus: got %q want open:active", q.QueueStatus)
}
if q.NumPend != 5 {
t.Errorf("NumPend: got %d want 5", q.NumPend)
}
}
func TestJobsResponse_FullUnmarshal(t *testing.T) {
raw := `{"code":200,"msg":"Success","data":{"count":1,"rows":[{"Fields":["job_id","submit_user","job_status","queue_name","job_name"],"Data":{"job_id":1001,"submit_user":"user1","job_status":"RUN","queue_name":"normal","job_name":"test_job_1"}}],"page_token":""}}`
var resp JobsResponse
if err := json.Unmarshal([]byte(raw), &resp); err != nil {
t.Fatal(err)
}
if len(resp.Data.Rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(resp.Data.Rows))
}
j := resp.Data.Rows[0]
if j.JobID != 1001 {
t.Errorf("JobID: got %d want 1001", j.JobID)
}
if j.JobStatus != "RUN" {
t.Errorf("JobStatus: got %q want RUN", j.JobStatus)
}
if j.SubmitUser != "user1" {
t.Errorf("SubmitUser: got %q want user1", j.SubmitUser)
}
}
func TestRolesResponse_FullUnmarshal(t *testing.T) {
raw := `{"code":200,"msg":"Success","data":{"count":1,"rows":[{"Fields":["role_name","action","domain_type","domain_values"],"Data":{"role_name":"basic_user","action":"cluster:view","domain_type":"all","domain_values":null}}],"page_token":""}}`
var resp RolesResponse
if err := json.Unmarshal([]byte(raw), &resp); err != nil {
t.Fatal(err)
}
if len(resp.Data.Rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(resp.Data.Rows))
}
r := resp.Data.Rows[0]
if r.RoleName != "basic_user" {
t.Errorf("RoleName: got %q want basic_user", r.RoleName)
}
if r.PermName != "cluster:view" {
t.Errorf("PermName: got %q want cluster:view", r.PermName)
}
}
func TestUserRolesResponse_FullUnmarshal(t *testing.T) {
raw := `{"code":200,"msg":"Success","data":{"count":1,"rows":[{"Fields":["user_name","role_name"],"Data":{"user_name":"admin","role_name":"cluster_admin"}}],"page_token":""}}`
var resp UserRolesResponse
if err := json.Unmarshal([]byte(raw), &resp); err != nil {
t.Fatal(err)
}
if len(resp.Data.Rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(resp.Data.Rows))
}
u := resp.Data.Rows[0]
if u.UserName != "admin" {
t.Errorf("UserName: got %q want admin", u.UserName)
}
if u.UserRoleName != "cluster_admin" {
t.Errorf("UserRoleName: got %q want cluster_admin", u.UserRoleName)
}
}
func TestResourceDistResponse_FullUnmarshal(t *testing.T) {
raw := `{"code":200,"msg":"Success","data":{"count":1,"rows":[{"Fields":["res_name","value","location","is_dynamic"],"Data":{"res_name":"ncpus","value":16,"location":"host1","is_dynamic":false}}],"page_token":""}}`
var resp ResourceDistResponse
if err := json.Unmarshal([]byte(raw), &resp); err != nil {
t.Fatal(err)
}
if len(resp.Data.Rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(resp.Data.Rows))
}
r := resp.Data.Rows[0]
if r.ResourceName != "ncpus" {
t.Errorf("ResourceName: got %q want ncpus", r.ResourceName)
}
if r.ResourceValue.String() != "16" {
t.Errorf("ResourceValue: got %q want 16", r.ResourceValue.String())
}
if r.ResourceLoc.String() != "host1" {
t.Errorf("ResourceLoc: got %q want host1", r.ResourceLoc.String())
}
}
func TestResourceDefResponse_FullUnmarshal(t *testing.T) {
// server sends value_type and order_type as strings, flag as object
raw := `{"code":200,"msg":"Success","data":{"count":1,"rows":[{"Fields":["res_name","description","value_type","order_type","flag","interval"],"Data":{"res_name":"gpu","description":"GPU devices","value_type":"Numeric","order_type":"Decr","flag":{"builtin":true,"consumable":true,"dynamic":true,"release":false},"interval":60}}],"page_token":""}}`
var resp ResourceDefResponse
if err := json.Unmarshal([]byte(raw), &resp); err != nil {
t.Fatal(err)
}
if len(resp.Data.Rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(resp.Data.Rows))
}
r := resp.Data.Rows[0]
if r.ResourceName != "gpu" {
t.Errorf("ResourceName: got %q want gpu", r.ResourceName)
}
if r.ResourceType != "Numeric" {
t.Errorf("ResourceType: got %q want Numeric", r.ResourceType)
}
if r.OrderType != "Decr" {
t.Errorf("OrderType: got %q want Decr", r.OrderType)
}
}

View file

@ -0,0 +1,53 @@
package models
type CreateRoleRequest struct {
RoleName string `json:"role_name" binding:"required"`
Permission []Permission `json:"permissions" binding:"required"`
}
type CreateRoleResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
RoleName string `json:"role_name"`
Message string `json:"message"`
} `json:"data"`
}
type RoleInfo struct {
RoleName string `json:"role_name"`
PermName string `json:"action"`
DomainType string `json:"domain_type"`
DomainValues interface{} `json:"domain_values"`
}
type RolesResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Count int `json:"count"`
Rows []RoleInfo `json:"rows"`
PageToken string `json:"page_token"`
} `json:"data"`
}
const (
HEADER_ROLE_NAME = "ROLE_NAME"
HEADER_ROLE_PERM_NAME = "PERM_NAME"
HEADER_ROLE_DOMAIN_TYPE = "DOMAIN_TYPE"
HEADER_ROLE_DOMAIN_VALUES = "DOMAIN_VALUES"
)
var RoleFieldsMap = map[string]string{
HEADER_ROLE_NAME: "role_name",
}
func (r *RoleInfo) UnmarshalJSON(data []byte) error {
type Alias RoleInfo
var a Alias
if err := UnmarshalOrderMapRow(data, &a); err != nil {
return err
}
*r = RoleInfo(a)
return nil
}

View file

@ -0,0 +1,54 @@
package models
const (
HEADER_USER_NAME = "USER_NAME"
HEADER_USERROLE_NAME = "ROLE_NAME"
)
var UserRoleFieldsMap = map[string]string{
HEADER_USER_NAME: "user_name",
}
type AddRoleForUserRequest struct {
UserName string `json:"user_name" binding:"required"`
RoleName []string `json:"role_name" binding:"required"`
}
type UserAssignRoleResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
UserName string `json:"user_name"`
Message string `json:"message"`
} `json:"data"`
}
type UserRolesResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Count int `json:"count"`
Rows []UserRoleInfo `json:"rows"`
PageToken string `json:"page_token"`
} `json:"data"`
}
type UserRoleInfo struct {
UserName string `json:"user_name"`
UserRoleName string `json:"role_name"`
}
type UserResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data string `json:"data"`
}
func (u *UserRoleInfo) UnmarshalJSON(data []byte) error {
type Alias UserRoleInfo
var a Alias
if err := UnmarshalOrderMapRow(data, &a); err != nil {
return err
}
*u = UserRoleInfo(a)
return nil
}