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,287 @@
package jobs
import (
"encoding/json"
"fmt"
"strings"
"xcecli/common/request"
"xcecli/config"
"xcecli/models"
"github.com/spf13/cobra"
)
// JobController defines job control
type JobController struct {
configManager *config.ConfigManager
action string
filter models.JobControlFilter
jobIDs []string
}
// NewJobController create job controller
func NewJobController(configManager *config.ConfigManager, action string) *JobController {
return &JobController{
configManager: configManager,
action: action,
}
}
// Run execute job control
func (c *JobController) RunJobsControl() error {
// Get server information
serverInfo, _ := c.configManager.GetServerInfo()
// Create API client
apiClient := request.NewAPIClient(serverInfo.URL)
apiClient.SetRootCAs(serverInfo.CACert)
if len(c.jobIDs) > 0 {
// if job IDs are specified, use job ID interface
return c.controlJobsByIDs(apiClient, serverInfo.Token, serverInfo.CACert)
} else {
// otherwise use filter conditions
return c.controlJobsByFilter(apiClient, serverInfo.Token, serverInfo.CACert)
}
}
// control jobs by job IDs
func (c *JobController) controlJobsByIDs(apiClient *request.APIClient, token string, certPath string) error {
jobIDsStr := strings.Join(c.jobIDs, ",")
url := fmt.Sprintf("/xce/v1/jobs/%s/control", jobIDsStr)
req := models.JobControlJobIDRequest{
Action: c.action,
}
// encode request data to JSON bytes
return c.printJobControlResponse(req, apiClient, token, certPath, url)
}
// control jobs by filter conditions
func (c *JobController) controlJobsByFilter(apiClient *request.APIClient, token string, certPath string) error {
url := "/xce/v1/jobs/control"
req := models.JobControlRequest{
Action: c.action,
Filter: c.filter,
}
// encode request data to JSON bytes
return c.printJobControlResponse(req, apiClient, token, certPath, url)
}
func (c *JobController) printJobControlResponse(req interface{}, apiClient *request.APIClient, token string, certPath string, url string) error {
reqData, err := json.Marshal(req)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("failed to encode request data: [%s]", errMsg)
}
var resp models.JobControlResponse
err = apiClient.PostRequestToAPIServer(
token,
certPath,
url,
reqData,
&resp,
)
if err != nil {
return err
}
for _, data := range resp.Data.Accepted {
if data.ArrayIdx != 0 {
fmt.Printf("job <%d[%d]> %s\n", data.JobID, data.ArrayIdx, strings.TrimPrefix(data.Msg, "job "))
} else {
fmt.Printf("job <%d> %s\n", data.JobID, strings.TrimPrefix(data.Msg, "job "))
}
}
for _, data := range resp.Data.Failed {
if data.ArrayIdx != 0 {
fmt.Printf("job <%d[%d]> %s\n", data.JobID, data.ArrayIdx, strings.TrimPrefix(data.Msg, "job "))
} else {
fmt.Printf("job <%d> %s\n", data.JobID, strings.TrimPrefix(data.Msg, "job "))
}
}
return nil
}
// Parse parse command line arguments
// Parse command line arguments
func (c *JobController) ParseCmdArgs(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
if len(args) == 1 && args[0] == "0" {
c.filter.AllJobs = true
} else {
// parse job ID parameters
c.jobIDs = strings.Split(args[0], ",")
return nil
}
}
// check if any filter conditions are set
if c.filter.JobName == "" && c.filter.QueueName == "" && c.filter.Users == "" && !c.filter.AllJobs {
// if no job IDs or filter conditions are specified, default to processing all jobs of the current user
return fmt.Errorf("no job IDs or filter conditions specified")
}
return nil
}
// NewKillJobsCmd create kill job command
func NewKillJobsCmd(configManager *config.ConfigManager) *cobra.Command {
controller := NewJobController(configManager, "kill")
cmd := &cobra.Command{
Use: "kill",
Short: "Kill jobs",
Long: `Kill jobs by job IDs or filters.
Examples:
xce jobs kill jobid1,jobid2,... # kill jobs by job IDs
xce jobs kill -J jobname # kill jobs by job name
xce jobs kill -q queue_name # kill jobs in a specific queue
xce jobs kill -u username # kill jobs by user`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := controller.ParseCmdArgs(cmd, args); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
if err := controller.RunJobsControl(); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
return nil
},
}
// add control options
flags := cmd.Flags()
flags.StringVarP(&controller.filter.JobName, "job_name", "J", "", "filter by job name")
flags.StringVarP(&controller.filter.QueueName, "queue", "q", "", "filter by queue name")
flags.StringVarP(&controller.filter.Users, "user", "u", "", "filter by user")
flags.BoolVarP(&controller.filter.AllJobs, "all", "a", false, "select all jobs")
return cmd
}
// NewStopJobsCmd create stop job command
func NewStopJobsCmd(configManager *config.ConfigManager) *cobra.Command {
controller := NewJobController(configManager, "stop")
cmd := &cobra.Command{
Use: "stop",
Short: "Stop jobs",
Long: `Stop jobs by job IDs or filters.
Examples:
xce jobs stop jobid1,jobid2,... # stop jobs by job IDs
xce jobs stop -J jobname # stop jobs by job name
xce jobs stop -q queue_name # stop jobs in a specific queue
xce jobs stop -u username # stop jobs by user`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := controller.ParseCmdArgs(cmd, args); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
if err := controller.RunJobsControl(); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
return nil
},
}
// add control options
flags := cmd.Flags()
flags.StringVarP(&controller.filter.JobName, "job_name", "J", "", "filter by job name")
flags.StringVarP(&controller.filter.QueueName, "queue", "q", "", "filter by queue name")
flags.StringVarP(&controller.filter.Users, "user", "u", "", "filter by user")
flags.BoolVarP(&controller.filter.AllJobs, "all", "a", false, "select all jobs")
return cmd
}
// NewResumeJobsCmd create resume job command
func NewResumeJobsCmd(configManager *config.ConfigManager) *cobra.Command {
controller := NewJobController(configManager, "resume")
cmd := &cobra.Command{
Use: "resume",
Short: "Resume jobs",
Long: `Resume jobs by job IDs or filters.
Examples:
xce jobs resume jobid1,jobid2,... # resume jobs by job IDs
xce jobs resume -J jobname # resume jobs by job name
xce jobs resume -u username # resume jobs by user`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := controller.ParseCmdArgs(cmd, args); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
if err := controller.RunJobsControl(); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
return nil
},
}
// add control options
flags := cmd.Flags()
flags.StringVarP(&controller.filter.JobName, "job_name", "J", "", "filter by job name")
flags.StringVarP(&controller.filter.Users, "user", "u", "", "filter by user")
flags.BoolVarP(&controller.filter.AllJobs, "all", "a", false, "select all jobs")
return cmd
}
func NewCheckPointJobsCmd(configManager *config.ConfigManager) *cobra.Command {
controller := NewJobController(configManager, "checkpoint")
cmd := &cobra.Command{
Use: "checkpoint",
Short: "Checkpoint jobs",
Long: `Checkpoint jobs by job IDs or filters.
Examples:
xce jobs checkpoint jobid1,jobid2,... # checkpoint jobs by job IDs`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := controller.ParseCmdArgs(cmd, args); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
if err := controller.RunJobsControl(); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
return nil
},
}
// add control options
return cmd
}
func NewRestartJobsCmd(configManager *config.ConfigManager) *cobra.Command {
restarter := NewJobRestart(configManager)
cmd := &cobra.Command{
Use: "restart",
Short: "Restart jobs",
Long: `Restart jobs by job ID.
Examples:
xce jobs restart jobid # restart job by jobid
xce jobs restart -m hostname # restart jobs by hostname`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := restarter.ParseCmdArgs(cmd, args); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
if err := restarter.RunJobsRestart(); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
return nil
},
}
// add control options
flags := cmd.Flags()
flags.StringVarP(&restarter.askedHosts, "hostname", "m", "", "restart on the specific host name")
return cmd
}

