improve frontend jobid display
This commit is contained in:
parent
3ed7ef011a
commit
d6008ead99
6 changed files with 305 additions and 41 deletions
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"xcecli/common/format"
|
||||||
"xcecli/common/request"
|
"xcecli/common/request"
|
||||||
"xcecli/config"
|
"xcecli/config"
|
||||||
"xcecli/models"
|
"xcecli/models"
|
||||||
|
|
@ -47,8 +48,15 @@ func (c *JobController) RunJobsControl() error {
|
||||||
|
|
||||||
// control jobs by job IDs
|
// control jobs by job IDs
|
||||||
func (c *JobController) controlJobsByIDs(apiClient *request.APIClient, token string, certPath string) error {
|
func (c *JobController) controlJobsByIDs(apiClient *request.APIClient, token string, certPath string) error {
|
||||||
jobIDsStr := strings.Join(c.jobIDs, ",")
|
apiIDs := make([]string, 0, len(c.jobIDs))
|
||||||
url := fmt.Sprintf("/xce/v1/jobs/%s/control", jobIDsStr)
|
for _, id := range c.jobIDs {
|
||||||
|
apiID, err := format.ToAPIJobID(strings.TrimSpace(id))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid job ID %q: %w", id, err)
|
||||||
|
}
|
||||||
|
apiIDs = append(apiIDs, apiID)
|
||||||
|
}
|
||||||
|
url := fmt.Sprintf("/xce/v1/jobs/%s/control", strings.Join(apiIDs, ","))
|
||||||
|
|
||||||
req := models.JobControlJobIDRequest{
|
req := models.JobControlJobIDRequest{
|
||||||
Action: c.action,
|
Action: c.action,
|
||||||
|
|
@ -88,19 +96,11 @@ func (c *JobController) printJobControlResponse(req interface{}, apiClient *requ
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, data := range resp.Data.Accepted {
|
for _, data := range resp.Data.Accepted {
|
||||||
if data.ArrayIdx != 0 {
|
fmt.Printf("job <%s> %s\n", format.FormatArrayChildID(data.JobID, data.ArrayIdx), strings.TrimPrefix(data.Msg, "job "))
|
||||||
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 {
|
for _, data := range resp.Data.Failed {
|
||||||
if data.ArrayIdx != 0 {
|
fmt.Printf("job <%s> %s\n", format.FormatArrayChildID(data.JobID, data.ArrayIdx), strings.TrimPrefix(data.Msg, "job "))
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
148
src/frontend/xcecli/common/format/jobid.go
Normal file
148
src/frontend/xcecli/common/format/jobid.go
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JobID bit layout constants — mirrors src/include/config.h.
|
||||||
|
//
|
||||||
|
// |63 62|61 56|55 52|51 20|19 15|14 0|
|
||||||
|
// | TAG | CLUSTER_ID | APISERVER_ID| TIMESTAMP | SEQ_HIGH | ARRAY_IDX |
|
||||||
|
// | 2 bits | 6 bits | 4 bits | 32 bits | 5 bits | 15 bits |
|
||||||
|
const (
|
||||||
|
nTagBits = 2
|
||||||
|
tagShift = 62
|
||||||
|
nClusterIDBits = 6
|
||||||
|
clusterIDShift = 56
|
||||||
|
nApiserverIDBits = 4
|
||||||
|
apiserverIDShift = 52
|
||||||
|
nTimestampBits = 32
|
||||||
|
timestampShift = 20
|
||||||
|
nArrayIdxBits = 15
|
||||||
|
sequenceHighShift = 15 // mirrors SEQUENCE_HIGH_SHIFT in config.h (== N_ARRAY_IDX_BITS)
|
||||||
|
nSequenceBits = 20 // N_JOBID_BITS(64) - N_CLUSTER_ID_BITS(6) - N_APISERVER_ID_BITS(4) - N_TIMESTAMP_BITS(32) - N_TAG_BITS(2) = 20
|
||||||
|
tagJobArray = 0x01
|
||||||
|
)
|
||||||
|
|
||||||
|
// extractBits extracts a bit field from id at the given shift position.
|
||||||
|
// Mirrors EXTRACT_BITS(id, shift, bits) in src/include/utils.h.
|
||||||
|
func extractBits(id uint64, shift, bits int) uint64 {
|
||||||
|
mask := uint64((1 << bits) - 1)
|
||||||
|
return (id >> shift) & mask
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatJobID decomposes a 64-bit packed job ID and formats it as a
|
||||||
|
// human-readable string matching the C fmt_jobid() format in src/lib/utils.c.
|
||||||
|
// - Regular job: "{cluster}-{apiserver}-{timestamp}-{sequence}"
|
||||||
|
// - Array child: "{cluster}-{apiserver}-{timestamp}-{base}[{idx}]"
|
||||||
|
func FormatJobID(uniqueJobID int64) string {
|
||||||
|
id := uint64(uniqueJobID)
|
||||||
|
clusterID := extractBits(id, clusterIDShift, nClusterIDBits)
|
||||||
|
apiserverID := extractBits(id, apiserverIDShift, nApiserverIDBits)
|
||||||
|
timestamp := extractBits(id, timestampShift, nTimestampBits)
|
||||||
|
|
||||||
|
if (extractBits(id, tagShift, nTagBits) & tagJobArray) != 0 {
|
||||||
|
// arrayBase mirrors EXTRACT_ARRAY_JOB: the seq_high bits shifted back to their
|
||||||
|
// original position, matching C's fmt_jobid_ex output exactly.
|
||||||
|
arrayBase := extractBits(id, sequenceHighShift, nSequenceBits-nArrayIdxBits) << uint(sequenceHighShift)
|
||||||
|
arrayIdx := extractBits(id, 0, nArrayIdxBits)
|
||||||
|
return fmt.Sprintf("%d-%d-%d-%d[%d]",
|
||||||
|
clusterID, apiserverID, timestamp, arrayBase, arrayIdx)
|
||||||
|
}
|
||||||
|
sequence := extractBits(id, 0, nSequenceBits)
|
||||||
|
return fmt.Sprintf("%d-%d-%d-%d",
|
||||||
|
clusterID, apiserverID, timestamp, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsArrayJob returns true if the 64-bit packed job ID has the array job tag set.
|
||||||
|
func IsArrayJob(uniqueJobID int64) bool {
|
||||||
|
return extractBits(uint64(uniqueJobID), tagShift, nTagBits)&tagJobArray != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecomposeJobID splits a 64-bit packed job ID into its component fields.
|
||||||
|
func DecomposeJobID(uniqueJobID int64) (clusterID, apiserverID, timestamp, sequence uint32, isArray bool, arrayIdx uint32) {
|
||||||
|
id := uint64(uniqueJobID)
|
||||||
|
clusterID = uint32(extractBits(id, clusterIDShift, nClusterIDBits))
|
||||||
|
apiserverID = uint32(extractBits(id, apiserverIDShift, nApiserverIDBits))
|
||||||
|
timestamp = uint32(extractBits(id, timestampShift, nTimestampBits))
|
||||||
|
isArray = extractBits(id, tagShift, nTagBits)&tagJobArray != 0
|
||||||
|
sequence = uint32(extractBits(id, 0, nSequenceBits))
|
||||||
|
arrayIdx = uint32(extractBits(id, 0, nArrayIdxBits))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeJobID packs the components into a 64-bit job ID, mirroring MAKE_JOBID in src/include/utils.h.
|
||||||
|
func MakeJobID(clusterID, apiserverID, timestamp uint64, tag, sequence uint64) uint64 {
|
||||||
|
return (clusterID << clusterIDShift) |
|
||||||
|
(apiserverID << apiserverIDShift) |
|
||||||
|
(timestamp << timestampShift) |
|
||||||
|
(tag << tagShift) |
|
||||||
|
sequence
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// regexJobID matches "{int}-{int}-{int}-{int}" — mirrors REGEX_JOBID in config.h.
|
||||||
|
regexJobID = regexp.MustCompile(`^\s*(\d+)-(\d+)-(\d+)-(\d+)\s*$`)
|
||||||
|
// regexJobArrayID matches "{int}-{int}-{int}-{int}[{int}]" — mirrors REGEX_JOBARRAY_ID in config.h.
|
||||||
|
regexJobArrayID = regexp.MustCompile(`^\s*(\d+)-(\d+)-(\d+)-(\d+)\[(\d+)\]\s*$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseJobID parses a human-readable job ID string into a packed 64-bit job ID,
|
||||||
|
// mirroring parse_jobid() in src/lib/utils.c:482.
|
||||||
|
// Accepts: "0-0-12874529-0" (regular) or "0-0-12874529-0[5]" (array child).
|
||||||
|
func ParseJobID(s string) (uint64, error) {
|
||||||
|
if m := regexJobArrayID.FindStringSubmatch(s); m != nil {
|
||||||
|
clusterID, _ := strconv.ParseUint(m[1], 10, 64)
|
||||||
|
apiserverID, _ := strconv.ParseUint(m[2], 10, 64)
|
||||||
|
timestamp, _ := strconv.ParseUint(m[3], 10, 64)
|
||||||
|
idJob, _ := strconv.ParseUint(m[4], 10, 64)
|
||||||
|
idIdx, _ := strconv.ParseUint(m[5], 10, 64)
|
||||||
|
return MakeJobID(clusterID, apiserverID, timestamp, tagJobArray, idJob+idIdx), nil
|
||||||
|
}
|
||||||
|
if m := regexJobID.FindStringSubmatch(s); m != nil {
|
||||||
|
clusterID, _ := strconv.ParseUint(m[1], 10, 64)
|
||||||
|
apiserverID, _ := strconv.ParseUint(m[2], 10, 64)
|
||||||
|
timestamp, _ := strconv.ParseUint(m[3], 10, 64)
|
||||||
|
idJob, _ := strconv.ParseUint(m[4], 10, 64)
|
||||||
|
return MakeJobID(clusterID, apiserverID, timestamp, 0, idJob), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("invalid job ID format: %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractBaseJobID clears the tag and array_idx bits, returning the base job ID
|
||||||
|
// that the API server stores in the job_id column.
|
||||||
|
func ExtractBaseJobID(packed uint64) uint64 {
|
||||||
|
id := packed &^ (uint64((1<<nTagBits)-1) << uint(tagShift)) // clear tag bits
|
||||||
|
id = id &^ uint64((1<<nArrayIdxBits)-1) // clear array_idx bits
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatArrayChildID formats a job ID from a base job ID and array index.
|
||||||
|
// Used when only base+index are available (e.g. JobControlResponseItem).
|
||||||
|
func FormatArrayChildID(baseJobID int64, arrayIdx int) string {
|
||||||
|
if arrayIdx == 0 {
|
||||||
|
return FormatJobID(baseJobID)
|
||||||
|
}
|
||||||
|
uniqueID := uint64(baseJobID) | (uint64(tagJobArray) << uint(tagShift)) | uint64(arrayIdx)
|
||||||
|
return FormatJobID(int64(uniqueID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToAPIJobID converts a fmt_jobid-format string to the numeric form the API
|
||||||
|
// server expects ("{int}" or "{int}[{idx}]"). Plain integers pass through unchanged.
|
||||||
|
func ToAPIJobID(s string) (string, error) {
|
||||||
|
if regexJobArrayID.MatchString(s) || regexJobID.MatchString(s) {
|
||||||
|
packed, err := ParseJobID(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if IsArrayJob(int64(packed)) {
|
||||||
|
baseID := ExtractBaseJobID(packed)
|
||||||
|
arrayIdx := extractBits(packed, 0, nArrayIdxBits)
|
||||||
|
return fmt.Sprintf("%d[%d]", baseID, arrayIdx), nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d", packed), nil
|
||||||
|
}
|
||||||
|
return s, nil // already plain int or int[idx]
|
||||||
|
}
|
||||||
117
src/frontend/xcecli/common/format/jobid_test.go
Normal file
117
src/frontend/xcecli/common/format/jobid_test.go
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatJobIDRegular(t *testing.T) {
|
||||||
|
packed := MakeJobID(1, 2, 12874529, 0, 42)
|
||||||
|
got := FormatJobID(int64(packed))
|
||||||
|
if got != "1-2-12874529-42" {
|
||||||
|
t.Errorf("got %q, want %q", got, "1-2-12874529-42")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatJobIDArray(t *testing.T) {
|
||||||
|
// seq_high=0, array_idx=5 → sequence = (0<<15)|5 = 5
|
||||||
|
packed := MakeJobID(0, 0, 12874529, tagJobArray, 5)
|
||||||
|
got := FormatJobID(int64(packed))
|
||||||
|
if got != "0-0-12874529-0[5]" {
|
||||||
|
t.Errorf("got %q, want %q", got, "0-0-12874529-0[5]")
|
||||||
|
}
|
||||||
|
|
||||||
|
// seq_high=1 (shifted: 1<<15 = 32768), array_idx=7 → sequence = 32768+7
|
||||||
|
packed2 := MakeJobID(0, 0, 12874529, tagJobArray, (1<<sequenceHighShift)+7)
|
||||||
|
got2 := FormatJobID(int64(packed2))
|
||||||
|
if got2 != "0-0-12874529-32768[7]" {
|
||||||
|
t.Errorf("seq_high=1: got %q, want %q", got2, "0-0-12874529-32768[7]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseJobIDRegular(t *testing.T) {
|
||||||
|
packed := MakeJobID(1, 2, 12874529, 0, 42)
|
||||||
|
got, err := ParseJobID("1-2-12874529-42")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if got != packed {
|
||||||
|
t.Errorf("got 0x%x, want 0x%x", got, packed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseJobIDArray(t *testing.T) {
|
||||||
|
packed := MakeJobID(0, 0, 12874529, tagJobArray, 5)
|
||||||
|
got, err := ParseJobID("0-0-12874529-0[5]")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if got != packed {
|
||||||
|
t.Errorf("got 0x%x, want 0x%x", got, packed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFormatRoundTrip(t *testing.T) {
|
||||||
|
cases := []string{
|
||||||
|
"1-2-12874529-42",
|
||||||
|
"0-0-12874529-0[5]",
|
||||||
|
"0-0-12874529-32768[7]",
|
||||||
|
}
|
||||||
|
for _, want := range cases {
|
||||||
|
packed, err := ParseJobID(want)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseJobID(%q): %v", want, err)
|
||||||
|
}
|
||||||
|
got := FormatJobID(int64(packed))
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("round-trip: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseJobIDInvalid(t *testing.T) {
|
||||||
|
for _, s := range []string{"abc", "1-2-3", "1[2]", ""} {
|
||||||
|
if _, err := ParseJobID(s); err == nil {
|
||||||
|
t.Errorf("ParseJobID(%q): expected error, got nil", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToAPIJobIDRegular(t *testing.T) {
|
||||||
|
packed := MakeJobID(0, 0, 12874529, 0, 42)
|
||||||
|
s := FormatJobID(int64(packed))
|
||||||
|
got, err := ToAPIJobID(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ToAPIJobID(%q): %v", s, err)
|
||||||
|
}
|
||||||
|
want := fmt.Sprintf("%d", packed)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToAPIJobIDArray(t *testing.T) {
|
||||||
|
packed := MakeJobID(0, 0, 12874529, tagJobArray, 5)
|
||||||
|
s := FormatJobID(int64(packed))
|
||||||
|
got, err := ToAPIJobID(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ToAPIJobID(%q): %v", s, err)
|
||||||
|
}
|
||||||
|
baseID := ExtractBaseJobID(packed)
|
||||||
|
want := fmt.Sprintf("%d[5]", baseID)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToAPIJobIDPassthrough(t *testing.T) {
|
||||||
|
for _, s := range []string{"123456", "123456[3]"} {
|
||||||
|
got, err := ToAPIJobID(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ToAPIJobID(%q): %v", s, err)
|
||||||
|
}
|
||||||
|
if got != s {
|
||||||
|
t.Errorf("passthrough: got %q, want %q", got, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
jobfmt "xcecli/common/format"
|
||||||
"xcecli/models"
|
"xcecli/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -81,9 +82,7 @@ func (p *ArrayElementsPagePrinter) initColumnWidths(jobs *models.JobsResponse) {
|
||||||
width := 0
|
width := 0
|
||||||
switch format.name {
|
switch format.name {
|
||||||
case models.HEADER_JOB_JOBID:
|
case models.HEADER_JOB_JOBID:
|
||||||
// format the job id as "jobID[idx]"
|
width = len(jobfmt.FormatJobID(job.UniqueJobID))
|
||||||
arrayJobIDStr := fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx)
|
|
||||||
width = len(arrayJobIDStr)
|
|
||||||
case models.HEADER_JOB_USER:
|
case models.HEADER_JOB_USER:
|
||||||
width = len(job.SubmitUser)
|
width = len(job.SubmitUser)
|
||||||
case models.HEADER_JOB_STATUS:
|
case models.HEADER_JOB_STATUS:
|
||||||
|
|
@ -156,9 +155,7 @@ func (p *ArrayElementsPagePrinter) formatRows(jobs *models.JobsResponse, truncat
|
||||||
align := format.align
|
align := format.align
|
||||||
switch format.name {
|
switch format.name {
|
||||||
case models.HEADER_JOB_JOBID:
|
case models.HEADER_JOB_JOBID:
|
||||||
// format the job id as "jobID[idx]"
|
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*s", 20, jobfmt.FormatJobID(job.UniqueJobID)), "-")
|
||||||
arrayJobIDStr := fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx)
|
|
||||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*s", 20, arrayJobIDStr), "-")
|
|
||||||
case models.HEADER_JOB_USER:
|
case models.HEADER_JOB_USER:
|
||||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.SubmitUser, width, truncatFlag, true), "-")
|
mainRow = appendOrDefault(mainRow, FormatStringField(job.SubmitUser, width, truncatFlag, true), "-")
|
||||||
case models.HEADER_JOB_STATUS:
|
case models.HEADER_JOB_STATUS:
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
jobfmt "xcecli/common/format"
|
||||||
"xcecli/models"
|
"xcecli/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -83,11 +84,7 @@ func (p *JobsPagePrinter) initColumnWidths(jobs *models.JobsResponse) {
|
||||||
width := 0
|
width := 0
|
||||||
switch format.name {
|
switch format.name {
|
||||||
case models.HEADER_JOB_JOBID:
|
case models.HEADER_JOB_JOBID:
|
||||||
if job.UniqueJobID != job.JobID {
|
width = len(jobfmt.FormatJobID(job.UniqueJobID))
|
||||||
width = len(fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx))
|
|
||||||
} else {
|
|
||||||
width = len(fmt.Sprintf("%d", job.JobID))
|
|
||||||
}
|
|
||||||
case models.HEADER_JOB_USER:
|
case models.HEADER_JOB_USER:
|
||||||
width = len(job.SubmitUser)
|
width = len(job.SubmitUser)
|
||||||
case models.HEADER_JOB_STATUS:
|
case models.HEADER_JOB_STATUS:
|
||||||
|
|
@ -177,14 +174,8 @@ func (p *JobsPagePrinter) formatRows(jobs *models.JobsResponse, truncatFlag bool
|
||||||
|
|
||||||
switch format.name {
|
switch format.name {
|
||||||
case models.HEADER_JOB_JOBID:
|
case models.HEADER_JOB_JOBID:
|
||||||
var arrayJobIDStr string
|
|
||||||
if job.UniqueJobID != job.JobID {
|
|
||||||
arrayJobIDStr = fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx)
|
|
||||||
} else {
|
|
||||||
arrayJobIDStr = fmt.Sprintf("%d", job.JobID)
|
|
||||||
}
|
|
||||||
mainRow = appendOrDefault(mainRow,
|
mainRow = appendOrDefault(mainRow,
|
||||||
fmt.Sprintf("%"+align+"*s", 20, arrayJobIDStr), "-")
|
fmt.Sprintf("%"+align+"*s", 20, jobfmt.FormatJobID(job.UniqueJobID)), "-")
|
||||||
|
|
||||||
case models.HEADER_JOB_EXEC_HOST:
|
case models.HEADER_JOB_EXEC_HOST:
|
||||||
var execHosts models.ExecHosts
|
var execHosts models.ExecHosts
|
||||||
|
|
@ -276,11 +267,7 @@ func printJobsVerboseOutput(jobs *models.JobsResponse, filter []string) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// generate jobId for two format: 111 or 111[10]
|
jobId := jobfmt.FormatJobID(job.UniqueJobID)
|
||||||
jobId := fmt.Sprintf("%d", job.JobID)
|
|
||||||
if job.UniqueJobID != job.JobID {
|
|
||||||
jobId = fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// (1) print the job baisc info when sending "jobs list -l" command
|
// (1) print the job baisc info when sending "jobs list -l" command
|
||||||
firstLine := "Job <" + jobId + ">, "
|
firstLine := "Job <" + jobId + ">, "
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"xcecli/common/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JobsFilter struct for filtering jobs
|
// JobsFilter struct for filtering jobs
|
||||||
|
|
@ -39,12 +41,18 @@ func (f *JobsFilter) HandleJobFilter(array_type string) (string, error) {
|
||||||
|
|
||||||
if f.JobIDs != "" {
|
if f.JobIDs != "" {
|
||||||
jobIdStr := strings.Split(f.JobIDs, ",")
|
jobIdStr := strings.Split(f.JobIDs, ",")
|
||||||
|
apiIDs := make([]string, 0, len(jobIdStr))
|
||||||
for _, jobId := range jobIdStr {
|
for _, jobId := range jobIdStr {
|
||||||
if err := isValidJobId(jobId); err != nil {
|
if err := isValidJobId(jobId); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
apiID, err := format.ToAPIJobID(strings.TrimSpace(jobId))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
apiIDs = append(apiIDs, apiID)
|
||||||
}
|
}
|
||||||
filters = append(filters, fmt.Sprintf("{job_id:in:(%s)}", f.JobIDs))
|
filters = append(filters, fmt.Sprintf("{job_id:in:(%s)}", strings.Join(apiIDs, ",")))
|
||||||
return strings.Join(filters, ","), nil
|
return strings.Join(filters, ","), nil
|
||||||
} else {
|
} else {
|
||||||
if array_type == "" {
|
if array_type == "" {
|
||||||
|
|
@ -163,13 +171,20 @@ func GetJobStatusSet(f *JobsFilter) []string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
reFmtJobID = regexp.MustCompile(`^\d+-\d+-\d+-\d+$`)
|
||||||
|
reFmtJobArrayID = regexp.MustCompile(`^\d+-\d+-\d+-\d+\[\d+\]$`)
|
||||||
|
reNumericID = regexp.MustCompile(`^\d+$`)
|
||||||
|
reNumericIndex = regexp.MustCompile(`^\d+\[\d+\]$`)
|
||||||
|
)
|
||||||
|
|
||||||
func isValidJobId(jobID string) error {
|
func isValidJobId(jobID string) error {
|
||||||
matched, _ := regexp.MatchString(`^\d+$`, jobID)
|
s := strings.TrimSpace(jobID)
|
||||||
matchedIndex, _ := regexp.MatchString(`^\d+\[\d+\]$`, jobID)
|
if reFmtJobID.MatchString(s) || reFmtJobArrayID.MatchString(s) ||
|
||||||
if !matched && !matchedIndex {
|
reNumericID.MatchString(s) || reNumericIndex.MatchString(s) {
|
||||||
return fmt.Errorf("jobid '%s' contains invalid characters. Allowed characters are 0-9 or with index value", jobID)
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return fmt.Errorf("jobid '%s': must be numeric, numeric[idx], or cluster-apiserver-timestamp-seq[idx] format", jobID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function, generates search rule
|
// Helper function, generates search rule
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue