Makes the Raft configuration API easier to consume.

This commit is contained in:
James Phillips 2016-08-30 11:30:56 -07:00
parent 5df4b6bef2
commit 6be1e07fec
No known key found for this signature in database
GPG Key ID: 77183E682AC5FC11
7 changed files with 116 additions and 72 deletions

View File

@ -14,23 +14,37 @@ func (c *Client) Operator() *Operator {
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.
// This has the low-level Raft structure, as well as some supplemental
// information from Consul.
type RaftConfiguration struct {
// Configuration is the low-level Raft configuration structure.
Configuration raft.Configuration
// Servers has the list of servers in the Raft configuration.
Servers []*RaftServer
// NodeMap maps IDs in the Raft configuration to node names known by
// Consul. It's possible that not all configuration entries may have
// 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
// Index has the Raft index of this configuration.
Index uint64
}
// RaftGetConfiguration is used to query the current Raft peer set.

View File

@ -15,9 +15,9 @@ func TestOperator_RaftGetConfiguration(t *testing.T) {
if err != nil {
t.Fatalf("err: %v", err)
}
if len(out.Configuration.Servers) != 1 ||
len(out.NodeMap) != 1 ||
len(out.Leader) == 0 {
if len(out.Servers) != 1 ||
!out.Servers[0].Leader ||
!out.Servers[0].Voter {
t.Fatalf("bad: %v", out)
}
}

View File

@ -30,9 +30,9 @@ func TestOperator_OperatorRaftConfiguration(t *testing.T) {
if !ok {
t.Fatalf("unexpected: %T", obj)
}
if len(out.Configuration.Servers) != 1 ||
len(out.NodeMap) != 1 ||
len(out.Leader) == 0 {
if len(out.Servers) != 1 ||
!out.Servers[0].Leader ||
!out.Servers[0].Voter {
t.Fatalf("bad: %v", out)
}
})

View File

@ -140,18 +140,13 @@ func (c *OperatorCommand) raft(args []string) error {
// Format it as a nice table.
result := []string{"Node|ID|Address|State|Voter"}
for _, s := range reply.Configuration.Servers {
node := "(unknown)"
if mappedNode, ok := reply.NodeMap[s.ID]; ok {
node = mappedNode
}
for _, s := range reply.Servers {
state := "follower"
if s.ID == reply.Leader {
if s.Leader {
state = "leader"
}
voter := s.Suffrage == raft.Voter
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))
} else if removePeer {

View File

@ -7,6 +7,7 @@ import (
"github.com/hashicorp/consul/consul/agent"
"github.com/hashicorp/consul/consul/structs"
"github.com/hashicorp/raft"
"github.com/hashicorp/serf/serf"
)
// 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 {
return err
}
reply.Configuration = future.Configuration()
leader := op.srv.raft.Leader()
// Index the configuration so we can easily look up IDs by address.
idMap := make(map[raft.ServerAddress]raft.ServerID)
for _, s := range reply.Configuration.Servers {
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 {
// Index the Consul information about the servers.
serverMap := make(map[raft.ServerAddress]*serf.Member)
for _, member := range op.srv.serfLAN.Members() {
valid, parts := agent.IsConsulServer(member)
if !valid {
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()
if id, ok := idMap[raft.ServerAddress(addr)]; ok {
reply.NodeMap[id] = member.Name
if leader == raft.ServerAddress(addr) {
reply.Leader = id
}
serverMap[raft.ServerAddress(addr)] = &member
}
// 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
}
@ -118,6 +122,6 @@ REMOVE:
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
}

View File

@ -34,13 +34,21 @@ func TestOperator_RaftGetConfiguration(t *testing.T) {
if err := future.Error(); err != nil {
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{
Configuration: future.Configuration(),
NodeMap: map[raft.ServerID]string{
raft.ServerID(s1.config.RPCAddr.String()): s1.config.NodeName,
Servers: []*structs.RaftServer{
&structs.RaftServer{
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) {
t.Fatalf("bad: %v", reply)
@ -102,13 +110,21 @@ func TestOperator_RaftGetConfiguration_ACLDeny(t *testing.T) {
if err := future.Error(); err != nil {
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{
Configuration: future.Configuration(),
NodeMap: map[raft.ServerID]string{
raft.ServerID(s1.config.RPCAddr.String()): s1.config.NodeName,
Servers: []*structs.RaftServer{
&structs.RaftServer{
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) {
t.Fatalf("bad: %v", reply)

View File

@ -4,23 +4,38 @@ import (
"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
// configuration. This has the low-level Raft structure, as well as some
// supplemental information from Consul.
// configuration.
type RaftConfigurationResponse struct {
// Configuration is the low-level Raft configuration structure.
Configuration raft.Configuration
// Servers has the list of servers in the Raft configuration.
Servers []*RaftServer
// NodeMap maps IDs in the Raft configuration to node names known by
// Consul. It's possible that not all configuration entries may have
// 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
// Index has the Raft index of this configuration.
Index uint64
}
// RaftPeerByAddressRequest is used by the Operator endpoint to apply a Raft