View file

@ -0,0 +1,129 @@
package jobs
import (
"fmt"
"path/filepath"
"regexp"
"strings"
"xcecli/common/request"
"xcecli/models"
)
type fileTransfer struct {
tranfers []*transferInfo
uploaded []models.UploadedFile
}
func newFileTransfer(uploadFiles []string) *fileTransfer {
f := &fileTransfer{tranfers: make([]*transferInfo, 0), uploaded: make([]models.UploadedFile, 0)}
for _, uploadFile := range uploadFiles {
transfer, err := parseFileTransfer(uploadFile)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
fmt.Printf("parse file transfer config failed: [%s]", errMsg)
continue
}
f.tranfers = append(f.tranfers, transfer)
}
return f
}
func (f *fileTransfer) uploadFiles(postFunc func([]string) (*models.UploadResponse, error)) error {
// upload files
files := make([]string, 0)
for _, transfer := range f.tranfers {
if transfer.isUpload {
files = append(files, transfer.sourcePath)
}
}
if len(files) > 0 {
resp, err := postFunc(files)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("upload files failed: [%s]", errMsg)
}
if resp.Data.Status == "accepted" {
f.uploaded = resp.Data.UploadedFiles
} else {
return fmt.Errorf("upload files failed: %v", resp.Data.Failed)
}
}
return nil
}
// find the matching uploaded file
func (f *fileTransfer) findMatchingUploadedFile(transfer *transferInfo) *models.UploadedFile {
// get the source file name
srcFileName := filepath.Base(transfer.sourcePath)
// iterate the uploaded files
for _, uploadedFile := range f.uploaded {
// if the source file name matches
if uploadedFile.SrcFile == srcFileName {
return &uploadedFile
}
}
return nil
}
func (f *fileTransfer) buildTransferFileList() []string {
var transferList []string
for _, transfer := range f.tranfers {
if transfer.isUpload {
// find the matching uploaded file
uploadedFile := f.findMatchingUploadedFile(transfer)
if uploadedFile == nil {
fmt.Printf("warning: no matching uploaded file (source file: %s, dest file: %s)\n",
transfer.sourcePath, transfer.destPath)
continue
}
transferList = append(transferList, fmt.Sprintf("%s %s %s",
uploadedFile.WebURL,
transfer.operator,
transfer.destPath))
} else {
// for the files that do not need to be uploaded, add them to the transfer list
transferList = append(transferList, fmt.Sprintf("%s %s %s",
transfer.destPath,
transfer.operator,
transfer.sourcePath))
}
}
return transferList
}
// transferInfo upload file related information
type transferInfo struct {
sourcePath string // source file path
operator string // operator
destPath string // dest file path
isUpload bool // is upload
}
// parseFileTransfer parse the file transfer string.
// Accepts "src > dst" style specs or a plain path (treated as upload to basename).
func parseFileTransfer(transfer string) (*transferInfo, error) {
re := regexp.MustCompile(`^\s*([^<>]+?)\s*(>|><|<>|<<|<)\s*([^<>]+?)\s*$`)
matches := re.FindStringSubmatch(transfer)
if len(matches) == 4 {
operator := strings.TrimSpace(matches[2])
isUpload := operator == ">" || operator == "><" || operator == "<>"
return &transferInfo{
sourcePath: strings.TrimSpace(matches[1]),
operator: operator,
destPath: strings.TrimSpace(matches[3]),
isUpload: isUpload,
}, nil
}
// plain path — treat as upload to basename
plain := strings.TrimSpace(transfer)
if plain != "" {
return &transferInfo{
sourcePath: plain,
operator: ">",
destPath: filepath.Base(plain),
isUpload: true,
}, nil
}
return nil, fmt.Errorf("cannot parse the file transfer config: [%s]", transfer)
}

View file

@ -0,0 +1,43 @@
package jobs
import (
"path/filepath"
"testing"
)
func TestParseFileTransfer_ExplicitOperator(t *testing.T) {
ti, err := parseFileTransfer("/tmp/test.txt > remote.txt")
if err != nil {
t.Fatal(err)
}
if ti.sourcePath != "/tmp/test.txt" || ti.destPath != "remote.txt" || !ti.isUpload {
t.Errorf("unexpected: %+v", ti)
}
}
func TestParseFileTransfer_PlainPath(t *testing.T) {
// plain path with no operator: treat as upload to basename
ti, err := parseFileTransfer("/tmp/test.txt")
if err != nil {
t.Fatal(err)
}
if ti.sourcePath != "/tmp/test.txt" {
t.Errorf("sourcePath: got %q want /tmp/test.txt", ti.sourcePath)
}
if ti.destPath != filepath.Base("/tmp/test.txt") {
t.Errorf("destPath: got %q want %q", ti.destPath, filepath.Base("/tmp/test.txt"))
}
if !ti.isUpload {
t.Error("expected isUpload=true for plain path")
}
}
func TestParseFileTransfer_PlainFilename(t *testing.T) {
ti, err := parseFileTransfer("myfile.sh")
if err != nil {
t.Fatal(err)
}
if ti.sourcePath != "myfile.sh" || ti.destPath != "myfile.sh" || !ti.isUpload {
t.Errorf("unexpected: %+v", ti)
}
}

