Skip to content

Commit

Permalink
Improve node listing in tsh and tctl (#39567)
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 committed Mar 19, 2024
1 parent de88345 commit 98846ac
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
Expand Up @@ -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),
Expand All @@ -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.
Expand Down
52 changes: 43 additions & 9 deletions tool/tctl/common/resource_command.go
Expand Up @@ -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 {
Expand Down

0 comments on commit 98846ac

Please sign in to comment.