From 98846acfe61ca1ab180e4f07e29fef4280a0c4a6 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:58:35 -0400 Subject: [PATCH] Improve node listing in tsh and tctl (#39567) Converts both tools to use the ListUnifiedResources RPC instead of the ListResources RPC to improve performance. While there may only be marginal gains when listing the entire set of nodes, there are substantial improvements when filtering via predicate, labels, or search. tctl was also changed slightly to make use of server side filtering when tctl get node/foo is run. Prior to this change the entire node set was always retreived and tctl performed the filter client side if the user provided a hostname or UUID. --- lib/client/api.go | 39 ++++++++++++++++++--- tool/tctl/common/resource_command.go | 52 +++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/lib/client/api.go b/lib/client/api.go index 643426c31fc0d..75916ff4f87fd 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -2378,13 +2378,22 @@ func isRemoteDest(name string) bool { // ListNodesWithFilters returns all nodes that match the filters in the current cluster // that the logged in user has access to. func (tc *TeleportClient) ListNodesWithFilters(ctx context.Context) ([]types.Server, error) { - req := tc.ResourceFilter(types.KindNode) + req := proto.ListUnifiedResourcesRequest{ + Kinds: []string{types.KindNode}, + Labels: tc.Labels, + SearchKeywords: tc.SearchKeywords, + PredicateExpression: tc.PredicateExpression, + UseSearchAsRoles: tc.UseSearchAsRoles, + SortBy: types.SortBy{ + Field: types.ResourceKind, + }, + } + ctx, span := tc.Tracer.Start( ctx, "teleportClient/ListNodesWithFilters", oteltrace.WithSpanKind(oteltrace.SpanKindClient), oteltrace.WithAttributes( - attribute.String("resource", req.ResourceType), attribute.Int("limit", int(req.Limit)), attribute.String("predicate", req.PredicateExpression), attribute.StringSlice("keywords", req.SearchKeywords), @@ -2398,8 +2407,30 @@ func (tc *TeleportClient) ListNodesWithFilters(ctx context.Context) ([]types.Ser } defer clt.Close() - servers, err := client.GetAllResources[types.Server](ctx, clt.AuthClient, req) - return servers, trace.Wrap(err) + var servers []types.Server + for { + page, err := client.ListUnifiedResourcePage(ctx, clt.AuthClient, &req) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, r := range page.Resources { + srv, ok := r.(types.Server) + if !ok { + log.Warnf("expected types.Server but received unexpected type %T", r) + continue + } + + servers = append(servers, srv) + } + + req.StartKey = page.NextKey + if req.StartKey == "" { + break + } + } + + return servers, nil } // GetClusterAlerts returns a list of matching alerts from the current cluster. diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index ccfccb059750c..ea20b7cb13149 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -1852,19 +1852,53 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *auth.Clien } return &authorityCollection{cas: []types.CertAuthority{authority}}, nil case types.KindNode: - nodes, err := client.GetNodes(ctx, rc.namespace) - if err != nil { - return nil, trace.Wrap(err) + var search []string + if rc.ref.Name != "" { + search = []string{rc.ref.Name} } - if rc.ref.Name == "" { - return &serverCollection{servers: nodes}, nil + + req := proto.ListUnifiedResourcesRequest{ + Kinds: []string{types.KindNode}, + SearchKeywords: search, + SortBy: types.SortBy{Field: types.ResourceKind}, } - for _, node := range nodes { - if node.GetName() == rc.ref.Name || node.GetHostname() == rc.ref.Name { - return &serverCollection{servers: []types.Server{node}}, nil + + var collection serverCollection + for { + page, err := apiclient.ListUnifiedResourcePage(ctx, client, &req) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, r := range page.Resources { + srv, ok := r.(types.Server) + if !ok { + log.Warnf("expected types.Server but received unexpected type %T", r) + continue + } + + if rc.ref.Name == "" { + collection.servers = append(collection.servers, srv) + continue + } + + if srv.GetName() == rc.ref.Name || srv.GetHostname() == rc.ref.Name { + collection.servers = []types.Server{srv} + return &collection, nil + } + } + + req.StartKey = page.NextKey + if req.StartKey == "" { + break } } - return nil, trace.NotFound("node with ID %q not found", rc.ref.Name) + + if len(collection.servers) == 0 && rc.ref.Name != "" { + return nil, trace.NotFound("node with ID %q not found", rc.ref.Name) + } + + return &collection, nil case types.KindAuthServer: servers, err := client.GetAuthServers() if err != nil {