View file

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

View file

@ -0,0 +1,369 @@
package jobs
import (
"fmt"
"strings"
"xcecli/common/output"
"xcecli/common/request"
"xcecli/config"
job_filter "xcecli/filters"
"xcecli/models"
"github.com/spf13/cobra"
)
var (
resp models.JobsResponse
loopCount int
pageToken string
arrayJobsStatsResp models.ArrayJobsStatsResponse
)
// JobsQuerier struct for querying jobs
type JobsQuerier struct {
configManager *config.ConfigManager
filter *job_filter.JobsFilter
fields string
jsonOutput bool
noHeader bool
verbose bool
wideFormat bool // wide format
arrayFlag bool // show array job statistics
arrayElementsFlag bool // show array job elements (sub-tasks of array jobs)
}
// NewJobsQuerier creates a new jobs querier
func NewJobsQuerier(configManager *config.ConfigManager) *JobsQuerier {
return &JobsQuerier{
configManager: configManager,
filter: &job_filter.JobsFilter{},
}
}
// Parses command line arguments, simplified version that only processes arguments not directly bound to filter
func (jq *JobsQuerier) ParseCmdArgs(cmd *cobra.Command, args []string) error {
// Process JobIDs parameter, which needs special handling
// input multiple jobids, it will use , to connect
if len(args) > 0 {
jq.filter.JobIDs = strings.Join(args, ",")
}
// Define mutual exclusions for verbose flag
if jq.verbose {
conflicts := make([]string, 0)
if jq.wideFormat {
conflicts = append(conflicts, "--wided (-w)")
}
if jq.fields != "" {
conflicts = append(conflicts, "--output (-o)")
}
if len(conflicts) > 0 {
return fmt.Errorf("flag --verbose (-l) cannot be used with: %s\n\n%s",
strings.Join(conflicts, ", "), cmd.UsageString())
}
}
return nil
}
// Run executes the jobs query command
func (jq *JobsQuerier) RunJobsList() error {
// Get server information
serverInfo, _ := jq.configManager.GetServerInfo()
// create API client
apiClient := request.NewAPIClient(serverInfo.URL)
apiClient.SetRootCAs(serverInfo.CACert)
// if array job statistics
if jq.arrayFlag {
if jq.verbose {
fmt.Println("not support -A and -l parameters together")
return nil
}
return jq.runArrayJobStats(serverInfo, apiClient)
}
// if array job elements (sub-tasks of array jobs)
if jq.arrayElementsFlag {
return jq.runArrayJobElements(serverInfo, apiClient)
}
// the following is the regular job query logic
// Build query parameters
params := make(map[string]string)
//fmt.Println(params)
// Get filter string
filterStr, err := jq.filter.HandleJobFilter("")
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("failed to get parameter: [%s]", errMsg)
} else if filterStr != "" {
params["filter"] = filterStr
}
// Field mapping
fieldsMap := models.JobFieldsMap
if jq.verbose && jq.fields == "" {
for header := range fieldsMap {
jq.fields += header + ","
}
jq.fields = strings.TrimSuffix(jq.fields, ",")
}
// Process field selection
if jq.fields != "" {
jq.fields = request.CleanFields(jq.fields)
mappedFields, err := request.CheckAndMapFields(jq.fields, fieldsMap)
if err != nil {
return err
}
params["fields"] = strings.Join(mappedFields, ",")
}
// create page printer
pagePrinter := output.NewPagePrinter(output.DataTypeJobs, output.WithFields(jq.fields),
output.WithFormatJson(jq.jsonOutput), output.WithNoHeader(jq.noHeader),
output.WithVerbose(jq.verbose), output.WithWideFormat(jq.wideFormat))
// record if the first page has been processed
isFirstPage := true
// Loop to get all pages of data
loopCount = 0
pageToken = ""
for {
if loopCount > 0 && pageToken == "" {
break
}
// Only add page_token to params if it's not empty
//if pageToken != "" {
//params["page_token"] = pageToken
//}
err = apiClient.GetRequestFromAPIServer(serverInfo.Token, pageToken, serverInfo.CACert, "/xce/v1/jobs", &resp, params)
if err != nil {
return err
}
if len(resp.Data.Rows) == 0 {
if isFirstPage {
fmt.Println("no job found")
return nil
}
}
// process the first page
if isFirstPage {
// start printing process
pagePrinter.StartPrinting()
//
pagePrinter.PrepareForPrinting(&resp)
isFirstPage = false
}
// print current page
pagePrinter.PrintPage(&resp)
// update page token and loop count
pageToken = resp.Data.Page_token
loopCount++
}
// finish printing
pagePrinter.Finish()
return nil
}
// runArrayJobStats process array job statistics
func (jq *JobsQuerier) runArrayJobStats(serverInfo *config.APIServerInfo, apiClient *request.APIClient) error {
// set query parameters, request array job statistics
params := map[string]string{
"type": "array_stats",
}
params["type"] = "array_stats"
filterStr, err := jq.filter.HandleJobFilter("array_stats")
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("failed to get parameter: [%s]", errMsg)
}
if filterStr != "" {
params["filter"] = filterStr
}
// create response object
// create array job page printer
pagePrinter := output.NewPagePrinter(output.DataTypeArrayJobs, output.WithFields(jq.fields),
output.WithFormatJson(jq.jsonOutput), output.WithNoHeader(jq.noHeader),
output.WithVerbose(jq.verbose), output.WithWideFormat(jq.wideFormat))
// record if the first page has been processed
isFirstPage := true
// Loop to get all pages of data
loopCount = 0
pageToken = ""
for {
if loopCount > 0 && pageToken == "" {
break
}
err = apiClient.GetRequestFromAPIServer(serverInfo.Token, pageToken, serverInfo.CACert, "/xce/v1/jobs", &arrayJobsStatsResp, params)
if err != nil {
return err
}
if len(arrayJobsStatsResp.Data.Rows) == 0 {
if isFirstPage {
fmt.Println("no job found")
return nil
}
}
// process the first page
if isFirstPage {
// start printing process
pagePrinter.StartPrinting()
//
pagePrinter.PrepareForPrinting(&arrayJobsStatsResp)
isFirstPage = false
}
// print current page
pagePrinter.PrintPage(&arrayJobsStatsResp)
// update page token and loop count
pageToken = arrayJobsStatsResp.Data.Page_token
loopCount++
}
// finish printing
pagePrinter.Finish()
return nil
}
// runArrayJobElements process array job elements
func (jq *JobsQuerier) runArrayJobElements(serverInfo *config.APIServerInfo, apiClient *request.APIClient) error {
// set query parameters, request array job elements
params := map[string]string{
"filter": "array_idx:ne:0", // query array_idx not equal to 0, that is, the sub-tasks of array jobs
}
// create response object
var jobsResp models.JobsResponse
// request array job elements data
err := apiClient.GetRequestFromAPIServer(serverInfo.Token, "", serverInfo.CACert, "/xce/v1/jobs?limit=1000", &jobsResp, params)
if err != nil {
return err
}
// create array job page printer
pagePrinter := output.NewPagePrinter(output.DataTypeArrayElements, output.WithFields(jq.fields),
output.WithFormatJson(jq.jsonOutput), output.WithNoHeader(jq.noHeader),
output.WithVerbose(jq.verbose), output.WithWideFormat(jq.wideFormat))
// start printing process
pagePrinter.StartPrinting()
// prepare and print data
pagePrinter.PrepareForPrinting(&jobsResp)
pagePrinter.PrintPage(&jobsResp)
// finish printing
pagePrinter.Finish()
return nil
}
func NewListJobsCmd(configManager *config.ConfigManager) *cobra.Command {
jobsQuerier := NewJobsQuerier(configManager)
// Create the list subcommand
listCmd := &cobra.Command{
Use: "list",
Short: "Query jobs info",
Long: `Query jobs detail info.
Example:
xce jobs list # list all jobs
xce jobs list jobid0,jobid1,...jobidn # list jobs by job id
xce jobs list -a # list run,done,pending,suspended jobs
xce jobs list -d # list done jobs
xce jobs list -d -r # list done and running jobs
xce jobs list -d -r -s # list done and running and suspended jobs
xce jobs list -d -r -s -p # list done and running and suspended and pending jobs
xce jobs list -d -u all # list done jobs by all users
xce jobs list -d -r -s -p -u username # list done and running and suspended and pending jobs by user
xce jobs list -d -r -s -p -u username -q queuename # list done and running and suspended and pending jobs by user and queue
xce jobs list -j jobname # list jobs by job name
xce jobs list -Jd jobdescription # list jobs by job description
xce jobs list --Lp licenseprojectname # list license project jobs
xce jobs list -o "jobid submit_user ..." # list jobs by fields
xce jobs list -o "jobid submit_user ..." --json # list jobs by fields and output in json format
xce jobs list -p # list pending jobs
xce jobs list -p -u username # list pending jobs by user
xce jobs list -p -u all # list pending jobs by all users
xce jobs list -P projectname # show project jobs
xce jobs list -q queuename # list jobs by queue
xce jobs list -q queuename -u username # list jobs by user in queue
xce jobs list -r # list running jobs
xce jobs list -r -u username # list running jobs by user
xce jobs list -s # list suspended jobs
xce jobs list -s -u username # list suspended jobs by user
xce jobs list -s -u all # list suspended jobs by all users
xce jobs list --sla serviceclassname # list jobs by service class
xce jobs list -u all # list jobs by all users
xce jobs list -u username # list jobs by user
xce jobs list -u username all # list jobs by all user
xce jobs list -u username -q queuename # list jobs by user and queue
xce jobs list -u all -q queuename # list jobs by all users and queue
xce jobs list -q -u all # list jobs by all users and all queues
xce jobs list -A # list array job statistics
xce jobs list -W # list array job elements
xce jobs list -m host1 # list jobs executing on specific host
xce jobs list -m host1,host2 # list jobs executing on multiple hosts`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := jobsQuerier.ParseCmdArgs(cmd, args); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
if err := jobsQuerier.RunJobsList(); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return nil
}
return nil
},
}
// Add command line parameters
flags := listCmd.Flags()
flags.BoolVarP(&jobsQuerier.filter.AllJobInfoFlag, "job", "a", false, "show all jobs status")
flags.StringVarP(&jobsQuerier.filter.User, "user", "u", "", "filter by user")
flags.StringVarP(&jobsQuerier.filter.Queue, "queue", "q", "", "filter by queue")
flags.BoolVarP(&jobsQuerier.filter.PendingFlag, "pending", "p", false, "show pending jobs")
flags.StringVarP(&jobsQuerier.filter.ProjectName, "project", "P", "", "show project jobs")
flags.BoolVarP(&jobsQuerier.filter.RunningFlag, "running", "r", false, "show running jobs")
flags.BoolVarP(&jobsQuerier.filter.DoneFlag, "done", "d", false, "show done jobs")
flags.BoolVarP(&jobsQuerier.filter.SSUSPFlag, "ssuspFlag", "s", false, "show suspended jobs")
flags.StringVarP(&jobsQuerier.filter.JobName, "job_name", "J", "", "filter by job name")
flags.StringVarP(&jobsQuerier.filter.LicenseProject, "Lp", "", "", "show license project jobs")
flags.StringVarP(&jobsQuerier.filter.JobDescription, "Jd", "", "", "show job description")
flags.StringVarP(&jobsQuerier.filter.ServiceClass, "sla", "", "", "show job by service class")
flags.BoolVarP(&jobsQuerier.arrayFlag, "array", "A", false, "show array job statistics")
//flags.BoolVarP(&jobsQuerier.arrayElementsFlag, "wide", "w", false, "show array job elements (sub-tasks of array jobs)")
flags.StringVarP(&jobsQuerier.filter.ExecHosts, "exechost", "m", "", "filter jobs by execution host, comma separated for multiple hosts")
flags.StringVarP(&jobsQuerier.fields, "output", "o", "", "output fields for jobs")
flags.BoolVarP(&jobsQuerier.wideFormat, "wided", "w", false, "output in wide format")
flags.BoolVarP(&jobsQuerier.verbose, "verbose", "l", false, "show job detail")
flags.BoolVarP(&jobsQuerier.jsonOutput, "json", "", false, "output in JSON format")
flags.BoolVarP(&jobsQuerier.noHeader, "noheader", "n", false, "output no header")
return listCmd
}

