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} 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.

View File

@ -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)
} }
} }

View File

@ -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)
} }
}) })

View File

@ -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 {

View File

@ -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
} }

View File

@ -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)

View File

@ -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