From 6b320ce64da1eb33930dbf171c2d5e30c5ab5056 Mon Sep 17 00:00:00 2001 From: Diego Ongaro Date: Mon, 31 Oct 2016 13:48:21 -0700 Subject: [PATCH] Add custom String() formatter to Membership Close #167: Make the stats into a real structure --- api.go | 2 +- api_test.go | 37 +++++++++++++++++++++++++++++++++++-- membership.go | 14 +++++++++++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/api.go b/api.go index e888b5d3b..74d342601 100644 --- a/api.go +++ b/api.go @@ -960,7 +960,7 @@ func (s *Stats) Strings() []struct{ K, V string } { {"fsm_pending", toString(uint64(s.FSMPending))}, {"last_snapshot_index", toString(uint64(s.LastSnapshotIndex))}, {"last_snapshot_term", toString(uint64(s.LastSnapshotTerm))}, - {"latest_membership", fmt.Sprintf("%+v", s.LatestMembership.Servers)}, + {"latest_membership", fmt.Sprintf("%v", s.LatestMembership)}, {"latest_membership_index", toString(uint64(s.LatestMembershipIndex))}, {"last_contact", lastContact}, {"num_peers", toString(uint64(s.NumPeers))}, diff --git a/api_test.go b/api_test.go index 934643c43..d3c795a5e 100644 --- a/api_test.go +++ b/api_test.go @@ -1,9 +1,13 @@ package raft -import "testing" +import ( + "fmt" + "testing" +) func TestAPI_Stats(t *testing.T) { c := MakeCluster(1, t, nil) + defer c.Close() future := c.rafts[0].Stats() if err := future.Error(); err != nil { c.FailNowf("Stats() returned err %v", err) @@ -12,5 +16,34 @@ func TestAPI_Stats(t *testing.T) { if s.LastLogTerm != 1 { c.FailNowf("stats.LastLogTerm expected 1, got %v", s.LastLogTerm) } - c.Close() +} + +func TestAPI_Stats_membershipFormatting(t *testing.T) { + c := MakeCluster(1, t, nil) + defer c.Close() + c.GetInState(Leader) + addFuture := c.rafts[0].AddNonvoter("S2", "s2-addr", 0, 0) + err := addFuture.Error() + if err != nil { + c.FailNowf("AddNonvoter() returned err %v", err) + } + + statsFuture := c.rafts[0].Stats() + err = statsFuture.Error() + if err != nil { + c.FailNowf("Stats() returned err %v", err) + } + s := statsFuture.Stats() + membership := "not found" + for _, kv := range s.Strings() { + if kv.K == "latest_membership" { + membership = kv.V + } + } + exp := fmt.Sprintf("[%v at %v (Voter), S2 at s2-addr (Nonvoter)]", + c.rafts[0].server.localID, c.rafts[0].server.localAddr) + if membership != exp { + c.Failf("membership not stringified correctly. Expected: '%s', got: '%s'", + exp, membership) + } } diff --git a/membership.go b/membership.go index 4074a90a3..9c30f8c03 100644 --- a/membership.go +++ b/membership.go @@ -1,6 +1,9 @@ package raft -import "fmt" +import ( + "fmt" + "strings" +) // Membership changes follow the single-server algorithm described in Diego // Ongaro's PhD dissertation. The Membership struct defines a cluster membership @@ -85,6 +88,15 @@ type Membership struct { Servers []Server } +func (m Membership) String() string { + vec := make([]string, 0, len(m.Servers)) + for _, server := range m.Servers { + vec = append(vec, fmt.Sprintf("%s at %s (%s)", + server.ID, server.Address, server.Suffrage)) + } + return fmt.Sprintf("[%s]", strings.Join(vec, ", ")) +} + // Clone makes a deep copy of a Membership. func (m *Membership) Clone() (copy Membership) { copy.Servers = append(copy.Servers, m.Servers...)