diff --git a/cmd/cli/node/list.go b/cmd/cli/node/list.go index 4b45f35e21..a891930dcc 100644 --- a/cmd/cli/node/list.go +++ b/cmd/cli/node/list.go @@ -2,6 +2,7 @@ package node import ( "fmt" + "slices" "github.com/spf13/cobra" "golang.org/x/exp/maps" @@ -14,14 +15,16 @@ import ( ) var defaultColumnGroups = []string{"labels", "capacity"} -var orderByFields = []string{"id", "type", "available_cpu", "available_memory", "available_disk", "available_gpu"} +var orderByFields = []string{"id", "type", "available_cpu", "available_memory", "available_disk", "available_gpu", "status"} +var filterStatusValues = []string{"approved", "pending", "rejected"} // ListOptions is a struct to support node command type ListOptions struct { output.OutputOptions cliflags.ListOptions - ColumnGroups []string - Labels string + ColumnGroups []string + Labels string + FilterByStatus string } // NewListOptions returns initialized Options @@ -43,11 +46,13 @@ func NewListCmd() *cobra.Command { } 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.FilterByStatus, "filter-status", o.FilterByStatus, + fmt.Sprintf("Filter nodes by status. One of: %q", filterStatusValues)) + return nodeCmd } @@ -63,8 +68,16 @@ func (o *ListOptions) run(cmd *cobra.Command, _ []string) { util.Fatal(cmd, fmt.Errorf("could not parse labels: %w", err), 1) } } + + 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) + } + } + response, err := util.GetAPIClientV2(cmd).Nodes().List(ctx, &apimodels.ListNodesRequest{ - Labels: labelRequirements, + Labels: labelRequirements, + FilterByStatus: o.FilterByStatus, BaseListRequest: apimodels.BaseListRequest{ Limit: o.Limit, NextToken: o.NextToken, diff --git a/pkg/publicapi/apimodels/node.go b/pkg/publicapi/apimodels/node.go index ab4540af4b..27f5cb944d 100644 --- a/pkg/publicapi/apimodels/node.go +++ b/pkg/publicapi/apimodels/node.go @@ -17,7 +17,8 @@ type GetNodeResponse struct { type ListNodesRequest struct { BaseListRequest - Labels []labels.Requirement `query:"-"` // don't auto bind as it requires special handling + Labels []labels.Requirement `query:"-"` // don't auto bind as it requires special handling + FilterByStatus string `query:"filter-status"` } // ToHTTPRequest is used to convert the request to an HTTP request @@ -27,6 +28,11 @@ func (o *ListNodesRequest) ToHTTPRequest() *HTTPRequest { for _, v := range o.Labels { r.Params.Add("labels", v.String()) } + + if o.FilterByStatus != "" { + r.Params.Add("filter-status", o.FilterByStatus) + } + return r } diff --git a/pkg/publicapi/endpoint/orchestrator/node.go b/pkg/publicapi/endpoint/orchestrator/node.go index 236c5b388c..bae1810d4d 100644 --- a/pkg/publicapi/endpoint/orchestrator/node.go +++ b/pkg/publicapi/endpoint/orchestrator/node.go @@ -3,6 +3,7 @@ package orchestrator import ( "context" "net/http" + "strings" "github.com/labstack/echo/v4" "golang.org/x/exp/slices" @@ -74,6 +75,10 @@ func (e *Endpoint) listNodes(c echo.Context) error { 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: return echo.NewHTTPError(http.StatusBadRequest, "invalid order_by") } @@ -97,9 +102,15 @@ func (e *Endpoint) listNodes(c echo.Context) error { return err } - // filter nodes + 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() { + continue + } + if selector.Matches(labels.Set(node.Labels)) { res = append(res, &allNodes[i]) }