diff --git a/etcdctl/ctlv3/command/ep_command.go b/etcdctl/ctlv3/command/ep_command.go index d540c4558..99ae1a756 100644 --- a/etcdctl/ctlv3/command/ep_command.go +++ b/etcdctl/ctlv3/command/ep_command.go @@ -60,7 +60,7 @@ func newEpStatusCommand() *cobra.Command { Use: "status", Short: "Prints out the status of endpoints specified in `--endpoints` flag", Long: `When --write-out is set to simple, this command prints out comma-separated status lists for each endpoint. -The items in the lists are endpoint, ID, version, db size, is leader, raft term, raft index. +The items in the lists are endpoint, ID, version, db size, is leader, is learner, raft term, raft index, raft applied index, errors. `, Run: epStatusCommandFunc, } diff --git a/etcdctl/ctlv3/command/printer.go b/etcdctl/ctlv3/command/printer.go index 55586a413..942668d9e 100644 --- a/etcdctl/ctlv3/command/printer.go +++ b/etcdctl/ctlv3/command/printer.go @@ -194,7 +194,8 @@ func makeEndpointHealthTable(healthList []epHealth) (hdr []string, rows [][]stri } func makeEndpointStatusTable(statusList []epStatus) (hdr []string, rows [][]string) { - hdr = []string{"endpoint", "ID", "version", "db size", "is leader", "raft term", "raft index", "raft applied index", "errors"} + hdr = []string{"endpoint", "ID", "version", "db size", "is leader", "is learner", "raft term", + "raft index", "raft applied index", "errors"} for _, status := range statusList { rows = append(rows, []string{ status.Ep, @@ -202,6 +203,7 @@ func makeEndpointStatusTable(statusList []epStatus) (hdr []string, rows [][]stri status.Resp.Version, humanize.Bytes(uint64(status.Resp.DbSize)), fmt.Sprint(status.Resp.Leader == status.Resp.Header.MemberId), + fmt.Sprint(status.Resp.IsLearner), fmt.Sprint(status.Resp.RaftTerm), fmt.Sprint(status.Resp.RaftIndex), fmt.Sprint(status.Resp.RaftAppliedIndex), diff --git a/etcdctl/ctlv3/command/printer_fields.go b/etcdctl/ctlv3/command/printer_fields.go index 220eb491f..38f5c7d93 100644 --- a/etcdctl/ctlv3/command/printer_fields.go +++ b/etcdctl/ctlv3/command/printer_fields.go @@ -158,6 +158,7 @@ func (p *fieldsPrinter) EndpointStatus(eps []epStatus) { fmt.Printf("\"Version\" : %q\n", ep.Resp.Version) fmt.Println(`"DBSize" :`, ep.Resp.DbSize) fmt.Println(`"Leader" :`, ep.Resp.Leader) + fmt.Println(`"IsLearner" :`, ep.Resp.IsLearner) fmt.Println(`"RaftIndex" :`, ep.Resp.RaftIndex) fmt.Println(`"RaftTerm" :`, ep.Resp.RaftTerm) fmt.Println(`"RaftAppliedIndex" :`, ep.Resp.RaftAppliedIndex) diff --git a/etcdserver/api/membership/cluster.go b/etcdserver/api/membership/cluster.go index cc8e171bd..bfe250cb5 100644 --- a/etcdserver/api/membership/cluster.go +++ b/etcdserver/api/membership/cluster.go @@ -693,3 +693,22 @@ func mustDetectDowngrade(lg *zap.Logger, cv *semver.Version) { } } } + +// IsLearner returns if the local member is raft learner +func (c *RaftCluster) IsLearner() bool { + c.Lock() + defer c.Unlock() + localMember, ok := c.members[c.localID] + if !ok { + if c.lg != nil { + c.lg.Panic( + "failed to find local ID in cluster members", + zap.String("cluster-id", c.cid.String()), + zap.String("local-member-id", c.localID.String()), + ) + } else { + plog.Panicf("failed to find local ID %s in cluster %s", c.localID.String(), c.cid.String()) + } + } + return localMember.IsLearner +} diff --git a/etcdserver/api/v3rpc/maintenance.go b/etcdserver/api/v3rpc/maintenance.go index be777629c..bc5453f70 100644 --- a/etcdserver/api/v3rpc/maintenance.go +++ b/etcdserver/api/v3rpc/maintenance.go @@ -55,6 +55,10 @@ type AuthGetter interface { AuthStore() auth.AuthStore } +type ClusterStatusGetter interface { + IsLearner() bool +} + type maintenanceServer struct { lg *zap.Logger rg etcdserver.RaftStatusGetter @@ -63,10 +67,11 @@ type maintenanceServer struct { a Alarmer lt LeaderTransferrer hdr header + cs ClusterStatusGetter } func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer { - srv := &maintenanceServer{lg: s.Cfg.Logger, rg: s, kg: s, bg: s, a: s, lt: s, hdr: newHeader(s)} + srv := &maintenanceServer{lg: s.Cfg.Logger, rg: s, kg: s, bg: s, a: s, lt: s, hdr: newHeader(s), cs: s} return &authMaintenanceServer{srv, s} } @@ -179,6 +184,7 @@ func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) ( RaftTerm: ms.rg.Term(), DbSize: ms.bg.Backend().Size(), DbSizeInUse: ms.bg.Backend().SizeInUse(), + IsLearner: ms.cs.IsLearner(), } if resp.Leader == raft.None { resp.Errors = append(resp.Errors, etcdserver.ErrNoLeader.Error()) diff --git a/etcdserver/server.go b/etcdserver/server.go index ed0941fd2..423a6e96c 100644 --- a/etcdserver/server.go +++ b/etcdserver/server.go @@ -2440,3 +2440,8 @@ func (s *EtcdServer) Alarms() []*pb.AlarmMember { func (s *EtcdServer) Logger() *zap.Logger { return s.lg } + +// IsLearner returns if the local member is raft learner +func (s *EtcdServer) IsLearner() bool { + return s.cluster.IsLearner() +}