style(frontend): gofmt the apiserver and xcecli sources

Formatting only (gofmt -w on the 29 non-vendored, non-generated files
gofmt -l reported); swag-generated apiserver/docs/docs.go left as is.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-02 09:33:16 +00:00
parent b57ce6d6a9
commit 3615c7c0fb
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
29 changed files with 1280 additions and 1285 deletions

View file

@ -23,10 +23,10 @@ config_file:
} }
tests := []struct { tests := []struct {
name string name string
user string user string
pass string pass string
wantErr bool wantErr bool
}{ }{
{"valid admin", "admin", "secret", false}, {"valid admin", "admin", "secret", false},
{"valid viewer", "viewer", "pass", false}, {"valid viewer", "viewer", "pass", false},

View file

@ -515,4 +515,4 @@ func TestIntegration_CrossFeature_SequentialConfigPushes(t *testing.T) {
if hb := atomic.LoadInt32(&heartbeatIntervalSecs); hb != 60 { if hb := atomic.LoadInt32(&heartbeatIntervalSecs); hb != 60 {
t.Errorf("heartbeatIntervalSecs = %d, want 60", hb) t.Errorf("heartbeatIntervalSecs = %d, want 60", hb)
} }
} }

View file

@ -1,9 +1,9 @@
package main package main
import ( import (
_ "apiserver/docs"
"apiserver/backend" "apiserver/backend"
"apiserver/backend/auth" "apiserver/backend/auth"
_ "apiserver/docs"
"apiserver/middleware" "apiserver/middleware"
"apiserver/router" "apiserver/router"
"apiserver/store" "apiserver/store"

View file

@ -44,16 +44,16 @@ const (
) )
const ( const (
SUCCESS_CODE = 200 SUCCESS_CODE = 200
CREATED = 201 CREATED = 201
ACCEPTED = 202 ACCEPTED = 202
StatusPartialContent = 206 StatusPartialContent = 206
ERROR_CODE = 500 ERROR_CODE = 500
INVALID_PARAMS_CODE = 400 INVALID_PARAMS_CODE = 400
ERROR_AUTH_CHECK_TOKEN_CODE = 401 ERROR_AUTH_CHECK_TOKEN_CODE = 401
ERROR_AUTH_CODE = 403 ERROR_AUTH_CODE = 403
ERROR_NOT_FOUND_CODE = 404 ERROR_NOT_FOUND_CODE = 404
ERROR_TOO_MANY_REQUESTS_CODE = 429 ERROR_TOO_MANY_REQUESTS_CODE = 429
//read file error //read file error
ERROR_READ_FILE_CODE = 501 ERROR_READ_FILE_CODE = 501

View file

@ -675,4 +675,4 @@ func TestIntegration_SpecialCharactersInHostname(t *testing.T) {
if hostnameVal != xssHostname { if hostnameVal != xssHostname {
t.Errorf("hostname round-trip: got %q, want %q", hostnameVal, xssHostname) t.Errorf("hostname round-trip: got %q, want %q", hostnameVal, xssHostname)
} }
} }

View file

@ -27,7 +27,7 @@ import (
const ( const (
//TODO: Need assign clusterID globally. //TODO: Need assign clusterID globally.
clusterID = 1 clusterID = 1
apiserverID = 0 apiserverID = 0
) )
@ -3190,4 +3190,3 @@ func GetHostList(hosts []string) ([]string, error) {
} }
return execHosts, nil return execHosts, nil
} }

View file

@ -1,11 +1,11 @@
package controllers package controllers
import ( import (
"errors"
"apiserver/backend" "apiserver/backend"
"apiserver/models" "apiserver/models"
"apiserver/store" "apiserver/store"
"apiserver/utils" "apiserver/utils"
"errors"
"strings" "strings"
"testing" "testing"

View file

@ -242,4 +242,3 @@ func parseJobIDParam(jobidStr string) (models.JobInfo, error) {
ArrayIdx: 0, // peek defaults to base job; array idx can be added later ArrayIdx: 0, // peek defaults to base job; array idx can be added later
}, nil }, nil
} }

View file

@ -237,7 +237,7 @@ func TestGetJobOutput_DefaultPathFallback(t *testing.T) {
// Use a real-ish packed job ID: cluster=1, apiserver=0, ts=1778311965, seq=0 // Use a real-ish packed job ID: cluster=1, apiserver=0, ts=1778311965, seq=0
// FormatJobID → "1-0-1778311965-0" // FormatJobID → "1-0-1778311965-0"
// WorkerDefaultOutputPath → "/tmp/job_1-0-1778311965-0_task0.out" // WorkerDefaultOutputPath → "/tmp/job_1-0-1778311965-0_task0.out"
const uniqueID = int64(0x0100000006A04BDD<<0) // MakeJobID(1,0,1778311965,0,0) const uniqueID = int64(0x0100000006A04BDD << 0) // MakeJobID(1,0,1778311965,0,0)
// Use backend.MakeJobID directly for clarity; compute inline: // Use backend.MakeJobID directly for clarity; compute inline:
// clusterID=1 at bit 56, timestamp=1778311965 at bit 20 // clusterID=1 at bit 56, timestamp=1778311965 at bit 20
// packed = (1 << 56) | (1778311965 << 20) // packed = (1 << 56) | (1778311965 << 20)
@ -512,4 +512,3 @@ func TestGetJobOutput_DoneJob(t *testing.T) {
t.Errorf("job_status = %q, want DONE", resp.Data.JobStatus) t.Errorf("job_status = %q, want DONE", resp.Data.JobStatus)
} }
} }

View file

@ -7,46 +7,46 @@ import (
func TestClusterInfo_NewFields(t *testing.T) { func TestClusterInfo_NewFields(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
cluster ClusterInfo cluster ClusterInfo
field string field string
want interface{} want interface{}
}{ }{
{ {
name: "HeartbeatInterval field exists and serializes", name: "HeartbeatInterval field exists and serializes",
cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 30}, cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 30},
field: "heartbeat_interval", field: "heartbeat_interval",
want: float64(30), want: float64(30),
}, },
{ {
name: "Vcpus field exists and serializes", name: "Vcpus field exists and serializes",
cluster: ClusterInfo{ClusterName: "test", Vcpus: 16}, cluster: ClusterInfo{ClusterName: "test", Vcpus: 16},
field: "vcpus", field: "vcpus",
want: float64(16), want: float64(16),
}, },
{ {
name: "InactiveHeartbeatCount field exists and serializes", name: "InactiveHeartbeatCount field exists and serializes",
cluster: ClusterInfo{ClusterName: "test", InactiveHeartbeatCount: 3}, cluster: ClusterInfo{ClusterName: "test", InactiveHeartbeatCount: 3},
field: "inactive_heartbeat_count", field: "inactive_heartbeat_count",
want: float64(3), want: float64(3),
}, },
{ {
name: "HeartbeatInterval zero value serializes as 0 not omitted", name: "HeartbeatInterval zero value serializes as 0 not omitted",
cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0}, cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0},
field: "heartbeat_interval", field: "heartbeat_interval",
want: float64(0), want: float64(0),
}, },
{ {
name: "Vcpus zero value serializes as 0 not omitted", name: "Vcpus zero value serializes as 0 not omitted",
cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0}, cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0},
field: "vcpus", field: "vcpus",
want: float64(0), want: float64(0),
}, },
{ {
name: "InactiveHeartbeatCount zero value serializes as 0 not omitted", name: "InactiveHeartbeatCount zero value serializes as 0 not omitted",
cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0}, cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0},
field: "inactive_heartbeat_count", field: "inactive_heartbeat_count",
want: float64(0), want: float64(0),
}, },
} }
@ -122,4 +122,4 @@ func mapKeys(m map[string]interface{}) []string {
keys = append(keys, k) keys = append(keys, k)
} }
return keys return keys
} }

View file

@ -1,17 +1,17 @@
package models package models
const ( const (
CONTROL_JOB = 1 CONTROL_JOB = 1
CONTROL_HOST = 2 CONTROL_HOST = 2
CONTROL_QUEUE = 3 CONTROL_QUEUE = 3
CONTROL_CLUSTER = 4 CONTROL_CLUSTER = 4
CONTROL_DEBUG = 5 CONTROL_DEBUG = 5
) )
type ActionControl struct { type ActionControl struct {
TargetType int `json:"target_type"` TargetType int `json:"target_type"`
ActionType int `json:"action_type"` ActionType int `json:"action_type"`
Content Jsonb `json:"content"` Content Jsonb `json:"content"`
SubmitTime int64 `json:"submit_time"` SubmitTime int64 `json:"submit_time"`
Submitter string `json:"submitter"` Submitter string `json:"submitter"`
} }

View file

@ -88,9 +88,9 @@ type HostInfo struct {
NumSSusp int `json:"num_ssusp"` NumSSusp int `json:"num_ssusp"`
NumUSusp int `json:"num_ususp"` NumUSusp int `json:"num_ususp"`
//NumRsv int `json:"num_rsv"` //NumRsv int `json:"num_rsv"`
NumDone int `json:"num_done"` NumDone int `json:"num_done"`
Topology string `json:"topology"` // JSONB Topology string `json:"topology"` // JSONB
SchedStop string `json:"sched_stop"` // JSONB SchedStop string `json:"sched_stop"` // JSONB
ResourceValues []ResourceDistributionInfo `json:"resource_values"` // per-worker resource values ResourceValues []ResourceDistributionInfo `json:"resource_values"` // per-worker resource values
//RealLoad string `json:"real_load"` // JSONB //RealLoad string `json:"real_load"` // JSONB
} }

View file

@ -68,4 +68,4 @@ func TestHostStateStr(t *testing.T) {
} }
}) })
} }
} }

View file

