Skip to content

Commit

Permalink
Makes the Raft configuration API easier to consume.
Browse files Browse the repository at this point in the history
  • Loading branch information
slackpad committed Aug 30, 2016
1 parent 3f16142 commit 3c9188c
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 74 deletions.
44 changes: 29 additions & 15 deletions api/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

// 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
// Servers has the list of servers in the Raft configuration.
Servers []*RaftServer

// Index has the Raft index of this configuration.
Index uint64
}

// RaftGetConfiguration is used to query the current Raft peer set.
Expand Down
6 changes: 3 additions & 3 deletions api/operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
6 changes: 3 additions & 3 deletions command/agent/operator_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
Expand Down
11 changes: 3 additions & 8 deletions command/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
44 changes: 24 additions & 20 deletions consul/operator_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
36 changes: 26 additions & 10 deletions consul/operator_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
45 changes: 30 additions & 15 deletions consul/structs/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

// 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
// Servers has the list of servers in the Raft configuration.
Servers []*RaftServer

// Index has the Raft index of this configuration.
Index uint64
}

// RaftPeerByAddressRequest is used by the Operator endpoint to apply a Raft
Expand Down

5 comments on commit 3c9188c

@jdubs
Copy link

@jdubs jdubs commented on 3c9188c Aug 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slackpad
I'm having some issues which I think are due to this commit.

--> Executing tests
# github.com/hashicorp/consul/api
../../../github.com/hashicorp/consul/api/operator.go:22: undefined: raft.ServerID
../../../github.com/hashicorp/consul/api/operator.go:29: undefined: raft.ServerAddress

@slackpad
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jdubs sorry about that - this commit pulled in a new dependency - do you have that available?

https://github.com/hashicorp/consul/blob/master/api/operator.go#L4

@slackpad
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait - it depends on a branch of Raft which I have vendored w/Consul. I'm going to go ahead and just change this to a string for now because this will likely break a lot of folks who will be trying to get master for Raft.

@slackpad
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jdubs should be good now in 84e8fc5

@jdubs
Copy link

@jdubs jdubs commented on 3c9188c Aug 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Please sign in to comment.