Skip to content

Commit

Permalink
Add node state to CLI node list
Browse files Browse the repository at this point in the history
Also enables the use of ordering and filtering of the status field
(nodeinfo.State).  This means that the approval field is no longer
labelled as Status, but as Approval.
  • Loading branch information
rossjones committed Apr 3, 2024
1 parent a8ed43e commit 6bf5028
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 41 deletions.
6 changes: 5 additions & 1 deletion cmd/cli/node/columns.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ var alwaysColumns = []output.TableColumn[*models.NodeInfo]{
Value: func(ni *models.NodeInfo) string { return ni.NodeType.String() },
},
{
ColumnConfig: table.ColumnConfig{Name: "status"},
ColumnConfig: table.ColumnConfig{Name: "approval"},
Value: func(ni *models.NodeInfo) string { return ni.Approval.String() },
},
{
ColumnConfig: table.ColumnConfig{Name: "status"},
Value: func(ni *models.NodeInfo) string { return ni.State.String() },
},
}

var toggleColumns = map[string][]output.TableColumn[*models.NodeInfo]{
Expand Down
31 changes: 22 additions & 9 deletions cmd/cli/node/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ import (

var defaultColumnGroups = []string{"labels", "capacity"}
var orderByFields = []string{"id", "type", "available_cpu", "available_memory", "available_disk", "available_gpu", "status"}
var filterStatusValues = []string{"approved", "pending", "rejected"}
var filterApprovalValues = []string{"approved", "pending", "rejected"}
var filterStatusValues = []string{"healthy", "unhealthy", "unknown"}

// ListOptions is a struct to support node command
type ListOptions struct {
output.OutputOptions
cliflags.ListOptions
ColumnGroups []string
Labels string
FilterByStatus string
ColumnGroups []string
Labels string
FilterByApproval string
FilterByStatus string
}

// NewListOptions returns initialized Options
Expand All @@ -42,22 +44,24 @@ func NewListCmd() *cobra.Command {
Use: "list",
Short: "List info of network nodes. ",
Args: cobra.NoArgs,
Run: o.run,
RunE: o.run,
}
nodeCmd.Flags().StringSliceVar(&o.ColumnGroups, "show", o.ColumnGroups,
fmt.Sprintf("What column groups to show. Zero or more of: %q", maps.Keys(toggleColumns)))
nodeCmd.Flags().StringVar(&o.Labels, "labels", o.Labels,
"Filter nodes by labels. See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more information.")
nodeCmd.Flags().AddFlagSet(cliflags.ListFlags(&o.ListOptions))
nodeCmd.Flags().AddFlagSet(cliflags.OutputFormatFlags(&o.OutputOptions))
nodeCmd.Flags().StringVar(&o.FilterByApproval, "filter-approval", o.FilterByApproval,
fmt.Sprintf("Filter nodes by approval. One of: %q", filterApprovalValues))
nodeCmd.Flags().StringVar(&o.FilterByStatus, "filter-status", o.FilterByStatus,
fmt.Sprintf("Filter nodes by status. One of: %q", filterStatusValues))

return nodeCmd
}

// Run executes node command
func (o *ListOptions) run(cmd *cobra.Command, _ []string) {
func (o *ListOptions) run(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()

var err error
Expand All @@ -69,15 +73,22 @@ func (o *ListOptions) run(cmd *cobra.Command, _ []string) {
}
}

if o.FilterByApproval != "" {
if !slices.Contains(filterApprovalValues, o.FilterByApproval) {
return fmt.Errorf("cannot use '%s' as filter approval value, should be one of: %q", o.FilterByApproval, filterApprovalValues)
}
}

if o.FilterByStatus != "" {
if !slices.Contains(filterStatusValues, o.FilterByStatus) {
util.Fatal(cmd, fmt.Errorf("cannot use '%s' as filter status value, should be one of: %q", o.FilterByStatus, filterStatusValues), 1)
return fmt.Errorf("cannot use '%s' as filter status value, should be one of: %q", o.FilterByStatus, filterStatusValues)
}
}

response, err := util.GetAPIClientV2(cmd).Nodes().List(ctx, &apimodels.ListNodesRequest{
Labels: labelRequirements,
FilterByStatus: o.FilterByStatus,
Labels: labelRequirements,
FilterByApproval: o.FilterByApproval,
FilterByStatus: o.FilterByStatus,
BaseListRequest: apimodels.BaseListRequest{
Limit: o.Limit,
NextToken: o.NextToken,
Expand All @@ -97,4 +108,6 @@ func (o *ListOptions) run(cmd *cobra.Command, _ []string) {
if err = output.Output(cmd, columns, o.OutputOptions, response.Nodes); err != nil {
util.Fatal(cmd, fmt.Errorf("failed to output: %w", err), 1)
}

return nil
}
9 changes: 7 additions & 2 deletions pkg/publicapi/apimodels/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ type GetNodeResponse struct {

type ListNodesRequest struct {
BaseListRequest
Labels []labels.Requirement `query:"-"` // don't auto bind as it requires special handling
FilterByStatus string `query:"filter-status"`
Labels []labels.Requirement `query:"-"` // don't auto bind as it requires special handling
FilterByApproval string `query:"filter-approval"`
FilterByStatus string `query:"filter-status"`
}

// ToHTTPRequest is used to convert the request to an HTTP request
Expand All @@ -29,6 +30,10 @@ func (o *ListNodesRequest) ToHTTPRequest() *HTTPRequest {
r.Params.Add("labels", v.String())
}

if o.FilterByApproval != "" {
r.Params.Add("filter-approval", o.FilterByApproval)
}

if o.FilterByStatus != "" {
r.Params.Add("filter-status", o.FilterByStatus)
}
Expand Down
73 changes: 44 additions & 29 deletions pkg/publicapi/endpoint/orchestrator/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ func (e *Endpoint) getNode(c echo.Context) error {
})
}

//nolint:gocyclo // cyclomatic complexity is high here becomes of the complex sorting logic
func (e *Endpoint) listNodes(c echo.Context) error {
ctx := c.Request().Context()
var args apimodels.ListNodesRequest
Expand All @@ -53,35 +52,11 @@ func (e *Endpoint) listNodes(c echo.Context) error {
}

// parse order_by
var sortFnc func(a, b *models.NodeInfo) int
switch args.OrderBy {
case "id", "":
sortFnc = func(a, b *models.NodeInfo) int { return util.Compare[string]{}.Cmp(a.ID(), b.ID()) }
case "type":
sortFnc = func(a, b *models.NodeInfo) int { return util.Compare[models.NodeType]{}.Cmp(a.NodeType, b.NodeType) }
case "available_cpu":
sortFnc = func(a, b *models.NodeInfo) int {
return util.Compare[float64]{}.CmpRev(capacity(a).CPU, capacity(b).CPU)
}
case "available_memory":
sortFnc = func(a, b *models.NodeInfo) int {
return util.Compare[uint64]{}.CmpRev(capacity(a).Memory, capacity(b).Memory)
}
case "available_disk":
sortFnc = func(a, b *models.NodeInfo) int {
return util.Compare[uint64]{}.CmpRev(capacity(a).Disk, capacity(b).Disk)
}
case "available_gpu":
sortFnc = func(a, b *models.NodeInfo) int {
return util.Compare[uint64]{}.CmpRev(capacity(a).GPU, capacity(b).GPU)
}
case "approval", "status":
sortFnc = func(a, b *models.NodeInfo) int {
return util.Compare[string]{}.Cmp(a.Approval.String(), b.Approval.String())
}
default:
sortFnc := e.getSortFunction(args.OrderBy, capacity)
if sortFnc == nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid order_by")
}

if args.Reverse {
baseSortFnc := sortFnc
sortFnc = func(a, b *models.NodeInfo) int {
Expand All @@ -102,12 +77,17 @@ func (e *Endpoint) listNodes(c echo.Context) error {
return err
}

args.FilterByApproval = strings.ToUpper(args.FilterByApproval)
args.FilterByStatus = strings.ToUpper(args.FilterByStatus)

// filter nodes, first by status, then by label selectors
res := make([]*models.NodeInfo, 0)
for i, node := range allNodes {
if args.FilterByStatus != "" && args.FilterByStatus != node.Approval.String() {
if args.FilterByApproval != "" && args.FilterByApproval != node.Approval.String() {
continue
}

if args.FilterByStatus != "" && args.FilterByStatus != node.State.String() {
continue
}

Expand All @@ -130,6 +110,41 @@ func (e *Endpoint) listNodes(c echo.Context) error {
})
}

type resourceFunc func(node *models.NodeInfo) *models.Resources
type sortFunc func(a, b *models.NodeInfo) int

func (e *Endpoint) getSortFunction(orderBy string, capacity resourceFunc) sortFunc {
switch orderBy {
case "id", "":
return func(a, b *models.NodeInfo) int { return util.Compare[string]{}.Cmp(a.ID(), b.ID()) }
case "type":
return func(a, b *models.NodeInfo) int { return util.Compare[models.NodeType]{}.Cmp(a.NodeType, b.NodeType) }
case "available_cpu":
return func(a, b *models.NodeInfo) int {
return util.Compare[float64]{}.CmpRev(capacity(a).CPU, capacity(b).CPU)
}
case "available_memory":
return func(a, b *models.NodeInfo) int {
return util.Compare[uint64]{}.CmpRev(capacity(a).Memory, capacity(b).Memory)
}
case "available_disk":
return func(a, b *models.NodeInfo) int {
return util.Compare[uint64]{}.CmpRev(capacity(a).Disk, capacity(b).Disk)
}
case "available_gpu":
return func(a, b *models.NodeInfo) int {
return util.Compare[uint64]{}.CmpRev(capacity(a).GPU, capacity(b).GPU)
}
case "approval", "status":
return func(a, b *models.NodeInfo) int {
return util.Compare[string]{}.Cmp(a.Approval.String(), b.Approval.String())
}
default:
}

return nil
}

func (e *Endpoint) updateNode(c echo.Context) error {
ctx := c.Request().Context()

Expand Down

0 comments on commit 6bf5028

Please sign in to comment.