@ -591,32 +591,32 @@ type ArrayJobInfo struct {
} }
type JobAdditionalFields struct { type JobAdditionalFields struct {
JobID int64 `json:"job_id"` JobID int64 `json:"job_id"`
SubmitUser string `json:"submit_user"` SubmitUser string `json:"submit_user"`
JobStatus int `json:"job_status"` JobStatus int `json:"job_status"`
SubmitTime int64 `json:"submit_time"` SubmitTime int64 `json:"submit_time"`
FromHost string `json:"from_host"` FromHost string `json:"from_host"`
ExecHosts string `json:"exec_hosts"` ExecHosts string `json:"exec_hosts"`
StartTime int64 `json:"start_time"` StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"` EndTime int64 `json:"end_time"`
JobRusage Jsonb `json:"job_rusage"` JobRusage Jsonb `json:"job_rusage"`
MaxMem float64 `json:"max_mem"` MaxMem float64 `json:"max_mem"`
AvgMem float64 `json:"avg_mem"` AvgMem float64 `json:"avg_mem"`
UsedMem float64 `json:"used_mem"` UsedMem float64 `json:"used_mem"`
CPUTime int `json:"cpu_time"` CPUTime int `json:"cpu_time"`
PeakCPUTime int `json:"peak_cpu_time"` PeakCPUTime int `json:"peak_cpu_time"`
PeakCPUDuration float64 `json:"peak_cpu_duration"` PeakCPUDuration float64 `json:"peak_cpu_duration"`
MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage
AvgCPUEfficiency float64 `json:"avg_cpu_efficiency"` // Average CPU 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 PeakCPUEfficiency float64 `json:"peak_cpu_efficiency"` // Peak CPU efficiency in percentage
NumHosts int `json:"num_hosts"` // Number of hosts used by the job NumHosts int `json:"num_hosts"` // Number of hosts used by the job
FirstHost string `json:"first_host"` // First host where the job ran FirstHost string `json:"first_host"` // First host where the job ran
AllocSlot string `json:"alloc_slots"` // Allocated slot information AllocSlot string `json:"alloc_slots"` // Allocated slot information
PIDInfo Jsonb `json:"pid_info"` PIDInfo Jsonb `json:"pid_info"`
Pids []int `json:"pids"` // List of process IDs associated with the job Pids []int `json:"pids"` // List of process IDs associated with the job
Pgids []int `json:"pgids"` // List of process group IDs associated with the job Pgids []int `json:"pgids"` // List of process group IDs associated with the job
InteractiveJobURL string `json:"interactive_job_url"` InteractiveJobURL string `json:"interactive_job_url"`
// CoreLimit/MemLimit: JobInfo has no equivalent field (these are // CoreLimit/MemLimit: JobInfo has no equivalent field (these are
// submission-time resource limits, not job-state fields), but xcecli's // submission-time resource limits, not job-state fields), but xcecli's
// JobFieldsMap accepts them as valid -o/-l display fields (bug-1674) -- // JobFieldsMap accepts them as valid -o/-l display fields (bug-1674) --

View file

@ -1,139 +1,139 @@
package models package models
type ValueType int type ValueType int
const ( const (
STRING ValueType = iota // Bool value STRING ValueType = iota // Bool value
NUMERIC // Numeric value NUMERIC // Numeric value
BOOLEAN // String value BOOLEAN // String value
) )
// String returns the string representation of ValueType // String returns the string representation of ValueType
func (v ValueType) String() string { func (v ValueType) String() string {
switch v { switch v {
case BOOLEAN: case BOOLEAN:
return "Boolean" return "Boolean"
case NUMERIC: case NUMERIC:
return "Numeric" return "Numeric"
case STRING: case STRING:
return "String" return "String"
default: default:
return "Unknown" return "Unknown"
} }
} }
type OrderType int type OrderType int
const ( const (
INCR OrderType = iota INCR OrderType = iota
DECR DECR
NA NA
) )
// String returns the string representation of OrderType // String returns the string representation of OrderType
func (o OrderType) String() string { func (o OrderType) String() string {
switch o { switch o {
case INCR: case INCR:
return "Incr" return "Incr"
case DECR: case DECR:
return "Decr" return "Decr"
case NA: case NA:
return "NA" return "NA"
default: default:
return "Unknown" return "Unknown"
} }
} }
var RESOURCE_DEFAULT_COLUMN = []string{ var RESOURCE_DEFAULT_COLUMN = []string{
"res_name", "res_name",
"description", "description",
"value_type", "value_type",
"order_type", "order_type",
"flag", "flag",
"interval", "interval",
} }
var RESOURCE_DISTRIBUTION_DEFAULT_COLUMN = []string{ var RESOURCE_DISTRIBUTION_DEFAULT_COLUMN = []string{
"res_name", "res_name",
"numeric_value", "numeric_value",
"location", "location",
"is_dynamic", "is_dynamic",
} }
var RESOURCE_DEFINITION_DEFAULT_COLUMN = []string{ var RESOURCE_DEFINITION_DEFAULT_COLUMN = []string{
"res_name", "res_name",
"description", "description",
"value_type", "value_type",
"order_type", "order_type",
"flag", "flag",
"interval", "interval",
} }
var ResourceFieldMappings = map[string]string{ var ResourceFieldMappings = map[string]string{
// API field name → struct field name // API field name → struct field name
"value": "numeric_value", "value": "numeric_value",
} }
var ResourcesAvailableTypes = []string{"static", "dynamic"} var ResourcesAvailableTypes = []string{"static", "dynamic"}
const ( const (
BUILTIN = 0x01 BUILTIN = 0x01
DYNAMIC = 0x02 DYNAMIC = 0x02
RELEASE = 0x20 RELEASE = 0x20
CONSUMABLE = 0x80 CONSUMABLE = 0x80
) )
type FlagValue int type FlagValue int
// ToJSON returns the JSON object representation of FlagValue // ToJSON returns the JSON object representation of FlagValue
func (f FlagValue) ToJSON() map[string]bool { func (f FlagValue) ToJSON() map[string]bool {
return map[string]bool{ return map[string]bool{
"builtin": f&BUILTIN != 0, "builtin": f&BUILTIN != 0,
"dynamic": f&DYNAMIC != 0, "dynamic": f&DYNAMIC != 0,
"release": f&RELEASE != 0, "release": f&RELEASE != 0,
"consumable": f&CONSUMABLE != 0, "consumable": f&CONSUMABLE != 0,
} }
} }
type ResourceInfo struct { type ResourceInfo struct {
ResName string ResName string
Description string Description string
ValueType ValueType ValueType ValueType
OrderType OrderType OrderType OrderType
Flag int Flag int
Interval int Interval int
} }
// queues info query supported filter // queues info query supported filter
var RESOURCE_DISTRIBUTION_SUPPORT_FILTERS = []string{ var RESOURCE_DISTRIBUTION_SUPPORT_FILTERS = []string{
"res_name", "res_name",
"is_dynamic", "is_dynamic",
"location", "location",
} }
var RESOURCE_DEFINITION_SUPPORT_FILTERS = []string{ var RESOURCE_DEFINITION_SUPPORT_FILTERS = []string{
"res_name", "res_name",
} }
type ResourceDistributionInfo struct { type ResourceDistributionInfo struct {
ResourceName string `json:"res_name"` ResourceName string `json:"res_name"`
IsDynamic bool `json:"is_dynamic"` IsDynamic bool `json:"is_dynamic"`
ValueType int `json:"value_type"` ValueType int `json:"value_type"`
BooleanValue bool `json:"boolean_value"` BooleanValue bool `json:"boolean_value"`
StringValue string `json:"string_value"` StringValue string `json:"string_value"`
NumericValue float64 `json:"numeric_value"` NumericValue float64 `json:"numeric_value"`
Location string `json:"location"` Location string `json:"location"`
} }
type ResourceDefinitionInfo struct { type ResourceDefinitionInfo struct {
ResourceName string `json:"res_name"` ResourceName string `json:"res_name"`
Description string `json:"description"` Description string `json:"description"`
ValueType int `json:"value_type"` ValueType int `json:"value_type"`
OrderType int `json:"order_type"` OrderType int `json:"order_type"`
Flag int `json:"flag"` Flag int `json:"flag"`
Interval int `json:"interval"` Interval int `json:"interval"`
} }
var RESOURCE_DISTRIBUTION_SUPPORT_SORT = []string{ var RESOURCE_DISTRIBUTION_SUPPORT_SORT = []string{
"res_name", "res_name",
} }

View file

@ -102,7 +102,7 @@ func TestIntegration_ConcurrentResourceValuePushes(t *testing.T) {
ResourceName: "mem", ResourceName: "mem",
Location: "node01", Location: "node01",
NumericValue: float64(idx + 1), NumericValue: float64(idx + 1),
ValueType: 1, // NUMERIC ValueType: 1, // NUMERIC
IsDynamic: true, IsDynamic: true,
}) })
count.Add(1) count.Add(1)
@ -129,4 +129,4 @@ func TestIntegration_ConcurrentResourceValuePushes(t *testing.T) {
if ResourceLocations.Count() != 1 { if ResourceLocations.Count() != 1 {
t.Errorf("ResourceLocations count = %d, want 1 (key exists exactly once)", ResourceLocations.Count()) t.Errorf("ResourceLocations count = %d, want 1 (key exists exactly once)", ResourceLocations.Count())
} }
} }

View file

@ -4,16 +4,16 @@ import "apiserver/models"
// Global store instances. // Global store instances.
var ( var (
Jobs *DataStore[uint64, models.JobInfo] Jobs *DataStore[uint64, models.JobInfo]
ArrayJobs *DataStore[uint64, models.ArrayJobInfo] ArrayJobs *DataStore[uint64, models.ArrayJobInfo]
Queues *DataStore[string, models.QueueInfo] Queues *DataStore[string, models.QueueInfo]
Hosts *DataStore[string, models.HostInfo] Hosts *DataStore[string, models.HostInfo]
Resources *DataStore[string, models.ResourceDefinitionInfo] Resources *DataStore[string, models.ResourceDefinitionInfo]
ResourceLocations *DataStore[string, models.ResourceDistributionInfo] ResourceLocations *DataStore[string, models.ResourceDistributionInfo]
Clusters *DataStore[string, models.ClusterInfo] Clusters *DataStore[string, models.ClusterInfo]
JobExecHosts *DataStore[int64, models.JobExecHosts] JobExecHosts *DataStore[int64, models.JobExecHosts]
Roles *DataStore[string, RoleEntry] Roles *DataStore[string, RoleEntry]
UserRoles *DataStore[string, []string] // user_name → role_names UserRoles *DataStore[string, []string] // user_name → role_names
) )
// RoleEntry holds a role's permissions. // RoleEntry holds a role's permissions.

View file

@ -1,50 +1,50 @@
package cmd package cmd
import ( import (
"fmt" "fmt"
"os" "os"
"text/tabwriter" "text/tabwriter"
"xcecli/common/request" "xcecli/common/request"
"xcecli/config" "xcecli/config"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// NewListCmd create the list command // NewListCmd create the list command
func NewListCmd(configManager *config.ConfigManager) *cobra.Command { func NewListCmd(configManager *config.ConfigManager) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "list", Use: "list",
GroupID: "api", GroupID: "api",
Short: "List the API Servers", Short: "List the API Servers",
Long: "List all the configured API Servers and the default API Server", Long: "List all the configured API Servers and the default API Server",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runList(configManager) return runList(configManager)
}, },
} }
} }
func runList(cm *config.ConfigManager) error { func runList(cm *config.ConfigManager) error {
cfg, err := cm.GetConfig() cfg, err := cm.GetConfig()
if err != nil { if err != nil {
errMsg := request.ExtractErrorMessage(err) errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("failed to get the config: [%s]", errMsg) return fmt.Errorf("failed to get the config: [%s]", errMsg)
} }
// use tabwriter to format the output // use tabwriter to format the output
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "Default\tVersion\tURL") fmt.Fprintln(w, "Default\tVersion\tURL")
for _, server := range cfg.APIServerInfo { for _, server := range cfg.APIServerInfo {
isDefault := " " isDefault := " "
if server.Token == "" { if server.Token == "" {
continue continue
} }
fmt.Fprintf(w, "%s\t%s\n", fmt.Fprintf(w, "%s\t%s\n",
isDefault, isDefault,
server.URL) server.URL)
} }
return w.Flush() return w.Flush()
} }

View file

