Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions internal/cacctmgr/cacctmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,14 +550,15 @@ func DeleteLicenseResource(name string, server string, clusters string) error {
}
}

func ModifyAccount(params []ModifyParam, name string) error {
func ModifyAccount(params []ModifyParam, partition string, name string) error {
var valueList []string
var err error

req := protos.ModifyAccountRequest{
Uid: userUid,
Name: name,
Force: FlagForce,
Uid: userUid,
Name: name,
Force: FlagForce,
Partition: partition,
}

for _, param := range params {
Expand Down
151 changes: 150 additions & 1 deletion internal/cacctmgr/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ var (
FlagNodeList string
FlagNumLimit uint32

FlagShowPartitionLimit bool

FlagEntityName string
FlagEntityAccount string
FlagEntityPartitions string
Expand Down Expand Up @@ -751,6 +753,7 @@ func executeModifyAccountCommand(command *CAcctMgrCommand) error {
FlagAllowedQosList = ""
FlagDeletePartitionList = ""
FlagDeleteQosList = ""
FlagPartition = ""

WhereParams := command.GetWhereParams()
SetParams, AddParams, DeleteParams := command.GetSetParams()
Expand All @@ -768,6 +771,8 @@ func executeModifyAccountCommand(command *CAcctMgrCommand) error {
switch strings.ToLower(key) {
case "name":
FlagEntityName = value
case "partition":
FlagPartition = value
default:
return util.NewCraneErr(util.ErrorCmdArg, fmt.Sprintf("Error: unknown where parameter '%s' for account modification\n", key))
}
Expand Down Expand Up @@ -816,6 +821,78 @@ func executeModifyAccountCommand(command *CAcctMgrCommand) error {
NewValue: FlagSetDefaultAccount,
RequestType: protos.OperationType_Overwrite,
})
case "maxsubmitjobs":
if FlagPartition == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxSubmitJobs' can only be set for a specific partition. Please specify the partition in the where clause.\n")
}
if err := validateUintValue(value, "maxSubmitJobs", 32); err != nil {
return util.WrapCraneErr(util.ErrorCmdArg, "%s\n", err)
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxSubmitJobs,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxjobs":
if FlagPartition == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxJobs' can only be set for a specific partition. Please specify the partition in the where clause.\n")
}
if err := validateUintValue(value, "maxJobs", 32); err != nil {
return util.WrapCraneErr(util.ErrorCmdArg, "%s\n", err)
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxJobs,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxtres":
if FlagPartition == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxTres' can only be set for a specific partition. Please specify the partition in the where clause.\n")
}
if _, err := util.ParseTres(value); err != nil {
return util.NewCraneErr(util.ErrorCmdArg, fmt.Sprintf("invalid argument %s for maxTres flag: %v", value, err))
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxTres,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxtresperjob":
if FlagPartition == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxTresPerJob' can only be set for a specific partition. Please specify the partition in the where clause.\n")
}
if _, err := util.ParseTres(value); err != nil {
return util.NewCraneErr(util.ErrorCmdArg, fmt.Sprintf("invalid argument %s for maxTresPerJob flag: %v", value, err))
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxTresPerJob,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxwall":
if FlagPartition == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxWall' can only be set for a specific partition. Please specify the partition in the where clause.\n")
}
if err := validateUintValue(value, "maxWall", 64); err != nil {
return util.WrapCraneErr(util.ErrorCmdArg, "%s\n", err)
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxWall,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxwallperjob":
if FlagPartition == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxWallPerJob' can only be set for a specific partition. Please specify the partition in the where clause.\n")
}
if err := validateUintValue(value, "maxWallPerJob", 64); err != nil {
return util.WrapCraneErr(util.ErrorCmdArg, "%s\n", err)
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxWallDurationPerJob,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
default:
return util.NewCraneErr(util.ErrorCmdArg, fmt.Sprintf("Error: unknown set parameter '%s' for account modification\n", key))
}
Expand Down Expand Up @@ -863,7 +940,7 @@ func executeModifyAccountCommand(command *CAcctMgrCommand) error {
}
}

return ModifyAccount(params, FlagEntityName)
return ModifyAccount(params, FlagPartition, FlagEntityName)
}

func executeModifyUserCommand(command *CAcctMgrCommand) error {
Expand Down Expand Up @@ -946,6 +1023,78 @@ func executeModifyUserCommand(command *CAcctMgrCommand) error {
NewValue: FlagAdminLevel,
RequestType: protos.OperationType_Overwrite,
})
case "maxsubmitjobs":
if FlagEntityPartitions == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxSubmitJobs' can only be set for a specific partition. Please specify the partition in the where clause.")
}
if err := validateUintValue(value, "maxSubmitJobs", 32); err != nil {
return util.WrapCraneErr(util.ErrorCmdArg, "%s\n", err)
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxSubmitJobs,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxjobs":
if FlagEntityPartitions == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxJobs' can only be set for a specific partition. Please specify the partition in the where clause.")
}
if err := validateUintValue(value, "maxJobs", 32); err != nil {
return util.WrapCraneErr(util.ErrorCmdArg, "%s\n", err)
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxJobs,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxtres":
if FlagEntityPartitions == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxTres' can only be set for a specific partition. Please specify the partition in the where clause.")
}
if _, err := util.ParseTres(value); err != nil {
return util.NewCraneErr(util.ErrorCmdArg, fmt.Sprintf("invalid argument %s for maxTres flag: %v", value, err))
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxTres,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxtresperjob":
if FlagEntityPartitions == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxTresPerJob' can only be set for a specific partition. Please specify the partition in the where clause.\n")
}
if _, err := util.ParseTres(value); err != nil {
return util.NewCraneErr(util.ErrorCmdArg, fmt.Sprintf("invalid argument %s for maxTresPerJob flag: %v\n", value, err))
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxTresPerJob,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxwall":
if FlagEntityPartitions == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxWall' can only be set for a specific partition. Please specify the partition in the where clause.\n")
}
if err := validateUintValue(value, "maxWall", 64); err != nil {
return util.WrapCraneErr(util.ErrorCmdArg, "%s\n", err)
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxWall,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
case "maxwallperjob":
if FlagEntityPartitions == "" {
return util.NewCraneErr(util.ErrorCmdArg, "Error: 'maxWallPerJob' can only be set for a specific partition. Please specify the partition in the where clause.\n")
}
if err := validateUintValue(value, "maxWallPerJob", 64); err != nil {
return util.WrapCraneErr(util.ErrorCmdArg, "%s\n", err)
}
params = append(params, ModifyParam{
ModifyField: protos.ModifyField_MaxWallDurationPerJob,
NewValue: value,
RequestType: protos.OperationType_Overwrite,
})
default:
return util.NewCraneErr(util.ErrorCmdArg, fmt.Sprintf("Error: unknown set parameter '%s' for user modification\n", key))
}
Expand Down
131 changes: 131 additions & 0 deletions internal/cacctmgr/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,68 @@ func PrintAccountList(accountList []*protos.AccountInfo) error {
}
fmt.Println(tree.String())

if FlagShowPartitionLimit {
PrintAccountPartitionTable(accountList)
}

// Print account table
return PrintAccountTable(accountList)
}