View file

@ -0,0 +1,86 @@
package jobs
import (
"encoding/json"
"fmt"
"xcecli/common/request"
"xcecli/config"
"xcecli/models"
"github.com/spf13/cobra"
)
func NewJobRestart(configManager *config.ConfigManager) *JobRestart {
return &JobRestart{
configManager: configManager,
jobID: "",
askedHosts: "",
}
}
type JobRestart struct {
configManager *config.ConfigManager
jobID string
askedHosts string
}
func (c *JobRestart) ParseCmdArgs(cmd *cobra.Command, args []string) error {
for _, arg := range args {
if arg != "" && c.jobID == "" {
c.jobID = arg
}
}
// check if any filter conditions are set
if c.jobID == "" {
// if no job IDs or filter conditions are specified, default to processing all jobs of the current user
return fmt.Errorf("no job ID is specified")
}
return nil
}
func (c *JobRestart) RunJobsRestart() error {
// Get server information
serverInfo, _ := c.configManager.GetServerInfo()
// Create API client
apiClient := request.NewAPIClient(serverInfo.URL)
apiClient.SetRootCAs(serverInfo.CACert)
return c.restartJobsByID(apiClient, serverInfo.Token, serverInfo.CACert)
}
func (c *JobRestart) restartJobsByID(apiClient *request.APIClient, token string, certPath string) error {
url := fmt.Sprintf("/xce/v1/jobs/%s/restart", c.jobID)
req := &models.JobRestartRequest{
AskedHosts: c.askedHosts,
}
return c.printJobRestartResponse(req, apiClient, token, certPath, url)
}
func (c *JobRestart) printJobRestartResponse(req interface{}, apiClient *request.APIClient, token string, certPath string, url string) error {
reqData, err := json.Marshal(req)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("failed to encode request data: [%s]", errMsg)
}
var resp models.JobRestartResponse
err = apiClient.PostRequestToAPIServer(
token,
certPath,
url,
reqData,
&resp,
)
if err != nil {
return err
}
row := resp.Data
if row != nil {
fmt.Printf("%s\n", row.Message)
}
return nil
}