@ -1,38 +1,38 @@
package jobs package jobs
import ( import (
"xcecli/config" "xcecli/config"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// NewJobsCmd creates a jobs query command // NewJobsCmd creates a jobs query command
func NewJobsCmd(configManager *config.ConfigManager) *cobra.Command { func NewJobsCmd(configManager *config.ConfigManager) *cobra.Command {
// Create the parent jobs command // Create the parent jobs command
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "jobs", Use: "jobs",
GroupID: "query", GroupID: "query",
Short: "Manage jobs", Short: "Manage jobs",
Long: `Manage jobs in the system. Long: `Manage jobs in the system.
Use 'xce jobs list' to query jobs information. Use 'xce jobs list' to query jobs information.
Use 'xce jobs <action>' to submit/kill/stop/resume/checkpoint/restart jobs.`, Use 'xce jobs <action>' to submit/kill/stop/resume/checkpoint/restart jobs.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Display help when no subcommand is specified // Display help when no subcommand is specified
cmd.Help() cmd.Help()
}, },
} }
cmd.AddCommand(NewListJobsCmd(configManager)) cmd.AddCommand(NewListJobsCmd(configManager))
// Add submit command to jobs command // Add submit command to jobs command
cmd.AddCommand(NewSubmitJobsCmd(configManager)) cmd.AddCommand(NewSubmitJobsCmd(configManager))
// Add job control sub-commands to jobs command // Add job control sub-commands to jobs command
cmd.AddCommand(NewKillJobsCmd(configManager)) cmd.AddCommand(NewKillJobsCmd(configManager))
cmd.AddCommand(NewStopJobsCmd(configManager)) cmd.AddCommand(NewStopJobsCmd(configManager))
cmd.AddCommand(NewResumeJobsCmd(configManager)) cmd.AddCommand(NewResumeJobsCmd(configManager))
cmd.AddCommand(NewCheckPointJobsCmd(configManager)) cmd.AddCommand(NewCheckPointJobsCmd(configManager))
cmd.AddCommand(NewRestartJobsCmd(configManager)) cmd.AddCommand(NewRestartJobsCmd(configManager))
// Add peek command to view job output // Add peek command to view job output
cmd.AddCommand(NewPeekJobsCmd(configManager)) cmd.AddCommand(NewPeekJobsCmd(configManager))
return cmd return cmd
} }

View file

@ -1,111 +1,111 @@
/* /*
The MIT License (MIT) The MIT License (MIT)
Copyright © 2025 xce Copyright © 2025 xce
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
*/ */
package main package main
import ( import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"xcecli/config" "xcecli/config"
logger "xcecli/config" logger "xcecli/config"
cmd "xcecli/router" cmd "xcecli/router"
"go.uber.org/zap" "go.uber.org/zap"
) )
// GitCommit and BuildTime are injected at link time via // GitCommit and BuildTime are injected at link time via
// `-ldflags "-X main.GitCommit=... -X main.BuildTime=..."` (see // `-ldflags "-X main.GitCommit=... -X main.BuildTime=..."` (see
// src/tools/go_build). The // src/tools/go_build). The
// "unknown" defaults matter: a bare `go build`/`go run` that bypasses that // "unknown" defaults matter: a bare `go build`/`go run` that bypasses that
// ldflags injection must still report "unknown" rather than an empty // ldflags injection must still report "unknown" rather than an empty
// string, matching this feature's unknown-fallback requirement (see // string, matching this feature's unknown-fallback requirement (see
// xce_version_field() in src/lib/utils.c for the equivalent C-side // xce_version_field() in src/lib/utils.c for the equivalent C-side
// convention). // convention).
// //
// DirtyFingerprint is injected the same way (`-X main.DirtyFingerprint=...`, // DirtyFingerprint is injected the same way (`-X main.DirtyFingerprint=...`,
// see src/tools/go_build task 4.1) but its default is a deliberately EMPTY // see src/tools/go_build task 4.1) but its default is a deliberately EMPTY
// string, not "unknown" -- matching dirty_fingerprint's convention // string, not "unknown" -- matching dirty_fingerprint's convention
// everywhere else in the dirty-content-fingerprint feature (empty means // everywhere else in the dirty-content-fingerprint feature (empty means
// "no fingerprint available", distinct from GitCommit/BuildTime's // "no fingerprint available", distinct from GitCommit/BuildTime's
// "unknown" sentinel for "not injected at all"). // "unknown" sentinel for "not injected at all").
var ( var (
GitCommit = "unknown" GitCommit = "unknown"
BuildTime = "unknown" BuildTime = "unknown"
DirtyFingerprint = "" DirtyFingerprint = ""
) )
// composeVersionDisplay applies the dirty-content-fingerprint feature's // composeVersionDisplay applies the dirty-content-fingerprint feature's
// strip-and-append rule to gitCommit: if dirtyFingerprint is non-empty, a // strip-and-append rule to gitCommit: if dirtyFingerprint is non-empty, a
// trailing "-dirty" suffix on gitCommit is stripped (an anchored suffix // trailing "-dirty" suffix on gitCommit is stripped (an anchored suffix
// check via strings.TrimSuffix, not a blind substring replace) and // check via strings.TrimSuffix, not a blind substring replace) and
// "-<dirtyFingerprint>" is appended in its place; otherwise gitCommit is // "-<dirtyFingerprint>" is appended in its place; otherwise gitCommit is
// returned exactly as-is (already correctly showing "-dirty" or not, per // returned exactly as-is (already correctly showing "-dirty" or not, per
// the original build-time-versioning feature). Mirrors // the original build-time-versioning feature). Mirrors
// xce_compose_version_ex() in src/lib/utils.c (task 3.1) and apiserver's // xce_compose_version_ex() in src/lib/utils.c (task 3.1) and apiserver's
// own composeVersionDisplay (task 4.4), the equivalent implementations // own composeVersionDisplay (task 4.4), the equivalent implementations
// already approved for the other version-displaying components. // already approved for the other version-displaying components.
// //
// xcecli has no startup log to protect (already established as out of // xcecli has no startup log to protect (already established as out of
// scope by the original build-time-versioning feature, see that feature's // scope by the original build-time-versioning feature, see that feature's
// own task 4.5 commit fb09afe), so this result feeds the ONLY consumption // own task 4.5 commit fb09afe), so this result feeds the ONLY consumption
// point below: the version string passed into router.Execute(cm, version), // point below: the version string passed into router.Execute(cm, version),
// which sets rootCmd.Version for Cobra's native --version/-v handling. // which sets rootCmd.Version for Cobra's native --version/-v handling.
func composeVersionDisplay(gitCommit, dirtyFingerprint string) string { func composeVersionDisplay(gitCommit, dirtyFingerprint string) string {
if dirtyFingerprint == "" { if dirtyFingerprint == "" {
return gitCommit return gitCommit
} }
return strings.TrimSuffix(gitCommit, "-dirty") + "-" + dirtyFingerprint return strings.TrimSuffix(gitCommit, "-dirty") + "-" + dirtyFingerprint
} }
func main() { func main() {
// init logger // init logger
if err := logger.InitLogger(); err != nil { if err := logger.InitLogger(); err != nil {
fmt.Println(strings.ToLower(err.Error())) fmt.Println(strings.ToLower(err.Error()))
os.Exit(1) os.Exit(1)
} }
logger.Info("********** Initialize Logger on client side done!") logger.Info("********** Initialize Logger on client side done!")
// initialize the config manager // initialize the config manager
configManager, err := config.NewConfigManager() configManager, err := config.NewConfigManager()
if err != nil { if err != nil {
fmt.Println("failed to initialize the config manager", zap.Error(err)) fmt.Println("failed to initialize the config manager", zap.Error(err))
os.Exit(1) os.Exit(1)
} }
// ensure the config manager is initialized correctly // ensure the config manager is initialized correctly
if configManager == nil { if configManager == nil {
fmt.Println("failed to initialize the config manager") fmt.Println("failed to initialize the config manager")
os.Exit(1) os.Exit(1)
} }
// execute the command // execute the command
version := fmt.Sprintf("%s built %s", composeVersionDisplay(GitCommit, DirtyFingerprint), BuildTime) version := fmt.Sprintf("%s built %s", composeVersionDisplay(GitCommit, DirtyFingerprint), BuildTime)
if err := cmd.Execute(configManager, version); err != nil { if err := cmd.Execute(configManager, version); err != nil {
fmt.Println(strings.ToLower(err.Error())) fmt.Println(strings.ToLower(err.Error()))
os.Exit(1) os.Exit(1)
} }
} }

View file

@ -26,7 +26,7 @@ Use 'xce resources definition list' to query resources definition information.`,
distributionCmd := &cobra.Command{ distributionCmd := &cobra.Command{
Use: "distribution", Use: "distribution",
Short: "Manage resource distribution", Short: "Manage resource distribution",
Long: `Manage resource distribution information. Long: `Manage resource distribution information.
Use 'xce resources distribution list' to query resources distribution information.`, Use 'xce resources distribution list' to query resources distribution information.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Display help when no subcommand is specified // Display help when no subcommand is specified
@ -39,7 +39,7 @@ Use 'xce resources distribution list' to query resources distribution informatio
definitionCmd := &cobra.Command{ definitionCmd := &cobra.Command{
Use: "definition", Use: "definition",
Short: "Manage resource definition", Short: "Manage resource definition",
Long: `Manage resource definition information. Long: `Manage resource definition information.
Use 'xce resources definition list' to query resources definition information.`, Use 'xce resources definition list' to query resources definition information.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Display help when no subcommand is specified // Display help when no subcommand is specified

View file

@ -1,45 +1,45 @@
package output package output
import ( import (
"fmt" "fmt"
"os" "os"
"text/tabwriter" "text/tabwriter"
"xcecli/models" "xcecli/models"
) )
type Output struct { type Output struct {
fields string fields string
formatJson bool formatJson bool
noheader bool noheader bool
verbose bool verbose bool
wideFormat bool wideFormat bool
jobStatusFilter []string jobStatusFilter []string
} }
func NewOutput(fields string, formatJson bool, noheader bool) *Output { func NewOutput(fields string, formatJson bool, noheader bool) *Output {
return &Output{fields: fields, formatJson: formatJson, noheader: noheader} return &Output{fields: fields, formatJson: formatJson, noheader: noheader}
} }
func (o *Output) SetVerbose(verbose bool) { func (o *Output) SetVerbose(verbose bool) {
o.verbose = verbose o.verbose = verbose
} }
func (o *Output) SetNoHeader(noheader bool) { func (o *Output) SetNoHeader(noheader bool) {
o.noheader = noheader o.noheader = noheader
} }
func (o *Output) SetWideFormat(wideFormat bool) { func (o *Output) SetWideFormat(wideFormat bool) {
o.wideFormat = wideFormat o.wideFormat = wideFormat
} }
func (o *Output) ClusterPrintData(cluster *models.ClusterInfo) { func (o *Output) ClusterPrintData(cluster *models.ClusterInfo) {
printClustersVerboseOutput(cluster) printClustersVerboseOutput(cluster)
} }
func printClustersVerboseOutput(clusters *models.ClusterInfo) { func printClustersVerboseOutput(clusters *models.ClusterInfo) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
defer w.Flush() defer w.Flush()
fmt.Fprintf(w, "XCE Standard Cluster\t%s\t%s\n", clusters.Version, "Jul 08 2025") fmt.Fprintf(w, "XCE Standard Cluster\t%s\t%s\n", clusters.Version, "Jul 08 2025")
fmt.Fprintf(w, "My cluster name: <%s>\n", clusters.ClusterName) fmt.Fprintf(w, "My cluster name: <%s>\n", clusters.ClusterName)
fmt.Fprintf(w, "The cluster status: <%s>\n", clusters.Status) fmt.Fprintf(w, "The cluster status: <%s>\n", clusters.Status)
} }

View file

@ -1,189 +1,189 @@
package config package config
import ( import (
"fmt" "fmt"
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
) )
var ( var (
Log *zap.Logger Log *zap.Logger
) )
const ( const (
defaultLoggerConfigFile = "logger.yaml" defaultLoggerConfigFile = "logger.yaml"
defaultLoggerOutputFile = "xcecli_out.log" defaultLoggerOutputFile = "xcecli_out.log"
) )
type LoggerConfig struct { type LoggerConfig struct {
Logging struct { Logging struct {
Level string `yaml:"level"` Level string `yaml:"level"`
Encoding string `yaml:"encoding"` Encoding string `yaml:"encoding"`
OutputPath string `yaml:"outputPath"` OutputPath string `yaml:"outputPath"`
File struct { File struct {
MaxSize int `yaml:"maxSize"` MaxSize int `yaml:"maxSize"`
MaxBackups int `yaml:"maxBackups"` MaxBackups int `yaml:"maxBackups"`
MaxAge int `yaml:"maxAge"` MaxAge int `yaml:"maxAge"`
Compress bool `yaml:"compress"` Compress bool `yaml:"compress"`
} `yaml:"file"` } `yaml:"file"`
EncoderConfig struct { EncoderConfig struct {
MessageKey string `yaml:"messageKey"` MessageKey string `yaml:"messageKey"`
LevelKey string `yaml:"levelKey"` LevelKey string `yaml:"levelKey"`
TimeKey string `yaml:"timeKey"` TimeKey string `yaml:"timeKey"`
NameKey string `yaml:"nameKey"` NameKey string `yaml:"nameKey"`
CallerKey string `yaml:"callerKey"` CallerKey string `yaml:"callerKey"`
FunctionKey string `yaml:"functionKey"` FunctionKey string `yaml:"functionKey"`
StacktraceKey string `yaml:"stacktraceKey"` StacktraceKey string `yaml:"stacktraceKey"`
EncodeTime string `yaml:"timeEncoder"` EncodeTime string `yaml:"timeEncoder"`
EncodeLevel string `yaml:"levelEncoder"` EncodeLevel string `yaml:"levelEncoder"`
} `yaml:"encoderConfig"` } `yaml:"encoderConfig"`
} `yaml:"logging"` } `yaml:"logging"`
} }
func InitLogger() error { func InitLogger() error {
var err error var err error
err = loadLoggerConfig() err = loadLoggerConfig()
return err return err
} }
func loadLoggerConfig() error { func loadLoggerConfig() error {
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
return fmt.Errorf("failed to get the user info: [%v]", err) return fmt.Errorf("failed to get the user info: [%v]", err)
} }
// read logger configuration file // read logger configuration file
configPath := filepath.Join(usr.HomeDir, ".xce") configPath := filepath.Join(usr.HomeDir, ".xce")
var loggerConfig LoggerConfig var loggerConfig LoggerConfig
v := viper.New() v := viper.New()
v.SetDefault("logging.level", "info") // debug, info, warn, error, fatal v.SetDefault("logging.level", "info") // debug, info, warn, error, fatal
v.SetDefault("logging.encoding", "console") // console, json v.SetDefault("logging.encoding", "console") // console, json
v.SetDefault("logging.outputPath", defaultLoggerOutputFile) // xcecli_out.log v.SetDefault("logging.outputPath", defaultLoggerOutputFile) // xcecli_out.log
v.SetDefault("logging.file.maxSize", 100) // 100MB v.SetDefault("logging.file.maxSize", 100) // 100MB
v.SetDefault("logging.file.maxBackups", 10) // 10 v.SetDefault("logging.file.maxBackups", 10) // 10
v.SetDefault("logging.file.maxAge", 14) // 14 days v.SetDefault("logging.file.maxAge", 14) // 14 days
v.SetDefault("logging.file.compress", true) // true v.SetDefault("logging.file.compress", true) // true
v.SetDefault("logging.encoderConfig.messageKey", "message") // message v.SetDefault("logging.encoderConfig.messageKey", "message") // message
v.SetDefault("logging.encoderConfig.levelKey", "level") // level v.SetDefault("logging.encoderConfig.levelKey", "level") // level
v.SetDefault("logging.encoderConfig.timeKey", "timestamp") // timestamp v.SetDefault("logging.encoderConfig.timeKey", "timestamp") // timestamp
v.SetDefault("logging.encoderConfig.nameKey", "logger") // logger v.SetDefault("logging.encoderConfig.nameKey", "logger") // logger
v.SetDefault("logging.encoderConfig.callerKey", "caller") // caller v.SetDefault("logging.encoderConfig.callerKey", "caller") // caller
v.SetDefault("logging.encoderConfig.functionKey", "func") // func v.SetDefault("logging.encoderConfig.functionKey", "func") // func
v.SetDefault("logging.encoderConfig.stacktraceKey", "stacktrace") // stacktrace v.SetDefault("logging.encoderConfig.stacktraceKey", "stacktrace") // stacktrace
v.SetDefault("logging.encoderConfig.timeEncoder", "ISO8601") // ISO8601 v.SetDefault("logging.encoderConfig.timeEncoder", "ISO8601") // ISO8601
v.SetDefault("logging.encoderConfig.levelEncoder", "capital") // capital v.SetDefault("logging.encoderConfig.levelEncoder", "capital") // capital
v.SetConfigName(defaultLoggerConfigFile) v.SetConfigName(defaultLoggerConfigFile)
v.SetConfigType("yaml") v.SetConfigType("yaml")
v.AddConfigPath(configPath) v.AddConfigPath(configPath)
// yamlContent := `# log level: debug, info, warn, error, fatal // yamlContent := `# log level: debug, info, warn, error, fatal
// # debug: detailed log // # debug: detailed log
// # info: normal log // # info: normal log
// # warn: warning log // # warn: warning log
// # error: error log // # error: error log
// # fatal: fatal log` + v.GetString("logging.level") // # fatal: fatal log` + v.GetString("logging.level")
err = v.ReadInConfig() err = v.ReadInConfig()
if err != nil { if err != nil {
os.MkdirAll(configPath, 0755) os.MkdirAll(configPath, 0755)
if err = v.WriteConfigAs(filepath.Join(configPath, "logger.yaml")); err != nil { if err = v.WriteConfigAs(filepath.Join(configPath, "logger.yaml")); err != nil {
fmt.Printf("failed to write default logger configuration file: [%v]", err) fmt.Printf("failed to write default logger configuration file: [%v]", err)
} }
} }
err = v.Unmarshal(&loggerConfig) err = v.Unmarshal(&loggerConfig)
if err != nil { if err != nil {
return fmt.Errorf("unable to decode into struct: [%v]", err) return fmt.Errorf("unable to decode into struct: [%v]", err)
} }
// Handle both formats for OutputPath // Handle both formats for OutputPath
if !filepath.IsAbs(loggerConfig.Logging.OutputPath) { if !filepath.IsAbs(loggerConfig.Logging.OutputPath) {
if loggerConfig.Logging.OutputPath == "" { if loggerConfig.Logging.OutputPath == "" {
loggerConfig.Logging.OutputPath = defaultLoggerOutputFile loggerConfig.Logging.OutputPath = defaultLoggerOutputFile
} }
// If OutputPath is relative, prepend the logs directory // If OutputPath is relative, prepend the logs directory
loggerConfig.Logging.OutputPath = filepath.Join(configPath, "logs", loggerConfig.Logging.OutputPath) loggerConfig.Logging.OutputPath = filepath.Join(configPath, "logs", loggerConfig.Logging.OutputPath)
} }
logDir := filepath.Dir(loggerConfig.Logging.OutputPath) // Extract the directory logDir := filepath.Dir(loggerConfig.Logging.OutputPath) // Extract the directory
// Create the directory if it does not exist // Create the directory if it does not exist
if err = os.MkdirAll(logDir, 0755); err != nil { if err = os.MkdirAll(logDir, 0755); err != nil {
return fmt.Errorf("failed to create client logs directory: %w", err) return fmt.Errorf("failed to create client logs directory: %w", err)
} }
Log, err = createLogger(loggerConfig) Log, err = createLogger(loggerConfig)
if err != nil { if err != nil {
return fmt.Errorf("failed to create client logger: %w", err) return fmt.Errorf("failed to create client logger: %w", err)
} }
return nil return nil
} }
func createLogger(loggerConfig LoggerConfig) (*zap.Logger, error) { func createLogger(loggerConfig LoggerConfig) (*zap.Logger, error) {
configLevel, err := zapcore.ParseLevel(loggerConfig.Logging.Level) configLevel, err := zapcore.ParseLevel(loggerConfig.Logging.Level)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// create log file and set the permission // create log file and set the permission
file, err := os.OpenFile(loggerConfig.Logging.OutputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) file, err := os.OpenFile(loggerConfig.Logging.OutputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil { if err != nil {
return nil, err return nil, err
} }
file.Close() file.Close()
// configure lumberjack for log rotation // configure lumberjack for log rotation
logWriter := &lumberjack.Logger{ logWriter := &lumberjack.Logger{
Filename: loggerConfig.Logging.OutputPath, Filename: loggerConfig.Logging.OutputPath,
MaxSize: loggerConfig.Logging.File.MaxSize, MaxSize: loggerConfig.Logging.File.MaxSize,
MaxBackups: loggerConfig.Logging.File.MaxBackups, MaxBackups: loggerConfig.Logging.File.MaxBackups,
MaxAge: loggerConfig.Logging.File.MaxAge, MaxAge: loggerConfig.Logging.File.MaxAge,
Compress: loggerConfig.Logging.File.Compress, Compress: loggerConfig.Logging.File.Compress,
} }
// Create cores for different log levels // Create cores for different log levels
core := zapcore.NewCore( core := zapcore.NewCore(
zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
MessageKey: loggerConfig.Logging.EncoderConfig.MessageKey, MessageKey: loggerConfig.Logging.EncoderConfig.MessageKey,
LevelKey: loggerConfig.Logging.EncoderConfig.LevelKey, LevelKey: loggerConfig.Logging.EncoderConfig.LevelKey,
TimeKey: loggerConfig.Logging.EncoderConfig.TimeKey, TimeKey: loggerConfig.Logging.EncoderConfig.TimeKey,
NameKey: loggerConfig.Logging.EncoderConfig.NameKey, NameKey: loggerConfig.Logging.EncoderConfig.NameKey,
CallerKey: loggerConfig.Logging.EncoderConfig.CallerKey, CallerKey: loggerConfig.Logging.EncoderConfig.CallerKey,
StacktraceKey: "", // disable stacktrace StacktraceKey: "", // disable stacktrace
EncodeTime: zapcore.ISO8601TimeEncoder, EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeLevel: zapcore.CapitalLevelEncoder, EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeCaller: zapcore.ShortCallerEncoder, EncodeCaller: zapcore.ShortCallerEncoder,
ConsoleSeparator: " ", ConsoleSeparator: " ",
}), }),
zapcore.AddSync(logWriter), zapcore.AddSync(logWriter),
configLevel, configLevel,
) )
logger := zap.New(core, logger := zap.New(core,
zap.AddCaller(), // caller info zap.AddCaller(), // caller info
zap.AddStacktrace(zap.ErrorLevel), // show error stacktrace zap.AddStacktrace(zap.ErrorLevel), // show error stacktrace
zap.AddCallerSkip(1), // skip one layer of caller zap.AddCallerSkip(1), // skip one layer of caller
) )
return logger, nil return logger, nil
} }
func Info(msg string, fields ...zap.Field) { func Info(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(false)).Info(msg, fields...) Log.WithOptions(zap.WithCaller(false)).Info(msg, fields...)
} }
func Error(msg string, fields ...zap.Field) { func Error(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(false)).Error(msg, fields...) Log.WithOptions(zap.WithCaller(false)).Error(msg, fields...)
} }
func Warn(msg string, fields ...zap.Field) { func Warn(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(false)).Warn(msg, fields...) Log.WithOptions(zap.WithCaller(false)).Warn(msg, fields...)
} }
func Debug(msg string, fields ...zap.Field) { func Debug(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(true)).Debug(msg, fields...) Log.WithOptions(zap.WithCaller(true)).Debug(msg, fields...)
} }
func Fatal(msg string, fields ...zap.Field) { func Fatal(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(false)).Fatal(msg, fields...) Log.WithOptions(zap.WithCaller(false)).Fatal(msg, fields...)
} }

View file

@ -1,92 +1,92 @@
package config package config
import ( import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"github.com/xeipuuv/gojsonschema" "github.com/xeipuuv/gojsonschema"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// validate the specified yaml file format whether is correct // validate the specified yaml file format whether is correct
// return the content of the yaml file if the format is correct // return the content of the yaml file if the format is correct
// return error if the format is incorrect // return error if the format is incorrect
func ValidateYamlFormat(yamlFilePath string) ([]byte, error) { func ValidateYamlFormat(yamlFilePath string) ([]byte, error) {
// find the file path // find the file path
yamlFilePath, err := filepath.Abs(yamlFilePath) yamlFilePath, err := filepath.Abs(yamlFilePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// get scheme file path, suppose schema file is in the same directory as yaml file // get scheme file path, suppose schema file is in the same directory as yaml file
yamlFileRootPath := filepath.Dir(yamlFilePath) yamlFileRootPath := filepath.Dir(yamlFilePath)
realFileName := filepath.Base(yamlFilePath) realFileName := filepath.Base(yamlFilePath)
preRealFileName := realFileName[:len(realFileName)-5] + "_schema.json" preRealFileName := realFileName[:len(realFileName)-5] + "_schema.json"
schemaFilePath := filepath.Join(yamlFileRootPath, "schema", preRealFileName) schemaFilePath := filepath.Join(yamlFileRootPath, "schema", preRealFileName)
// checking if the schema file exists // checking if the schema file exists
if _, err := os.Stat(schemaFilePath); os.IsNotExist(err) { if _, err := os.Stat(schemaFilePath); os.IsNotExist(err) {
err = fmt.Errorf("schema file %s does not exist ", schemaFilePath) err = fmt.Errorf("schema file %s does not exist ", schemaFilePath)
return nil, err return nil, err
} }
data, err := ValidateYAML(yamlFilePath, schemaFilePath) data, err := ValidateYAML(yamlFilePath, schemaFilePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return data, nil return data, nil
} }
// ValidateYAML validates the YAML content against the provided JSON schema // ValidateYAML validates the YAML content against the provided JSON schema
func ValidateYAML(yamlPath, schemaPath string) ([]byte, error) { func ValidateYAML(yamlPath, schemaPath string) ([]byte, error) {
// schemaLoader is a absolute file path // schemaLoader is a absolute file path
schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(schemaPath)) schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(schemaPath))
// get yaml file content by reading the file // get yaml file content by reading the file
data, err := os.ReadFile(yamlPath) data, err := os.ReadFile(yamlPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// get the yaml file into object // get the yaml file into object
var raw interface{} var raw interface{}
err = yaml.Unmarshal(data, &raw) err = yaml.Unmarshal(data, &raw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// transform into map[string]interface{} // transform into map[string]interface{}
yamlContent := convertYAML(raw) yamlContent := convertYAML(raw)
// create documentLoader // create documentLoader
documentLoader := gojsonschema.NewGoLoader(yamlContent) documentLoader := gojsonschema.NewGoLoader(yamlContent)
result, err := gojsonschema.Validate(schemaLoader, documentLoader) result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !result.Valid() { if !result.Valid() {
var errorMessages string var errorMessages string
for _, desc := range result.Errors() { for _, desc := range result.Errors() {
errorMessages += fmt.Sprintf("- %s\n", desc) errorMessages += fmt.Sprintf("- %s\n", desc)
} }
err = errors.New(errorMessages) err = errors.New(errorMessages)
return nil, err return nil, err
} }
// return the yaml content // return the yaml content
return data, nil return data, nil
} }
// convertYAML recursively converts map[interface{}]interface{} to map[string]interface{} // convertYAML recursively converts map[interface{}]interface{} to map[string]interface{}
func convertYAML(i interface{}) interface{} { func convertYAML(i interface{}) interface{} {
switch x := i.(type) { switch x := i.(type) {
case map[interface{}]interface{}: case map[interface{}]interface{}:
m2 := make(map[string]interface{}) m2 := make(map[string]interface{})
for k, v := range x { for k, v := range x {
m2[fmt.Sprintf("%v", k)] = convertYAML(v) m2[fmt.Sprintf("%v", k)] = convertYAML(v)
} }
return m2 return m2
case []interface{}: case []interface{}:
for idx, v := range x { for idx, v := range x {
x[idx] = convertYAML(v) x[idx] = convertYAML(v)
} }
} }
return i return i
} }

View file

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

View file

@ -1,172 +1,172 @@
package models package models
const ( const (
HEADER_HOST_NAME = "HOST_NAME" HEADER_HOST_NAME = "HOST_NAME"
HEADER_HOST_STATUS = "STATUS" HEADER_HOST_STATUS = "STATUS"
HEADER_HOST_NCPUS = "NCPUS" HEADER_HOST_NCPUS = "NCPUS"
HEADER_HOST_MAX_SLOT = "MAX_SLOT" HEADER_HOST_MAX_SLOT = "MAX_SLOT"
HEADER_HOST_NUM_RUN = "NUM_RUN" HEADER_HOST_NUM_RUN = "NUM_RUN"
HEADER_HOST_NUM_SSUSP = "NUM_SSUSP" HEADER_HOST_NUM_SSUSP = "NUM_SSUSP"
HEADER_HOST_NUM_USUSP = "NUM_USUSP" HEADER_HOST_NUM_USUSP = "NUM_USUSP"
HEADER_HOST_HOST_TYPE = "HOST_TYPE" HEADER_HOST_HOST_TYPE = "HOST_TYPE"
// host details info // host details info
HEADER_HOST_CPUF = "CPUF" HEADER_HOST_CPUF = "CPUF"
HEADER_HOST_JLU = "JL/U" HEADER_HOST_JLU = "JL/U"
HEADER_HOST_MAX = "MAX" HEADER_HOST_MAX = "MAX"
HEADER_HOST_NJOBS = "NJOBS" HEADER_HOST_NJOBS = "NJOBS"
HEADER_HOST_RUN = "RUN" HEADER_HOST_RUN = "RUN"
HEADER_HOST_SSUSP = "SSUSP" HEADER_HOST_SSUSP = "SSUSP"
HEADER_HOST_USUSP = "USUSP" HEADER_HOST_USUSP = "USUSP"
HEADER_HOST_RSV = "RSV" HEADER_HOST_RSV = "RSV"
HEADER_HOST_DISPT_WINDOW = "DISPATCH_WINDOW" HEADER_HOST_DISPT_WINDOW = "DISPATCH_WINDOW"
// second line // second line
HEADER_HOST_R15S = "r15s" HEADER_HOST_R15S = "r15s"
HEADER_HOST_R1M = "r1m" HEADER_HOST_R1M = "r1m"
HEADER_HOST_R15M = "r15m" HEADER_HOST_R15M = "r15m"
HEADER_HOST_UT = "ut" HEADER_HOST_UT = "ut"
HEADER_HOST_PG = "pg" HEADER_HOST_PG = "pg"
HEADER_HOST_IO = "io" HEADER_HOST_IO = "io"
HEADER_HOST_LS = "ls" HEADER_HOST_LS = "ls"
HEADER_HOST_IT = "it" HEADER_HOST_IT = "it"
HEADER_HOST_TMP = "tmp" HEADER_HOST_TMP = "tmp"
HEADER_HOST_SWP = "swp" HEADER_HOST_SWP = "swp"
HEADER_HOST_MEM = "mem" HEADER_HOST_MEM = "mem"
HEADER_HOST_SLOTS = "slots" HEADER_HOST_SLOTS = "slots"
) )
var HostDetailHeaders = []string{ var HostDetailHeaders = []string{
HEADER_HOST_STATUS, HEADER_HOST_STATUS,
HEADER_HOST_CPUF, HEADER_HOST_CPUF,
HEADER_HOST_JLU, HEADER_HOST_JLU,
HEADER_HOST_MAX, HEADER_HOST_MAX,
HEADER_HOST_NJOBS, HEADER_HOST_NJOBS,
HEADER_HOST_RUN, HEADER_HOST_RUN,
HEADER_HOST_SSUSP, HEADER_HOST_SSUSP,
HEADER_HOST_USUSP, HEADER_HOST_USUSP,
HEADER_HOST_RSV, HEADER_HOST_RSV,
HEADER_HOST_DISPT_WINDOW, HEADER_HOST_DISPT_WINDOW,
} }
var HostLoadSchedulingHeaders = []string{ var HostLoadSchedulingHeaders = []string{
HEADER_HOST_R15S, HEADER_HOST_R15S,
HEADER_HOST_R1M, HEADER_HOST_R1M,
HEADER_HOST_R15M, HEADER_HOST_R15M,
HEADER_HOST_UT, HEADER_HOST_UT,
HEADER_HOST_PG, HEADER_HOST_PG,
HEADER_HOST_IO, HEADER_HOST_IO,
HEADER_HOST_LS, HEADER_HOST_LS,
HEADER_HOST_IT, HEADER_HOST_IT,
HEADER_HOST_TMP, HEADER_HOST_TMP,
HEADER_HOST_SWP, HEADER_HOST_SWP,
HEADER_HOST_MEM, HEADER_HOST_MEM,
HEADER_HOST_SLOTS, HEADER_HOST_SLOTS,
} }
// HostsResponse define the data structure in the host query response // HostsResponse define the data structure in the host query response
type HostsResponse struct { type HostsResponse struct {
Code int `json:"code"` Code int `json:"code"`
Msg string `json:"msg"` Msg string `json:"msg"`
Data struct { Data struct {
Count int `json:"count"` Count int `json:"count"`
Rows []XCEHost `json:"rows"` Rows []XCEHost `json:"rows"`
Page_token string `json:"page_token"` Page_token string `json:"page_token"`
} `json:"data"` } `json:"data"`
} }
// Host define the data structure in the host info\ // Host define the data structure in the host info\
type XCEHost struct { type XCEHost struct {
HostID int64 `json:"host_id"` HostID int64 `json:"host_id"`
HostName string `json:"host_name"` HostName string `json:"host_name"`
HostStatus string `json:"host_status"` HostStatus string `json:"host_status"`
HostModel string `json:"host_model"` HostModel string `json:"host_model"`
HostType string `json:"host_type"` HostType string `json:"host_type"`
HostGroup string `json:"host_group"` HostGroup string `json:"host_group"`
Flag int `json:"flag"` Flag int `json:"flag"`
CPUFactor float64 `json:"cpu_factor"` CPUFactor float64 `json:"cpu_factor"`
DisptWindow interface{} `json:"dispt_window"` DisptWindow interface{} `json:"dispt_window"`
NumCPUs int `json:"num_cpus"` NumCPUs int `json:"num_cpus"`
MaxSlot int `json:"max_slot"` MaxSlot int `json:"max_slot"`
MaxMem int `json:"max_mem"` MaxMem int `json:"max_mem"`
MaxSwap int `json:"max_swap"` MaxSwap int `json:"max_swap"`
MaxTmp int `json:"max_tmp"` MaxTmp int `json:"max_tmp"`
NumDisk int `json:"num_disk"` NumDisk int `json:"num_disk"`
NumRun int `json:"num_run"` NumRun int `json:"num_run"`
NumSSusp int `json:"num_ssusp"` NumSSusp int `json:"num_ssusp"`
NumUSusp int `json:"num_ususp"` NumUSusp int `json:"num_ususp"`
NumDone int `json:"num_done"` NumDone int `json:"num_done"`
AffTopo interface{} `json:"aff_topo"` // JSONB AffTopo interface{} `json:"aff_topo"` // JSONB
GPUTopo interface{} `json:"gpu_topo"` // JSONB GPUTopo interface{} `json:"gpu_topo"` // JSONB
SchedStop SchedStop `json:"sched_stop"` // JSONB SchedStop SchedStop `json:"sched_stop"` // JSONB
RealLoad interface{} `json:"real_load"` RealLoad interface{} `json:"real_load"`
// ResourceValues carries this host's live per-resource wire values // ResourceValues carries this host's live per-resource wire values
// (r15s/r1m/r15m/ut/pg/io/ls/it/tmp/swp/mem/slots, among others), the // (r15s/r1m/r15m/ut/pg/io/ls/it/tmp/swp/mem/slots, among others), the
// same set the C scheduler's own "info worker" REPL command shows -- // same set the C scheduler's own "info worker" REPL command shows --
// populated by the apiserver from the worker's own resval pushes. // populated by the apiserver from the worker's own resval pushes.
ResourceValues []ResourceDistributionInfo `json:"resource_values"` ResourceValues []ResourceDistributionInfo `json:"resource_values"`
} }
// ResourceDistributionInfo mirrors apiserver/models.ResourceDistributionInfo's // ResourceDistributionInfo mirrors apiserver/models.ResourceDistributionInfo's
// JSON shape -- one resource's current value for one host/worker. // JSON shape -- one resource's current value for one host/worker.
type ResourceDistributionInfo struct { type ResourceDistributionInfo struct {
ResourceName string `json:"res_name"` ResourceName string `json:"res_name"`
IsDynamic bool `json:"is_dynamic"` IsDynamic bool `json:"is_dynamic"`
ValueType int `json:"value_type"` ValueType int `json:"value_type"`
BooleanValue bool `json:"boolean_value"` BooleanValue bool `json:"boolean_value"`
StringValue string `json:"string_value"` StringValue string `json:"string_value"`
NumericValue float64 `json:"numeric_value"` NumericValue float64 `json:"numeric_value"`
Location string `json:"location"` Location string `json:"location"`
} }
var HostFieldsMap = map[string]string{ var HostFieldsMap = map[string]string{
HEADER_HOST_NAME: "host_name", HEADER_HOST_NAME: "host_name",
HEADER_HOST_STATUS: "host_status", HEADER_HOST_STATUS: "host_status",
HEADER_HOST_NCPUS: "num_cores", HEADER_HOST_NCPUS: "num_cores",
HEADER_HOST_MAX_SLOT: "max_slot", HEADER_HOST_MAX_SLOT: "max_slot",
HEADER_HOST_NUM_RUN: "num_run", HEADER_HOST_NUM_RUN: "num_run",
HEADER_HOST_NUM_SSUSP: "num_ssusp", HEADER_HOST_NUM_SSUSP: "num_ssusp",
HEADER_HOST_NUM_USUSP: "num_ususp", HEADER_HOST_NUM_USUSP: "num_ususp",
HEADER_HOST_HOST_TYPE: "host_type", HEADER_HOST_HOST_TYPE: "host_type",
} }
type SchedStop struct { type SchedStop struct {
Ls string `json:"ls"` // Load Sharing Ls string `json:"ls"` // Load Sharing
Pg string `json:"pg"` // Page Pg string `json:"pg"` // Page
R1m string `json:"r1m"` // Recent 1 minute R1m string `json:"r1m"` // Recent 1 minute
Tmp string `json:"tmp"` // Temporary Tmp string `json:"tmp"` // Temporary
} }
type HostControlRequest struct { type HostControlRequest struct {
ControlAction string `json:"action" binding:"required"` //control action ControlAction string `json:"action" binding:"required"` //control action
Hostname []string `json:"host_name" binding:"required"` //host name Hostname []string `json:"host_name" binding:"required"` //host name
Comment string `json:"comment"` //comment Comment string `json:"comment"` //comment
} }
type HostControlResponse struct { type HostControlResponse struct {
Data struct { Data struct {
Accepted []*HostControlResponseItem `json:"accepted"` Accepted []*HostControlResponseItem `json:"accepted"`
Failed []*HostControlResponseItem `json:"failed"` Failed []*HostControlResponseItem `json:"failed"`
} `json:"data"` } `json:"data"`
} }
type HostControlResponseItem struct { type HostControlResponseItem struct {
HostName string `json:"host_name"` HostName string `json:"host_name"`
Msg string `json:"msg"` Msg string `json:"msg"`
} }
func (h *XCEHost) UnmarshalJSON(data []byte) error { func (h *XCEHost) UnmarshalJSON(data []byte) error {
type Alias XCEHost type Alias XCEHost
var a Alias var a Alias
if err := UnmarshalOrderMapRow(data, &a); err != nil { if err := UnmarshalOrderMapRow(data, &a); err != nil {
return err return err
} }
*h = XCEHost(a) *h = XCEHost(a)
return nil return nil
} }
type ResReqSelectRequest struct { type ResReqSelectRequest struct {
Select string `json:"select"` Select string `json:"select"`
Order string `json:"order,omitempty"` Order string `json:"order,omitempty"`
Limit int `json:"limit,omitempty"` Limit int `json:"limit,omitempty"`
PageToken string `json:"page_token,omitempty"` PageToken string `json:"page_token,omitempty"`
} }

View file

@ -60,8 +60,6 @@ var JobLoadSchedulingHeaders = []string{
HEADER_JOB_MEM, HEADER_JOB_MEM,
} }
// for array jobs // for array jobs
const ( const (
HEADER_ARRAY_JOB_JOBID = "JOBID" HEADER_ARRAY_JOB_JOBID = "JOBID"
@ -93,96 +91,96 @@ type Jsonb struct {
// Job define the data structure in the job info // Job define the data structure in the job info
type QueryJob struct { type QueryJob struct {
JobID int64 `json:"job_id"` JobID int64 `json:"job_id"`
SubmitUser string `json:"submit_user"` SubmitUser string `json:"submit_user"`
JobStatus string `json:"job_status"` JobStatus string `json:"job_status"`
JobName string `json:"job_name"` JobName string `json:"job_name"`
Command string `json:"command"` Command string `json:"command"`
QueueName string `json:"queue_name"` QueueName string `json:"queue_name"`
ProjectName string `json:"project_name"` ProjectName string `json:"project_name"`
UserGroup string `json:"user_group"` UserGroup string `json:"user_group"`
LicenseProject string `json:"license_project"` LicenseProject string `json:"license_project"`
JobDescription string `json:"job_description"` JobDescription string `json:"job_description"`
Resreq string `json:"resreq"` Resreq string `json:"resreq"`
RsvID string `json:"rsv_id"` RsvID string `json:"rsv_id"`
SLA string `json:"sla"` SLA string `json:"sla"`
MaxNumProcessors int `json:"maxnum_processors"` MaxNumProcessors int `json:"maxnum_processors"`
NumProcessors int `json:"num_processors"` NumProcessors int `json:"num_processors"`
AskedHosts string `json:"asked_hosts"` AskedHosts string `json:"asked_hosts"`
BeginTime string `json:"begin_time"` BeginTime string `json:"begin_time"`
TermTime string `json:"term_time"` TermTime string `json:"term_time"`
UserPriority int `json:"user_priority"` UserPriority int `json:"user_priority"`
DependCond string `json:"depend_cond"` DependCond string `json:"depend_cond"`
SubmitTime int64 `json:"submit_time"` SubmitTime int64 `json:"submit_time"`
ReserveTime int64 `json:"reserve_time"` ReserveTime int64 `json:"reserve_time"`
StartTime int64 `json:"start_time"` StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"` EndTime int64 `json:"end_time"`
JobRusage string `json:"job_rusage"` JobRusage string `json:"job_rusage"`
MaxMem float64 `json:"max_mem"` MaxMem float64 `json:"max_mem"`
AvgMem float64 `json:"avg_mem"` AvgMem float64 `json:"avg_mem"`
UsedMem float64 `json:"used_mem"` UsedMem float64 `json:"used_mem"`
CPUTime int `json:"cpu_time"` CPUTime int `json:"cpu_time"`
PeakCPUTime int `json:"peak_cpu_time"` PeakCPUTime int `json:"peak_cpu_time"`
PeakCPUDuration float64 `json:"peak_cpu_duration"` PeakCPUDuration float64 `json:"peak_cpu_duration"`
MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage
AvgCPUEfficiency float64 `json:"avg_cpu_efficiency"` // Average CPU 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 PeakCPUEfficiency float64 `json:"peak_cpu_efficiency"` // Peak CPU efficiency in percentage
NumHosts int `json:"num_hosts"` // Number of hosts used by the job NumHosts int `json:"num_hosts"` // Number of hosts used by the job
FirstHost string `json:"first_host"` // First host where the job ran FirstHost string `json:"first_host"` // First host where the job ran
AllocSlot string `json:"alloc_slots"` // Allocated slot information AllocSlot string `json:"alloc_slots"` // Allocated slot information
Duration int `json:"duration"` Duration int `json:"duration"`
Umask int `json:"umask"` Umask int `json:"umask"`
CWD string `json:"cwd"` CWD string `json:"cwd"`
SubmitHomeDir string `json:"submit_home_dir"` SubmitHomeDir string `json:"submit_home_dir"`
FromHost string `json:"from_host"` FromHost string `json:"from_host"`
ExecHosts ExecHosts `json:"exec_hosts"` ExecHosts ExecHosts `json:"exec_hosts"`
ExitStatus ExitStatusValue `json:"exit_status"` ExitStatus ExitStatusValue `json:"exit_status"`
ExecUID int `json:"exec_uid"` ExecUID int `json:"exec_uid"`
ExecHome string `json:"exec_home"` ExecHome string `json:"exec_home"`
ExecCWD string `json:"exec_cwd"` ExecCWD string `json:"exec_cwd"`
ExecUserName string `json:"exec_user_name"` ExecUserName string `json:"exec_user_name"`
JobPriority int `json:"job_priority"` JobPriority int `json:"job_priority"`
ChargedSAAP string `json:"charged_SAAP"` ChargedSAAP string `json:"charged_SAAP"`
PreExecCmd string `json:"pre_exec_cmd"` PreExecCmd string `json:"pre_exec_cmd"`
PostExecCmd string `json:"post_exec_cmd"` PostExecCmd string `json:"post_exec_cmd"`
RequeueErrorValues string `json:"requeue_error_values"` RequeueErrorValues string `json:"requeue_error_values"`
NumLicenses int `json:"num_licenses"` NumLicenses int `json:"num_licenses"`
LicenseNames []string `json:"license_names"` LicenseNames []string `json:"license_names"`
RunTime int `json:"run_time"` RunTime int `json:"run_time"`
RunTimeEstimation int `json:"run_time_estimation"` RunTimeEstimation int `json:"run_time_estimation"`
MigThreshold int `json:"mig_threshold"` MigThreshold int `json:"mig_threshold"`
ChkpntPeriod int `json:"chkpnt_period"` ChkpntPeriod int `json:"chkpnt_period"`
ChkpntDir string `json:"chkpnt_dir"` ChkpntDir string `json:"chkpnt_dir"`
OutDir string `json:"out_dir"` OutDir string `json:"out_dir"`
InFile string `json:"in_file"` InFile string `json:"in_file"`
OutFile string `json:"out_file"` OutFile string `json:"out_file"`
ErrFile string `json:"err_file"` ErrFile string `json:"err_file"`
MailUser string `json:"mail_user"` MailUser string `json:"mail_user"`
LoginShell string `json:"login_shell"` LoginShell string `json:"login_shell"`
ExceptList string `json:"except_list"` ExceptList string `json:"except_list"`
RLimits64 []int64 `json:"rlimits64"` RLimits64 []int64 `json:"rlimits64"`
SubEnvVars string `json:"sub_env_vars"` SubEnvVars string `json:"sub_env_vars"`
PendingReason string `json:"pending_reason"` PendingReason string `json:"pending_reason"`
JobStartInfo string `json:"job_start_info"` JobStartInfo string `json:"job_start_info"`
JobExecInfo string `json:"job_exec_info"` JobExecInfo string `json:"job_exec_info"`
CoreLimit float64 `json:"core_limit"` CoreLimit float64 `json:"core_limit"`
CPULimit string `json:"cpu_limit"` CPULimit string `json:"cpu_limit"`
RunLimit string `json:"run_limit"` RunLimit string `json:"run_limit"`
FileLimit float64 `json:"file_limit"` FileLimit float64 `json:"file_limit"`
DataLimit float64 `json:"data_limit"` DataLimit float64 `json:"data_limit"`
StackLimit float64 `json:"stack_limit"` StackLimit float64 `json:"stack_limit"`
MemLimit float64 `json:"mem_limit"` MemLimit float64 `json:"mem_limit"`
SwapLimit float64 `json:"swap_limit"` SwapLimit float64 `json:"swap_limit"`
ProcessLimit float64 `json:"process_limit"` ProcessLimit float64 `json:"process_limit"`
ThreadLimit float64 `json:"thread_limit"` ThreadLimit float64 `json:"thread_limit"`
NumFiles int `json:"num_files"` NumFiles int `json:"num_files"`
ArrayIdx int `json:"array_idx"` // ArrayIdx int `json:"array_idx"` //
UniqueJobID int64 `json:"unique_job_id"` // UniqueJobID int64 `json:"unique_job_id"` //
ExtendData JobExtendData `json:"extend_data"` ExtendData JobExtendData `json:"extend_data"`
InteractiveJobURL string `json:"interactive_job_url"` InteractiveJobURL string `json:"interactive_job_url"`
PGIDs IntSlice `json:"pgids"` PGIDs IntSlice `json:"pgids"`
PID IntSlice `json:"pids"` PID IntSlice `json:"pids"`
LaunchType string `json:"launch_type"` LaunchType string `json:"launch_type"`
} }
var JobFieldsMap = map[string]string{ var JobFieldsMap = map[string]string{

View file

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

View file

@ -1,117 +1,117 @@
package cmd package cmd
import ( import (
"fmt" "fmt"
apiserver "xcecli/cmd/auth" apiserver "xcecli/cmd/auth"
"xcecli/cmd/clusters" "xcecli/cmd/clusters"
"xcecli/cmd/hosts" "xcecli/cmd/hosts"
"xcecli/cmd/jobs" "xcecli/cmd/jobs"
"xcecli/cmd/permissions" "xcecli/cmd/permissions"
"xcecli/cmd/queues" "xcecli/cmd/queues"
"xcecli/cmd/resources" "xcecli/cmd/resources"
"xcecli/cmd/roles" "xcecli/cmd/roles"
"xcecli/cmd/users" "xcecli/cmd/users"
"xcecli/common/request" "xcecli/common/request"
"xcecli/config" "xcecli/config"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var ( var (
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "xce", Use: "xce",
Short: "XCE CLI tool for the API Server operations", Short: "XCE CLI tool for the API Server operations",
} }
configManager *config.ConfigManager configManager *config.ConfigManager
) )
// execute the root command // execute the root command
func Execute(cm *config.ConfigManager, version string) error { func Execute(cm *config.ConfigManager, version string) error {
if cm == nil { if cm == nil {
return fmt.Errorf("the config manager is not initialized") return fmt.Errorf("the config manager is not initialized")
} }
configManager = cm configManager = cm
rootCmd.Version = version rootCmd.Version = version
rootCmd.CompletionOptions.DisableDefaultCmd = true rootCmd.CompletionOptions.DisableDefaultCmd = true
rootCmd.SilenceErrors = true rootCmd.SilenceErrors = true
rootCmd.SilenceUsage = true rootCmd.SilenceUsage = true
// initialize the subcommands // initialize the subcommands
initCommands() initCommands()
return rootCmd.Execute() return rootCmd.Execute()
} }
// withAuth wraps a command with authentication check // withAuth wraps a command with authentication check
// This function creates a middleware that checks authentication before executing any command // This function creates a middleware that checks authentication before executing any command
func withAuth(cmd *cobra.Command) *cobra.Command { func withAuth(cmd *cobra.Command) *cobra.Command {
// Store original handlers // Store original handlers
originalRunE := cmd.RunE originalRunE := cmd.RunE
originalRun := cmd.Run originalRun := cmd.Run
// Create new RunE that checks authentication first // Create new RunE that checks authentication first
cmd.RunE = func(cmd *cobra.Command, args []string) error { cmd.RunE = func(cmd *cobra.Command, args []string) error {
// Check authentication before executing the command // Check authentication before executing the command
_, err := configManager.IsAuthenticated() _, err := configManager.IsAuthenticated()
if err != nil { if err != nil {
return err return err
} }
// Execute the original command logic // Execute the original command logic
if originalRunE != nil { if originalRunE != nil {
return originalRunE(cmd, args) return originalRunE(cmd, args)
} }
if originalRun != nil { if originalRun != nil {
originalRun(cmd, args) originalRun(cmd, args)
return nil return nil
} }
return nil return nil
} }
return cmd return cmd
} }
// addCommandWithAuth adds a command and all its subcommands with authentication check // addCommandWithAuth adds a command and all its subcommands with authentication check
func addCommandWithAuth(parent *cobra.Command, cmd *cobra.Command) { func addCommandWithAuth(parent *cobra.Command, cmd *cobra.Command) {
// Apply auth check to the command itself // Apply auth check to the command itself
withAuth(cmd) withAuth(cmd)
// Apply auth check to all subcommands recursively // Apply auth check to all subcommands recursively
for _, subCmd := range cmd.Commands() { for _, subCmd := range cmd.Commands() {
withAuth(subCmd) withAuth(subCmd)
} }
parent.AddCommand(cmd) parent.AddCommand(cmd)
} }
// initialize all the subcommands // initialize all the subcommands
func initCommands() { func initCommands() {
cobra.EnableCommandSorting = false cobra.EnableCommandSorting = false
rootCmd.AddGroup( rootCmd.AddGroup(
&cobra.Group{ &cobra.Group{
ID: "api", ID: "api",
Title: "API Server Operations", Title: "API Server Operations",
}, },
&cobra.Group{ &cobra.Group{
ID: "query", ID: "query",
Title: "Query Operations", Title: "Query Operations",
}, },
) )
// Add authentication commands (no auth required) // Add authentication commands (no auth required)
rootCmd.AddCommand(apiserver.NewLogonCmd(configManager)) rootCmd.AddCommand(apiserver.NewLogonCmd(configManager))
rootCmd.AddCommand(apiserver.NewLogoutCmd(configManager)) rootCmd.AddCommand(apiserver.NewLogoutCmd(configManager))
// Add commands that require authentication // Add commands that require authentication
// These commands will automatically check authentication before execution // These commands will automatically check authentication before execution
addCommandWithAuth(rootCmd, apiserver.NewListCmd(configManager)) addCommandWithAuth(rootCmd, apiserver.NewListCmd(configManager))
addCommandWithAuth(rootCmd, permissions.NewPermsCmd(configManager)) addCommandWithAuth(rootCmd, permissions.NewPermsCmd(configManager))
addCommandWithAuth(rootCmd, roles.NewRolesCmd(configManager)) addCommandWithAuth(rootCmd, roles.NewRolesCmd(configManager))
addCommandWithAuth(rootCmd, users.NewUsersCmd(configManager)) addCommandWithAuth(rootCmd, users.NewUsersCmd(configManager))
addCommandWithAuth(rootCmd, jobs.NewJobsCmd(configManager)) addCommandWithAuth(rootCmd, jobs.NewJobsCmd(configManager))
addCommandWithAuth(rootCmd, hosts.NewHostsCmd(configManager)) addCommandWithAuth(rootCmd, hosts.NewHostsCmd(configManager))
addCommandWithAuth(rootCmd, queues.NewQueuesCmd(configManager)) addCommandWithAuth(rootCmd, queues.NewQueuesCmd(configManager))
addCommandWithAuth(rootCmd, clusters.NewClustersCmd(configManager)) addCommandWithAuth(rootCmd, clusters.NewClustersCmd(configManager))
addCommandWithAuth(rootCmd, resources.NewResourcesCmd(configManager)) addCommandWithAuth(rootCmd, resources.NewResourcesCmd(configManager))
rootCmd.SetFlagErrorFunc(request.FlagErrorHandler) rootCmd.SetFlagErrorFunc(request.FlagErrorHandler)
} }