func PrintAccountPartitionTable(accountList []*protos.AccountInfo) {
table := tablewriter.NewWriter(os.Stdout) //table format control
util.SetBorderTable(table)
table.SetHeader([]string{"Name", "Partition", "MaxTres", "MaxTresPerJob", "MaxJobs", "MaxSubmitJobs", "MaxWall", "MaxWallPerJob"})
var tableData [][]string
for _, accountInfo := range accountList {
if accountInfo.PartitionResourceLimit != nil {
for partition, partitionResourceLimit := range accountInfo.PartitionResourceLimit {
var maxJobsStr string
if partitionResourceLimit.MaxJobs == math.MaxUint32 {
maxJobsStr = "unlimited"
} else {
maxJobsStr = strconv.FormatUint(uint64(partitionResourceLimit.MaxJobs), 10)
}
var maxSubmitJobsStr string
if partitionResourceLimit.MaxSubmitJobs == math.MaxUint32 {
maxSubmitJobsStr = "unlimited"
} else {
maxSubmitJobsStr = strconv.FormatUint(uint64(partitionResourceLimit.MaxSubmitJobs), 10)
}
var maxWallStr string
if partitionResourceLimit.MaxWall == 0 {
maxWallStr = "unlimited"
} else {
maxWallStr = util.SecondTimeFormat(int64(partitionResourceLimit.MaxWall))
}
var maxWallPerJobStr string
if partitionResourceLimit.MaxWallDurationPerJob >= util.MaxJobTimeLimit {
maxWallPerJobStr = "unlimited"
} else {
maxWallPerJobStr = util.SecondTimeFormat(int64(partitionResourceLimit.MaxWallDurationPerJob))
}

tableData = append(tableData, []string{
accountInfo.Name,
partition,
util.ResourceViewToTres(partitionResourceLimit.MaxTres),
util.ResourceViewToTres(partitionResourceLimit.MaxTresPerJob),
maxJobsStr,
maxSubmitJobsStr,
maxWallStr,
maxWallPerJobStr,
})
}
}
}
if !FlagFull && FlagFormat == "" {
util.TrimTable(&tableData)
}

table.AppendBulk(tableData)
table.Render()
Comment on lines +184 to +232
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Empty table is still rendered when no account has partition limits.

If every accountInfo.PartitionResourceLimit is nil/empty, tableData stays empty but the header is still printed, producing an empty table above the regular account table. Consider an early return when len(tableData) == 0 (after the loop) to avoid noisy output, mirroring the len(...) == 0 guard you already have in PrintUserPartitionLimit.

🔧 Proposed fix
 	}
