Makes the Raft configuration API easier to consume.
This commit is contained in:
parent
5df4b6bef2
commit
6be1e07fec
|
@ -14,23 +14,37 @@ func (c *Client) Operator() *Operator {
|
||||||
return &Operator{c}
|
return &Operator{c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RaftServer has information about a server in the Raft configuration.
|
||||||
|
type RaftServer struct {
|
||||||
|
// ID is the unique ID for the server. These are currently the same
|
||||||
|
// as the address, but they will be changed to a real GUID in a future
|
||||||
|
// release of Consul.
|
||||||
|
ID raft.ServerID
|
||||||
|
|
||||||
|
// Node is the node name of the server, as known by Consul, or this
|
||||||
|
// will be set to "(unknown)" otherwise.
|
||||||
|
Node string
|
||||||
|
|
||||||
|
// Address is the IP:port of the server, used for Raft communications.
|
||||||
|
Address raft.ServerAddress
|
||||||
|
|
||||||
|
// Leader is true if this server is the current cluster leader.
|
||||||
|
Leader bool
|
||||||
|
|
||||||
|
// Voter is true if this server has a vote in the cluster. This might
|
||||||
|
// be false if the server is staging and still coming online, or if
|
||||||
|
// it's a non-voting server, which will be added in a future release of
|
||||||
|
// Consul.
|
||||||
|
Voter bool
|
||||||
|
}
|
||||||
|
|
||||||
// RaftConfigration is returned when querying for the current Raft configuration.
|
// RaftConfigration is returned when querying for the current Raft configuration.
|
||||||
// This has the low-level Raft structure, as well as some supplemental
|
|
||||||
// information from Consul.
|
|
||||||
type RaftConfiguration struct {
|
type RaftConfiguration struct {
|
||||||
// Configuration is the low-level Raft configuration structure.
|
// Servers has the list of servers in the Raft configuration.
|
||||||
Configuration raft.Configuration
|
Servers []*RaftServer
|
||||||
|
|
||||||
// NodeMap maps IDs in the Raft configuration to node names known by
|
// Index has the Raft index of this configuration.
|
||||||
// Consul. It's possible that not all configuration entries may have
|
Index uint64
|
||||||
// an entry here if the node isn't known to Consul. Given how this is
|
|
||||||
// generated, this may also contain entries that aren't present in the
|
|
||||||
// Raft configuration.
|
|
||||||
NodeMap map[raft.ServerID]string
|
|
||||||
|
|
||||||
// Leader is the ID of the current Raft leader. This may be blank if
|
|
||||||
// there isn't one.
|
|
||||||
Leader raft.ServerID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RaftGetConfiguration is used to query the current Raft peer set.
|
// RaftGetConfiguration is used to query the current Raft peer set.
|
||||||
|
|
|
@ -15,9 +15,9 @@ func TestOperator_RaftGetConfiguration(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
if len(out.Configuration.Servers) != 1 ||
|
if len(out.Servers) != 1 ||
|
||||||
len(out.NodeMap) != 1 ||
|
!out.Servers[0].Leader ||
|
||||||
len(out.Leader) == 0 {
|
!out.Servers[0].Voter {
|
||||||
t.Fatalf("bad: %v", out)
|
t.Fatalf("bad: %v", out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,9 @@ func TestOperator_OperatorRaftConfiguration(t *testing.T) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("unexpected: %T", obj)
|
t.Fatalf("unexpected: %T", obj)
|
||||||
}
|
}
|
||||||
if len(out.Configuration.Servers) != 1 ||
|
if len(out.Servers) != 1 ||
|
||||||
len(out.NodeMap) != 1 ||
|
!out.Servers[0].Leader ||
|
||||||
len(out.Leader) == 0 {
|
!out.Servers[0].Voter {
|
||||||
t.Fatalf("bad: %v", out)
|
t.Fatalf("bad: %v", out)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -140,18 +140,13 @@ func (c *OperatorCommand) raft(args []string) error {
|
||||||
|
|
||||||
// Format it as a nice table.
|
// Format it as a nice table.
|
||||||
result := []string{"Node|ID|Address|State|Voter"}
|
result := []string{"Node|ID|Address|State|Voter"}
|
||||||
for _, s := range reply.Configuration.Servers {
|
for _, s := range reply.Servers {
|
||||||
node := "(unknown)"
|
|
||||||
if mappedNode, ok := reply.NodeMap[s.ID]; ok {
|
|
||||||
node = mappedNode
|
|
||||||
}
|
|
||||||
state := "follower"
|
state := "follower"
|
||||||
if s.ID == reply.Leader {
|
if s.Leader {
|
||||||
state = "leader"
|
state = "leader"
|
||||||
}
|
}
|
||||||
voter := s.Suffrage == raft.Voter
|
|
||||||
result = append(result, fmt.Sprintf("%s|%s|%s|%s|%v",
|
result = append(result, fmt.Sprintf("%s|%s|%s|%s|%v",
|
||||||
node, s.ID, s.Address, state, voter))
|
s.Node, s.ID, s.Address, state, s.Voter))
|
||||||
}
|
}
|
||||||
c.Ui.Output(columnize.SimpleFormat(result))
|
c.Ui.Output(columnize.SimpleFormat(result))
|
||||||
} else if removePeer {
|
} else if removePeer {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/hashicorp/consul/consul/agent"
|
"github.com/hashicorp/consul/consul/agent"
|
||||||
"github.com/hashicorp/consul/consul/structs"
|
"github.com/hashicorp/consul/consul/structs"
|
||||||
"github.com/hashicorp/raft"
|
"github.com/hashicorp/raft"
|
||||||
|
"github.com/hashicorp/serf/serf"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Operator endpoint is used to perform low-level operator tasks for Consul.
|
// Operator endpoint is used to perform low-level operator tasks for Consul.
|
||||||
|
@ -35,33 +36,36 @@ func (op *Operator) RaftGetConfiguration(args *structs.DCSpecificRequest, reply
|
||||||
if err := future.Error(); err != nil {
|
if err := future.Error(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reply.Configuration = future.Configuration()
|
|
||||||
leader := op.srv.raft.Leader()
|
|
||||||
|
|
||||||
// Index the configuration so we can easily look up IDs by address.
|
// Index the Consul information about the servers.
|
||||||
idMap := make(map[raft.ServerAddress]raft.ServerID)
|
serverMap := make(map[raft.ServerAddress]*serf.Member)
|
||||||
for _, s := range reply.Configuration.Servers {
|
for _, member := range op.srv.serfLAN.Members() {
|
||||||
idMap[s.Address] = s.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill out the node map and leader.
|
|
||||||
reply.NodeMap = make(map[raft.ServerID]string)
|
|
||||||
members := op.srv.serfLAN.Members()
|
|
||||||
for _, member := range members {
|
|
||||||
valid, parts := agent.IsConsulServer(member)
|
valid, parts := agent.IsConsulServer(member)
|
||||||
if !valid {
|
if !valid {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (slackpad) We need to add a Raft API to get the leader by
|
|
||||||
// ID so we don't have to do this mapping.
|
|
||||||
addr := (&net.TCPAddr{IP: member.Addr, Port: parts.Port}).String()
|
addr := (&net.TCPAddr{IP: member.Addr, Port: parts.Port}).String()
|
||||||
if id, ok := idMap[raft.ServerAddress(addr)]; ok {
|
serverMap[raft.ServerAddress(addr)] = &member
|
||||||
reply.NodeMap[id] = member.Name
|
}
|
||||||
if leader == raft.ServerAddress(addr) {
|
|
||||||
reply.Leader = id
|
// Fill out the reply.
|
||||||
}
|
leader := op.srv.raft.Leader()
|
||||||
|
reply.Index = future.Index()
|
||||||
|
for _, server := range future.Configuration().Servers {
|
||||||
|
node := "(unknown)"
|
||||||
|
if member, ok := serverMap[server.Address]; ok {
|
||||||
|
node = member.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry := &structs.RaftServer{
|
||||||
|
ID: server.ID,
|
||||||
|
Node: node,
|
||||||
|
Address: server.Address,
|
||||||
|
Leader: server.Address == leader,
|
||||||
|
Voter: server.Suffrage == raft.Voter,
|
||||||
|
}
|
||||||
|
reply.Servers = append(reply.Servers, entry)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -118,6 +122,6 @@ REMOVE:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
op.srv.logger.Printf("[WARN] consul.operator: Removed Raft peer %q by", args.Address)
|
op.srv.logger.Printf("[WARN] consul.operator: Removed Raft peer %q", args.Address)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,21 @@ func TestOperator_RaftGetConfiguration(t *testing.T) {
|
||||||
if err := future.Error(); err != nil {
|
if err := future.Error(); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
if len(future.Configuration().Servers) != 1 {
|
||||||
|
t.Fatalf("bad: %v", future.Configuration().Servers)
|
||||||
|
}
|
||||||
|
me := future.Configuration().Servers[0]
|
||||||
expected := structs.RaftConfigurationResponse{
|
expected := structs.RaftConfigurationResponse{
|
||||||
Configuration: future.Configuration(),
|
Servers: []*structs.RaftServer{
|
||||||
NodeMap: map[raft.ServerID]string{
|
&structs.RaftServer{
|
||||||
raft.ServerID(s1.config.RPCAddr.String()): s1.config.NodeName,
|
ID: me.ID,
|
||||||
|
Node: s1.config.NodeName,
|
||||||
|
Address: me.Address,
|
||||||
|
Leader: true,
|
||||||
|
Voter: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Leader: raft.ServerID(s1.config.RPCAddr.String()),
|
Index: future.Index(),
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(reply, expected) {
|
if !reflect.DeepEqual(reply, expected) {
|
||||||
t.Fatalf("bad: %v", reply)
|
t.Fatalf("bad: %v", reply)
|
||||||
|
@ -102,13 +110,21 @@ func TestOperator_RaftGetConfiguration_ACLDeny(t *testing.T) {
|
||||||
if err := future.Error(); err != nil {
|
if err := future.Error(); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
if len(future.Configuration().Servers) != 1 {
|
||||||
|
t.Fatalf("bad: %v", future.Configuration().Servers)
|
||||||
|
}
|
||||||
|
me := future.Configuration().Servers[0]
|
||||||
expected := structs.RaftConfigurationResponse{
|
expected := structs.RaftConfigurationResponse{
|
||||||
Configuration: future.Configuration(),
|
Servers: []*structs.RaftServer{
|
||||||
NodeMap: map[raft.ServerID]string{
|
&structs.RaftServer{
|
||||||
raft.ServerID(s1.config.RPCAddr.String()): s1.config.NodeName,
|
ID: me.ID,
|
||||||
|
Node: s1.config.NodeName,
|
||||||
|
Address: me.Address,
|
||||||
|
Leader: true,
|
||||||
|
Voter: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Leader: raft.ServerID(s1.config.RPCAddr.String()),
|
Index: future.Index(),
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(reply, expected) {
|
if !reflect.DeepEqual(reply, expected) {
|
||||||
t.Fatalf("bad: %v", reply)
|
t.Fatalf("bad: %v", reply)
|
||||||
|
|
|
@ -4,23 +4,38 @@ import (
|
||||||
"github.com/hashicorp/raft"
|
"github.com/hashicorp/raft"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RaftServer has information about a server in the Raft configuration.
|
||||||
|
type RaftServer struct {
|
||||||
|
// ID is the unique ID for the server. These are currently the same
|
||||||
|
// as the address, but they will be changed to a real GUID in a future
|
||||||
|
// release of Consul.
|
||||||
|
ID raft.ServerID
|
||||||
|
|
||||||
|
// Node is the node name of the server, as known by Consul, or this
|
||||||
|
// will be set to "(unknown)" otherwise.
|
||||||
|
Node string
|
||||||
|
|
||||||
|
// Address is the IP:port of the server, used for Raft communications.
|
||||||
|
Address raft.ServerAddress
|
||||||
|
|
||||||
|
// Leader is true if this server is the current cluster leader.
|
||||||
|
Leader bool
|
||||||
|
|
||||||
|
// Voter is true if this server has a vote in the cluster. This might
|
||||||
|
// be false if the server is staging and still coming online, or if
|
||||||
|
// it's a non-voting server, which will be added in a future release of
|
||||||
|
// Consul.
|
||||||
|
Voter bool
|
||||||
|
}
|
||||||
|
|
||||||
// RaftConfigrationResponse is returned when querying for the current Raft
|
// RaftConfigrationResponse is returned when querying for the current Raft
|
||||||
// configuration. This has the low-level Raft structure, as well as some
|
// configuration.
|
||||||
// supplemental information from Consul.
|
|
||||||
type RaftConfigurationResponse struct {
|
type RaftConfigurationResponse struct {
|
||||||
// Configuration is the low-level Raft configuration structure.
|
// Servers has the list of servers in the Raft configuration.
|
||||||
Configuration raft.Configuration
|
Servers []*RaftServer
|
||||||
|
|
||||||
// NodeMap maps IDs in the Raft configuration to node names known by
|
// Index has the Raft index of this configuration.
|
||||||
// Consul. It's possible that not all configuration entries may have
|
Index uint64
|
||||||
// an entry here if the node isn't known to Consul. Given how this is
|
|
||||||
// generated, this may also contain entries that aren't present in the
|
|
||||||
// Raft configuration.
|
|
||||||
NodeMap map[raft.ServerID]string
|
|
||||||
|
|
||||||
// Leader is the ID of the current Raft leader. This may be blank if
|
|
||||||
// there isn't one.
|
|
||||||
Leader raft.ServerID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RaftPeerByAddressRequest is used by the Operator endpoint to apply a Raft
|
// RaftPeerByAddressRequest is used by the Operator endpoint to apply a Raft
|
||||||
|
|
Loading…
Reference in New Issue