Skip to content

Commit

Permalink
Improve node listing in tsh and tctl (#39568)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rosstimothy authored Mar 19, 2024
1 parent 0ab5444 commit cf8d5c9
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 13 deletions.
39 changes: 35 additions & 4 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2309,13 +2309,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),
Expand All @@ -2329,8 +2338,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.
Expand Down
52 changes: 43 additions & 9 deletions tool/tctl/common/resource_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -1692,19 +1692,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 {
Expand Down

0 comments on commit cf8d5c9

Please sign in to comment.