+	if len(tableData) == 0 {
+		return
+	}
 	if !FlagFull && FlagFormat == "" {
 		util.TrimTable(&tableData)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cacctmgr/output.go` around lines 184 - 232, The code builds
tableData from accountList.PartitionResourceLimit but still renders the table
header when tableData is empty; after the loop that populates tableData (and
before util.TrimTable / table.AppendBulk / table.Render) add a guard: if
len(tableData) == 0 { return } so the function exits early and does not call
table.AppendBulk or table.Render (mirroring the guard in
PrintUserPartitionLimit); reference the variables/functions tableData,
accountInfo.PartitionResourceLimit, util.TrimTable, table.AppendBulk, and
table.Render when making the change.

}

// Name Desciption AllowedPartition Users DefaultQos AllowedQosList Coordinators Blocked
func AccountFormatOutput(tableCtx *Tableoutput, accountList []*protos.AccountInfo) error {
formatTableData := make([][]string, len(accountList))
Expand Down Expand Up @@ -330,6 +388,75 @@ func PrintUserList(userList []*protos.UserInfo) error {
return PrintUserTable(userMap)
}

func PrintUserPartitionLimit(userList []*protos.UserInfo) {
if len(userList) == 0 {
return
}

sort.Slice(userList, func(i, j int) bool {
return userList[i].Uid < userList[j].Uid
})

table := tablewriter.NewWriter(os.Stdout)
util.SetBorderTable(table)
table.SetHeader([]string{"Account", "UserName", "Uid", "Partition", "MaxTres", "MaxTresPerJob", "MaxJobs", "MaxSubmitJobs", "MaxWall", "MaxWallPerJob"})
var tableData [][]string
for _, userInfo := range userList {
if userInfo.AccountToPartitionLimit != nil {
for account, partitionToLimitMap := range userInfo.AccountToPartitionLimit {
if partitionToLimitMap != nil {
for partition, partitionResourceLimit := range partitionToLimitMap.PartitionResourceLimit {
var maxJobsStr string
if partitionResourceLimit.MaxJobs == math.MaxUint32 {
maxJobsStr = "unlimited"
} else {
maxJobsStr = strconv.FormatUint(uint64(partitionResourceLimit.MaxJobs), 10)
}
var maxSubmitJobsStr string
if partitionResourceLimit.MaxSubmitJobs == math.MaxUint32 {
maxSubmitJobsStr = "unlimited"
} else {
maxSubmitJobsStr = strconv.FormatUint(uint64(partitionResourceLimit.MaxSubmitJobs), 10)
}
var maxWallStr string
if partitionResourceLimit.MaxWall == 0 {
maxWallStr = "unlimited"
} else {
maxWallStr = util.SecondTimeFormat(int64(partitionResourceLimit.MaxWall))
}
var maxWallPerJobStr string
if partitionResourceLimit.MaxWallDurationPerJob >= util.MaxJobTimeLimit {
maxWallPerJobStr = "unlimited"
} else {
maxWallPerJobStr = util.SecondTimeFormat(int64(partitionResourceLimit.MaxWallDurationPerJob))
}

tableData = append(tableData, []string{
account,
userInfo.Name,
strconv.FormatUint(uint64(userInfo.Uid), 10),
partition,
util.ResourceViewToTres(partitionResourceLimit.MaxTres),
util.ResourceViewToTres(partitionResourceLimit.MaxTresPerJob),
maxJobsStr,
maxSubmitJobsStr,
maxWallStr,
maxWallPerJobStr,
})
}
}
}
}
}

if !FlagFull && FlagFormat == "" {
util.TrimTable(&tableData)
}

table.AppendBulk(tableData)
table.Render()
}

func UserDefaultOutput(tableCtx *Tableoutput, userMap map[string][]*protos.UserInfo) {
tableCtx.header = []string{"Account", "UserName", "Uid", "AllowedPartition", "AllowedQosList", "DefaultQos", "Coordinated", "AdminLevel", "Blocked"}
for _, value := range userMap {
Expand Down Expand Up @@ -608,6 +735,10 @@ func ShowUser(value string, account string) error {
return util.NewCraneErr(util.ErrorBackend, msg)
}

if FlagShowPartitionLimit {
PrintUserPartitionLimit(reply.UserList)
}

return PrintUserList(reply.UserList)
}

Expand Down
2 changes: 2 additions & 0 deletions internal/cacctmgr/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ func preParseGlobalFlags(args []string) []string {
}
case "--force", "-f":
FlagForce = true
case "--show-partition", "-P":
FlagShowPartitionLimit = true
default:
remainingArgs = append(remainingArgs, arg)
}
Expand Down
Loading
Loading