1b2237d6a8
Co-authored-by: James Rasell <jrasell@users.noreply.github.com>
169 lines
4.8 KiB
Go
169 lines
4.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package nomad
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
// Status endpoint is used to check on server status
|
|
type Status struct {
|
|
srv *Server
|
|
ctx *RPCContext
|
|
logger hclog.Logger
|
|
}
|
|
|
|
func NewStatusEndpoint(srv *Server, ctx *RPCContext) *Status {
|
|
return &Status{srv: srv, ctx: ctx, logger: srv.logger.Named("status")}
|
|
}
|
|
|
|
// Ping is used to just check for connectivity
|
|
func (s *Status) Ping(args structs.GenericRequest, reply *struct{}) error {
|
|
// note: we're intentionally throwing away any auth error here and only
|
|
// authenticate so that we can measure rate metrics
|
|
s.srv.Authenticate(s.ctx, &args)
|
|
s.srv.MeasureRPCRate("status", structs.RateMetricRead, &args)
|
|
return nil
|
|
}
|
|
|
|
// Leader is used to get the address of the leader
|
|
func (s *Status) Leader(args *structs.GenericRequest, reply *string) error {
|
|
// note: we're intentionally throwing away any auth error here and only
|
|
// authenticate so that we can measure rate metrics
|
|
s.srv.Authenticate(s.ctx, args)
|
|
s.srv.MeasureRPCRate("status", structs.RateMetricRead, args)
|
|
|
|
if args.Region == "" {
|
|
args.Region = s.srv.config.Region
|
|
}
|
|
if done, err := s.srv.forward("Status.Leader", args, args, reply); done {
|
|
return err
|
|
}
|
|
|
|
leader, _ := s.srv.raft.LeaderWithID()
|
|
if leader != "" {
|
|
*reply = string(leader)
|
|
} else {
|
|
*reply = ""
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Peers is used to get all the Raft peers
|
|
func (s *Status) Peers(args *structs.GenericRequest, reply *[]string) error {
|
|
// note: we're intentionally throwing away any auth error here and only
|
|
// authenticate so that we can measure rate metrics
|
|
s.srv.Authenticate(s.ctx, args)
|
|
s.srv.MeasureRPCRate("status", structs.RateMetricList, args)
|
|
|
|
if args.Region == "" {
|
|
args.Region = s.srv.config.Region
|
|
}
|
|
if done, err := s.srv.forward("Status.Peers", args, args, reply); done {
|
|
return err
|
|
}
|
|
|
|
future := s.srv.raft.GetConfiguration()
|
|
if err := future.Error(); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, server := range future.Configuration().Servers {
|
|
*reply = append(*reply, string(server.Address))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Members return the list of servers in a cluster that a particular server is
|
|
// aware of
|
|
func (s *Status) Members(args *structs.GenericRequest, reply *structs.ServerMembersResponse) error {
|
|
authErr := s.srv.Authenticate(s.ctx, args)
|
|
s.srv.MeasureRPCRate("status", structs.RateMetricList, args)
|
|
if authErr != nil {
|
|
return structs.ErrPermissionDenied
|
|
}
|
|
// Check node read permissions
|
|
if aclObj, err := s.srv.ResolveACL(args); err != nil {
|
|
return err
|
|
} else if aclObj != nil && !aclObj.AllowNodeRead() {
|
|
return structs.ErrPermissionDenied
|
|
}
|
|
|
|
serfMembers := s.srv.Members()
|
|
members := make([]*structs.ServerMember, len(serfMembers))
|
|
for i, mem := range serfMembers {
|
|
members[i] = &structs.ServerMember{
|
|
Name: mem.Name,
|
|
Addr: mem.Addr,
|
|
Port: mem.Port,
|
|
Tags: mem.Tags,
|
|
Status: mem.Status.String(),
|
|
ProtocolMin: mem.ProtocolMin,
|
|
ProtocolMax: mem.ProtocolMax,
|
|
ProtocolCur: mem.ProtocolCur,
|
|
DelegateMin: mem.DelegateMin,
|
|
DelegateMax: mem.DelegateMax,
|
|
DelegateCur: mem.DelegateCur,
|
|
}
|
|
}
|
|
*reply = structs.ServerMembersResponse{
|
|
ServerName: s.srv.config.NodeName,
|
|
ServerRegion: s.srv.config.Region,
|
|
ServerDC: s.srv.config.Datacenter,
|
|
Members: members,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RaftStats is used by Autopilot to query the raft stats of the local server.
|
|
func (s *Status) RaftStats(args *structs.GenericRequest, reply *structs.RaftStats) error {
|
|
// note: we're intentionally throwing away any auth error here and only
|
|
// authenticate so that we can measure rate metrics
|
|
s.srv.Authenticate(s.ctx, args)
|
|
s.srv.MeasureRPCRate("status", structs.RateMetricRead, args)
|
|
|
|
stats := s.srv.raft.Stats()
|
|
|
|
var err error
|
|
reply.LastContact = stats["last_contact"]
|
|
reply.LastIndex, err = strconv.ParseUint(stats["last_log_index"], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing server's last_log_index value: %s", err)
|
|
}
|
|
reply.LastTerm, err = strconv.ParseUint(stats["last_log_term"], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing server's last_log_term value: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasNodeConn returns whether the server has a connection to the requested
|
|
// Node.
|
|
func (s *Status) HasNodeConn(args *structs.NodeSpecificRequest, reply *structs.NodeConnQueryResponse) error {
|
|
// note: we're intentionally throwing away any auth error here and only
|
|
// authenticate so that we can measure rate metrics
|
|
s.srv.Authenticate(s.ctx, args)
|
|
s.srv.MeasureRPCRate("status", structs.RateMetricRead, args)
|
|
|
|
// Validate the args
|
|
if args.NodeID == "" {
|
|
return errors.New("Must provide the NodeID")
|
|
}
|
|
|
|
state, ok := s.srv.getNodeConn(args.NodeID)
|
|
if ok {
|
|
reply.Connected = true
|
|
reply.Established = state.Established
|
|
}
|
|
|
|
return nil
|
|
}
|