open-nomad/nomad/client_stats_endpoint.go
Tim Gross 6677a103c2
metrics: measure rate of RPC requests that serve API (#15876)
This changeset configures the RPC rate metrics that were added in #15515 to all
the RPCs that support authenticated HTTP API requests. These endpoints already
configured with pre-forwarding authentication in #15870, and a handful of others
were done already as part of the proof-of-concept work. So this changeset is
entirely copy-and-pasting one method call into a whole mess of handlers.

Upcoming PRs will wire up pre-forwarding auth and rate metrics for the remaining
set of RPCs that have no API consumers or aren't authenticated, in smaller
chunks that can be more thoughtfully reviewed.
2023-01-25 16:37:24 -05:00

87 lines
2.2 KiB
Go

package nomad
import (
"errors"
"time"
metrics "github.com/armon/go-metrics"
log "github.com/hashicorp/go-hclog"
nstructs "github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/client/structs"
)
// ClientStats is used to forward RPC requests to the targed Nomad client's
// ClientStats endpoint.
type ClientStats struct {
srv *Server
logger log.Logger
}
func NewClientStatsEndpoint(srv *Server) *ClientStats {
return &ClientStats{srv: srv, logger: srv.logger.Named("client_stats")}
}
func (s *ClientStats) Stats(args *nstructs.NodeSpecificRequest, reply *structs.ClientStatsResponse) error {
// We only allow stale reads since the only potentially stale information is
// the Node registration and the cost is fairly high for adding another hope
// in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := s.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := s.srv.forward("ClientStats.Stats", args, args, reply); done {
return err
}
s.srv.MeasureRPCRate("client_stats", nstructs.RateMetricRead, args)
if authErr != nil {
return nstructs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client_stats", "stats"}, time.Now())
// Check node read permissions
if aclObj, err := s.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNodeRead() {
return nstructs.ErrPermissionDenied
}
// Verify the arguments.
if args.NodeID == "" {
return errors.New("missing NodeID")
}
// Check if the node even exists and is compatible with NodeRpc
snap, err := s.srv.State().Snapshot()
if err != nil {
return err
}
// Make sure Node is new enough to support RPC
_, err = getNodeForRpc(snap, args.NodeID)
if err != nil {
return err
}
// Get the connection to the client
state, ok := s.srv.getNodeConn(args.NodeID)
if !ok {
// Determine the Server that has a connection to the node.
srv, err := s.srv.serverWithNodeConn(args.NodeID, s.srv.Region())
if err != nil {
return err
}
if srv == nil {
return nstructs.ErrNoNodeConn
}
return s.srv.forwardServer(srv, "ClientStats.Stats", args, reply)
}
// Make the RPC
return NodeRpc(state.Session, "ClientStats.Stats", args, reply)
}