View file

@ -0,0 +1,734 @@
package jobs
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
"time"
"xcecli/common/request"
"xcecli/common/utils"
"xcecli/config"
logger "xcecli/config"
"xcecli/models"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"go.uber.org/zap"
)
// JobSubmitter structure for managing job submission from the CLI parameters and operations
type JobSubmitter struct {
configManager *config.ConfigManager
queue string
resReq string
beginTime string
termTime string
jobName string
jobDescription string
cpuLimit string
command string
runLimit string
coreLimit string
fileLimit string
memLimit string
swapLimit string
dataLimit string
processLimit string
stackLimit string
threadLimit string
useShellLimit bool
useSystemEnv bool
subEnvVars []string
cwd string
uploadFiles []string
processorRange string
infile string
outfile string
errfile string
outdir string
configFile string
interactive bool
useTTY bool
useXJob bool
projectName string
askedHosts string
launchType string
preExecCmd string
postExecCmd string
}
// NewJobSubmitter creates a new JobSubmitter instance
func NewJobSubmitter(configManager *config.ConfigManager) *JobSubmitter {
return &JobSubmitter{
configManager: configManager,
}
}
// Parse parses command line arguments into JobSubmitter struct
func (js *JobSubmitter) ParseCmdArgs(cmd *cobra.Command, args []string) error {
// check if the -R flag is used more than once
if cmd.Flags().Changed("resreq") {
count := 0
cmd.Flags().Visit(func(f *pflag.Flag) {
if f.Name == "resreq" {
count++
}
})
if count > 1 {
return fmt.Errorf("error: the -R/--resreq flag can only be used once")
}
}
if js.interactive {
if js.useTTY && js.useXJob {
return fmt.Errorf("error: cannot use both --tty and --xjob")
}
}
// check if there is standard input redirection
fileInfo, err := os.Stdin.Stat()
if err == nil && (fileInfo.Mode()&os.ModeCharDevice) == 0 {
if tmpFileName, err := js.handleStdin(); err != nil {
errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("error reading from stdin: [%s]", errMsg)
} else if tmpFileName != "" {
defer os.Remove(tmpFileName)
}
}
// if the config file is specified, parse the config file first
if js.configFile != "" {
return js.parseConfigFile()
}
// command line parameters will override the settings in the config file
if len(args) > 0 {
js.command = strings.Join(args, " ")
}
return nil
}
func (js *JobSubmitter) handleStdin() (string, error) {
content, err := io.ReadAll(os.Stdin)
if err != nil {
return "", err
}
if len(content) == 0 {
return "", nil
}
tmpFile, err := os.CreateTemp("", "xcecli_stdin_*.xce")
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return "", fmt.Errorf("failed to create temporary file: [%s]", errMsg)
}
defer tmpFile.Close()
if _, err := tmpFile.Write(content); err != nil {
errMsg := request.ExtractErrorMessage(err)
return "", fmt.Errorf("failed to write to temporary file: [%s]", errMsg)
}
js.configFile = tmpFile.Name()
return js.configFile, nil
}
// parseConfigFile parse the parameters in the config file
func (js *JobSubmitter) parseConfigFile() error {
// check if the file exists
fileInfo, err := os.Stat(js.configFile)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("config file %s does not exist or cannot be accessed: [%s]", js.configFile, errMsg)
}
if fileInfo.IsDir() {
return fmt.Errorf("%s is a directory, not a valid config file", js.configFile)
}
// if the file is a temporary file, delete it when the function ends
if strings.Contains(js.configFile, "xcecli_stdin_") {
defer os.Remove(js.configFile)
}
// read the config file content
content, err := os.ReadFile(js.configFile)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("failed to read the config file: %s", errMsg)
}
lines := strings.Split(string(content), "\n")
fmt.Printf("config file %s contains %d lines of commands\n", js.configFile, len(lines))
// create the main command
rootCmd := NewSubmitJobsCmd(js.configManager)
// collect all parameters and commands
var allParams []string
var allCmdParts []string
// process each line
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue // skip empty lines
}
if strings.HasPrefix(line, "#xce job submit") {
// process the parameters in the comment line
paramLine := strings.TrimPrefix(line, "#xce job submit")
paramLine = strings.TrimSpace(paramLine)
if paramLine != "" {
parts := strings.Fields(paramLine)
// separate the parameters and command parts
for j := 0; j < len(parts); j++ {
if strings.HasPrefix(parts[j], "-") {
allParams = append(allParams, parts[j])
// if the parameter has a value and is not a -
if j+1 < len(parts) && !strings.HasPrefix(parts[j+1], "-") {
allParams = append(allParams, parts[j+1])
j++
}
} else {
// non-parameter part as command
cmdPart := strings.Join(parts[j:], " ")
if cmdPart != "" {
allCmdParts = append(allCmdParts, cmdPart)
}
break
}
}
}
} else {
// non-comment line as command part
allCmdParts = append(allCmdParts, line)
}
}
// merge all command parts
fullCommand := strings.Join(allCmdParts, " ")
// merge all parameters
fullArgs := append(allParams, strings.Fields(fullCommand)...)
fmt.Println("merged full command:")
fmt.Printf("xce jobs submit %s\n", strings.Join(fullArgs, " "))
// execute the merged command
rootCmd.SetArgs(fullArgs)
if err := rootCmd.Execute(); err != nil {
errMsg := request.ExtractErrorMessage(err)
return fmt.Errorf("failed to execute the command: [%s]", errMsg)
}
os.Exit(0)
return nil
}
// BuildJobRequest
func (js *JobSubmitter) BuildJobRequest() (*models.JobSubmitRequest, error) {
// create the job request with only required fields
jobReq := &models.JobSubmitRequest{
Command: js.command,
}
if js.interactive {
if js.useTTY {
jobReq.Interactive = 1 // TTY mode
} else if js.useXJob {
jobReq.Interactive = 2 // XJob mode
} else {
jobReq.Interactive = 3 // only interactive, no special mode
}
} else {
jobReq.Interactive = 0 // default value
}
// Helper function to format limit values with space
formatLimit := func(value string) string {
if value == "" {
return ""
}
re := regexp.MustCompile(`^(\d+)(MB|GB|TB|PB|[MGTP])$`)
matches := re.FindStringSubmatch(strings.ToUpper(value))
if matches != nil {
return matches[1] + " " + matches[2]
}
return value
}
// Set resource limits
jobReq.SwapLimit = formatLimit(js.swapLimit)
jobReq.DataLimit = formatLimit(js.dataLimit)
jobReq.MemLimit = formatLimit(js.memLimit)
jobReq.FileLimit = formatLimit(js.fileLimit)
jobReq.CoreLimit = formatLimit(js.coreLimit)
jobReq.ProcessLimit = formatLimit(js.processLimit)
jobReq.StackLimit = formatLimit(js.stackLimit)
jobReq.ThreadLimit = formatLimit(js.threadLimit)
jobReq.CPULimit = formatLimit(js.cpuLimit)
jobReq.RunLimit = formatLimit(js.runLimit)
// Set other fields if explicitly set
jobReq.QueueName = js.queue
jobReq.Resreq = js.resReq
jobReq.JobName = js.jobName
jobReq.JobDescription = js.jobDescription
// Parse processor range if specified
if js.processorRange != "" {
parts := strings.Split(js.processorRange, ",")
if len(parts) == 2 {
// Try to parse min and max processors
minProc, err := utils.ParseInt(parts[0])
if err != nil {
return nil, errors.New("invalid min processor value")
}
maxProc, err := utils.ParseInt(parts[1])
if err != nil {
return nil, errors.New("invalid max processor value")
}
jobReq.NumProcessors = minProc
jobReq.MaxNumProcessors = maxProc
} else {
// Single value case
numProc, err := utils.ParseInt(js.processorRange)
if err != nil {
return nil, errors.New("invalid processor value")
}
jobReq.NumProcessors = numProc
}
}
// parse the begin time
if js.beginTime != "" {
// Parse the time in local timezone
parsedTime, err := utils.ParseTime(js.beginTime)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return nil, fmt.Errorf("invalid begin time value and %s", errMsg)
}
jobReq.BeginTime = parsedTime
}
// If the date is in the past, add appropriate time
// parse the end time
if js.termTime != "" {
parsedTime, err := utils.ParseTime(js.termTime)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return nil, fmt.Errorf("invalid end time value and %s", errMsg)
}
jobReq.TermTime = parsedTime
}
// if shell limit is enabled, get the system limits
if js.useShellLimit {
limits := utils.NewSystemLimits()
// set the limits in the job request
if val, err := limits.GetCPULimit(); err == nil && val > 0 {
jobReq.CPULimit = fmt.Sprintf("%d", val)
}
if val, err := limits.GetFileLimit(); err == nil && val > 0 {
jobReq.FileLimit = fmt.Sprintf("%d", val)
}
if val, err := limits.GetDataLimit(); err == nil && val > 0 {
jobReq.DataLimit = fmt.Sprintf("%d", val)
}
if val, err := limits.GetStackLimit(); err == nil && val > 0 {
jobReq.StackLimit = fmt.Sprintf("%d", val)
}
if val, err := limits.GetCoreLimit(); err == nil && val > 0 {
jobReq.CoreLimit = fmt.Sprintf("%d", val)
}
if val, err := limits.GetProcessLimit(); err == nil && val > 0 {
jobReq.ProcessLimit = fmt.Sprintf("%d", val)
}
if val, err := limits.GetThreadLimit(); err == nil && val > 0 {
jobReq.ThreadLimit = fmt.Sprintf("%d", val)
}
if val, err := limits.GetMemLimit(); err == nil && val > 0 {
jobReq.MemLimit = fmt.Sprintf("%d", val)
}
if val, err := limits.GetSwapLimit(); err == nil && val > 0 {
jobReq.SwapLimit = fmt.Sprintf("%d", val)
}
}
// if system environment variables are enabled, get and set them
if js.useSystemEnv {
systemEnv := utils.GetSystemEnv()
filteredEnv := []string{}
filteredEnv = append(filteredEnv, systemEnv...)
jobReq.Env = filteredEnv
}
// Add submitted environment variables
if len(js.subEnvVars) > 0 {
jobReq.Env = append(jobReq.Env, js.getEnvironmentVariables()...)
}
if js.infile != "" {
jobReq.InFile = js.infile
}
if js.outfile != "" {
jobReq.OutFile = js.outfile
}
if js.errfile != "" {
jobReq.ErrFile = js.errfile
}
if js.outdir != "" {
jobReq.OutDir = js.outdir
}
if js.projectName != "" {
jobReq.ProjectName = js.projectName
}
if js.cwd != "" {
jobReq.CWD = js.cwd
} else {
cwd, _ := os.Getwd()
jobReq.CWD = cwd
}
if js.askedHosts != "" {
jobReq.AskedHosts = js.askedHosts
}
if js.launchType != "" {
jobReq.LaunchType = js.launchType
}
if js.preExecCmd != "" {
jobReq.PreExecCmd = js.preExecCmd
}
if js.postExecCmd != "" {
jobReq.PostExecCmd = js.postExecCmd
}
return jobReq, nil
}
// Run executes the job submission operation
func (js *JobSubmitter) RunJobsSubmit() error {
// Get server info and validate authentication
serverInfo, _ := js.configManager.GetServerInfo()
if js.command == "" {
return fmt.Errorf("please provide the command to execute")
}
if js.infile != "" {
js.uploadFiles = append(js.uploadFiles, fmt.Sprintf("%s > %s", js.infile, js.infile))
}
if js.outfile != "" {
js.uploadFiles = append(js.uploadFiles, fmt.Sprintf("%s < %s", js.outfile, js.outfile))
}
if js.errfile != "" {
js.uploadFiles = append(js.uploadFiles, fmt.Sprintf("%s < %s", js.errfile, js.errfile))
}
fileExists := func(path string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("error: the file %s does not exist, please check the file name or path", path)
}
if info.IsDir() {
return fmt.Errorf("error: the file %s is a directory, please check the file name or path", path)
}
return nil
}
if js.infile != "" {
if err := fileExists(js.infile); err != nil {
return err
}
}
if js.outdir != "" {
info, err := os.Stat(js.outdir)
if os.IsNotExist(err) {
return fmt.Errorf("error: the output directory %s does not exist", js.outdir)
}
if !info.IsDir() {
return fmt.Errorf("error: the output directory %s is not a directory", js.outdir)
}
}
// create the API client and submit the job
apiClient := request.NewAPIClient(serverInfo.URL)
apiClient.SetRootCAs(serverInfo.CACert)
var transferFile []string
if len(js.uploadFiles) > 0 {
fileTrans := newFileTransfer(js.uploadFiles)
err := fileTrans.uploadFiles(func(s []string) (*models.UploadResponse, error) {
return apiClient.PostFile("Bearer "+serverInfo.Token, s)
})
if err != nil {
return err
}
transferFile = fileTrans.buildTransferFileList()
}
// build the job request
jobReq, err := js.BuildJobRequest()
if err != nil {
return err
}
jobReq.TransferFiles = transferFile
jobResp, err := apiClient.SubmitJob("Bearer "+serverInfo.Token, jobReq)
logger.Debug("jobResp", zap.Any("jobResp", jobResp), zap.Error(err))
logger.Info("jobResp", zap.Any("jobResp", jobResp), zap.Error(err))
if err != nil {
return err
}
fmt.Printf("%s\n", jobResp.Data.Message)
logger.Debug("job submitted successfully", zap.String("message", jobResp.Data.Message))
logger.Info("job submitted successfully", zap.String("message", jobResp.Data.Message))
if js.interactive {
fmt.Println("<<waiting for dispatch ...>>")
url, err := js.waitForInteractiveURL(apiClient, serverInfo.Token, fmt.Sprintf("%d", jobResp.Data.JobID))
if err != nil {
return err
}
fmt.Printf("interactive session URL: %s\n", url)
// automatically open the browser
if err := openBrowser(url); err != nil {
errMsg := request.ExtractErrorMessage(err)
fmt.Printf("warning: failed to open browser: [%s]\n", errMsg)
fmt.Println("Please manually open the URL in your browser.")
}
}
return nil
}
func (js *JobSubmitter) checkJobStatus(apiClient *request.APIClient, token string, jobID string) (string, error) {
var resp models.JobsResponse
err := apiClient.GetRequestFromAPIServer(
"Bearer "+token,
"",
"",
fmt.Sprintf("/xce/v1/jobs/%s", jobID),
&resp,
map[string]string{ /*"fields": "interactive_job_url"*/ },
)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return "", fmt.Errorf("failed to get job details: [%s]", errMsg)
}
for _, row := range resp.Data.Rows {
switch row.JobStatus {
case "PEND":
return row.JobStatus, nil
case "RUN":
var execHosts models.ExecHosts
if err := json.Unmarshal([]byte(row.ExecHosts.String()), &execHosts); err == nil && len(execHosts.HostSlots) > 0 {
fmt.Println("<<Starting on", execHosts.HostSlots[0].HostName, ">>")
}
return row.JobStatus, nil
default:
return "", fmt.Errorf("job %s is in state %s, please check the job status", jobID, row.JobStatus)
}
}
return "", fmt.Errorf("timeout waiting for job status")
}
func (js *JobSubmitter) checkExtendData(apiClient *request.APIClient, token string, jobID string) (string, error) {
var resp models.JobsResponse
err := apiClient.GetRequestFromAPIServer(
"Bearer "+token,
"",
"",
fmt.Sprintf("/xce/v1/jobs/%s", jobID),
&resp,
map[string]string{"fields": "interactive_job_url"},
)
if err != nil {
errMsg := request.ExtractErrorMessage(err)
return "", fmt.Errorf("failed to get job details: [%s]", errMsg)
}
for _, row := range resp.Data.Rows {
//fmt.Println(row)
if row.InteractiveJobURL != "" {
return row.InteractiveJobURL, nil
}
}
return "", fmt.Errorf("no interactive job URL found")
}
func (js *JobSubmitter) waitForInteractiveURL(apiClient *request.APIClient, token string, jobID string) (string, error) {
timeout := time.After(24 * time.Hour)
timer := time.NewTimer(0)
defer timer.Stop()
for {
select {
case <-timeout:
return "", fmt.Errorf("timeout waiting for interactive URL")
case <-timer.C:
status, err := js.checkJobStatus(apiClient, token, jobID)
if err != nil {
return "", err
}
if status == "PEND" {
timer.Reset(5 * time.Second)
continue
}
if status == "RUN" {
//time.Sleep(45 * time.Second)
return js.checkExtendData(apiClient, token, jobID)
}
return "", fmt.Errorf("job %s is in state %s, please check the job status", jobID, status)
}
}
}
func openBrowser(url string) error {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("cmd", "/c", "start", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
return err
}
// NewSubCmd creates a command for submitting jobs
func NewSubmitJobsCmd(configManager *config.ConfigManager) *cobra.Command {
jobSubmitter := NewJobSubmitter(configManager)
cmd := &cobra.Command{
Use: "submit",
Short: "Submit job to API Server",
Long: `Submit job to API Server.
Example:
xce jobs submit -q queue_name sleep 10 # submit job to queue queue_name with command "sleep 10"
xce jobs submit -R "select(!mg)" sleep 10 # submit job with resource requirement "select(!mg)"
xce jobs submit -c 10 sleep 10 # submit job with cpu limit 10
xce jobs submit -C 10 sleep 10 # submit job with core limit 10
xce jobs submit -b "2025-03-20 10:00" sleep 10 # submit job with begin time "2025-03-20 10:00"
xce jobs submit -t "2025-03-20 10:00" sleep 10 # submit job with termination time "2025-03-20 10:00"
xce jobs submit -J "test" sleep 10 # submit job with job name "test"
xce jobs submit --Jd "test" sleep 10 # submit job with job description "test"
xce jobs submit -F 1024 sleep 10 # submit job with file limit 1024
xce jobs submit -W 10 sleep 10 # sets the runtime limit of the job
xce jobs submit -M 1024 sleep 10 # sets the memory limit of the job
xce jobs submit -v 1024 sleep 10 # sets the swap limit of the job
xce jobs submit -D 1024 sleep 10 # sets the data limit of the job
xce jobs submit -p 10 sleep 10 # sets the process limit of the job
xce jobs submit -S 1024 sleep 10 # sets the stack limit of the job
xce jobs submit -T 1024 sleep 10 # sets the thread limit of the job
xce jobs submit --ul sleep 10 # use current shell limits
xce jobs submit --env sleep 10 # use system environment variables
xce jobs submit --cwd "/tmp/" sleep 10 # use current working directory for job execution
xce jobs submit -f "t1.txt > t2.txt" sleep 10 # submit job with file "t1.txt" and redirect the output to "t2.txt"
xce jobs submit -q "normal" sleep 10 # submit job to queue "normal" and "priority"
xce jobs submit -n 4,6 sleep 10 # submit job with processor range 4-6
xce jobs submit -P project_name sleep 10 # submit job with project name
xce jobs submit --configfile job.xce # submit job using parameters from a configuration file
xce jobs submit -m "host1 host2" sleep 10 # submit job into host1 host2
xce jobs submit -I --tty sleep 10 # submit interactive job with tty
xce jobs submit -I --xjob sleep 10 # submit interactive job with xjob`,
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {
if err := jobSubmitter.ParseCmdArgs(cmd, args); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return
}
if err := jobSubmitter.RunJobsSubmit(); err != nil {
fmt.Println(strings.ToLower(err.Error()))
return
}
},
}
// add the flags
flags := cmd.Flags()
// modify the resreq flag
resReqFlag := &pflag.Flag{
Name: "resreq",
Shorthand: "R",
Usage: "specify the resource requirement (can only be used once)",
Value: (*stringValue)(&jobSubmitter.resReq),
NoOptDefVal: "",
}
flags.AddFlag(resReqFlag)
//modify the flags
flags.StringVarP(&jobSubmitter.beginTime, "begin_time", "b", "", "specify the begin time")
flags.StringVarP(&jobSubmitter.coreLimit, "core_limit", "C", "", "specify the core number")
flags.StringVarP(&jobSubmitter.jobName, "job_name", "J", "", "specify the job name")
flags.StringVarP(&jobSubmitter.jobDescription, "Jd", "", "", "specify the job description")
flags.StringVarP(&jobSubmitter.termTime, "term_time", "t", "", "specify the termination time")
flags.StringVarP(&jobSubmitter.cpuLimit, "cpu_limit", "c", "", "specify the cpu limit")
flags.StringVarP(&jobSubmitter.runLimit, "run_limit", "W", "", "specify the run time")
flags.StringVarP(&jobSubmitter.fileLimit, "file_limit", "F", "", "specify the file limit")
flags.StringVarP(&jobSubmitter.memLimit, "mem_limit", "M", "", "specify the memory limit")
flags.StringVarP(&jobSubmitter.swapLimit, "swap_limit", "v", "", "specify the swap limit")
flags.StringVarP(&jobSubmitter.dataLimit, "data_limit", "D", "", "specify the data limit")
flags.StringVarP(&jobSubmitter.processLimit, "process_limit", "p", "", "specify the process limit")
flags.StringVarP(&jobSubmitter.stackLimit, "stack_limit", "S", "", "specify the stack limit")
flags.StringVarP(&jobSubmitter.threadLimit, "thread_limit", "T", "", "specify the thread limit")
flags.BoolVarP(&jobSubmitter.useShellLimit, "ul", "", false, "use current shell limits")
flags.BoolVarP(&jobSubmitter.useSystemEnv, "env", "", false, "use system environment variables")
flags.StringVarP(&jobSubmitter.cwd, "cwd", "", "", "current working directory")
flags.StringArrayVarP(&jobSubmitter.uploadFiles, "file", "f", []string{}, "specify the files to upload")
flags.StringVarP(&jobSubmitter.queue, "queue_name", "q", "", "specify the job queue")
flags.StringVarP(&jobSubmitter.processorRange, "processor_range", "n", "", "specify processor range in format min,max (e.g. 4,6)")
flags.StringVarP(&jobSubmitter.infile, "infile", "i", "", "specify the input file")
flags.StringVarP(&jobSubmitter.outfile, "outfile", "o", "", "specify the output file")
flags.StringVarP(&jobSubmitter.errfile, "errfile", "e", "", "specify the error file")
flags.StringVarP(&jobSubmitter.outdir, "outdir", "d", "", "specify the output directory")
flags.StringVarP(&jobSubmitter.projectName, "project_name", "P", "", "specify the project name")
flags.StringVar(&jobSubmitter.configFile, "configfile", "", "specify the configuration file for job submission")
flags.BoolVarP(&jobSubmitter.interactive, "interactive", "I", false, "submit interactive job")
flags.BoolVar(&jobSubmitter.useTTY, "tty", false, "use TTY for interactive job")
flags.BoolVar(&jobSubmitter.useXJob, "xjob", false, "use XJob for interactive job")
flags.StringVarP(&jobSubmitter.askedHosts, "m", "m", "", "specify the running hosts, with space for multiple values")
flags.StringVarP(&jobSubmitter.launchType, "launch_type", "", "", "specify the launch type")
flags.StringVarP(&jobSubmitter.preExecCmd, "pre_exec", "", "", "specify the pre-execution command")
flags.StringVarP(&jobSubmitter.postExecCmd, "post_exec", "", "", "specify the post-execution command")
return cmd
//
}
// modify the stringValue
type stringValue string
func (s *stringValue) String() string { return string(*s) }
func (s *stringValue) Set(val string) error {
if *s != "" {
return fmt.Errorf("the -R/--resreq flag can only be used once")
}
*s = stringValue(val)
return nil
}
func (s *stringValue) Type() string { return "string" }
func (js *JobSubmitter) getEnvironmentVariables() []string {
var envVars []string
for _, env := range js.subEnvVars {
// Skip environment variables with multiple equal signs
if strings.Count(env, "=") > 1 {
continue
}
envVars = append(envVars, env)
}
return envVars
}