From 14e73217895307983ae762cb5ffd29ac45d8d1d1 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 22 May 2023 00:06:30 +0300 Subject: move quorum into its own package --- internal/quorum.go | 72 ------------------------- internal/quorum/quorum.go | 72 +++++++++++++++++++++++++ internal/quorum/quorum_test.go | 119 +++++++++++++++++++++++++++++++++++++++++ internal/quorum_test.go | 119 ----------------------------------------- internal/server.go | 9 ++-- 5 files changed, 196 insertions(+), 195 deletions(-) delete mode 100644 internal/quorum.go create mode 100644 internal/quorum/quorum.go create mode 100644 internal/quorum/quorum_test.go delete mode 100644 internal/quorum_test.go (limited to 'internal') diff --git a/internal/quorum.go b/internal/quorum.go deleted file mode 100644 index a64b07b..0000000 --- a/internal/quorum.go +++ /dev/null @@ -1,72 +0,0 @@ -package internal - -import ( - "fmt" - "log" - "sort" - - "codeberg.org/snonux/gorum/internal/config" - "codeberg.org/snonux/gorum/internal/vote" -) - -type quorumMap map[string]vote.Vote - -type score struct { - id string - score int -} - -func (q quorumMap) vote(v vote.Vote) { - log.Printf("Adding vote %v", v) - q[v.From] = v -} - -func (q quorumMap) winner(conf config.Config) (string, error) { - scores := q.score(conf) - if len(scores) == 0 { - return "", fmt.Errorf("unable to find a winner, empty score list") - } - return scores[0].id, nil -} - -func (q quorumMap) score(conf config.Config) (scores []score) { - scoreMap := make(map[string]int) - - for _, vote := range q { - for _, id := range vote.IDs { - score, _ := scoreMap[id] - scoreMap[id] = score + 1 - } - } - - for id, score_ := range scoreMap { - scores = append(scores, score{id, score_}) - } - - sort.Slice(scores, func(i, j int) bool { - if scores[i].score != scores[j].score { - return scores[i].score > scores[j].score - } - - // Score tie, use participant number. - i_, _ := conf.ParticipantNumber(scores[i].id) - j_, _ := conf.ParticipantNumber(scores[j].id) - return i_ < j_ - }) - - return -} - -func (q quorumMap) cleanExpired() { - var expired []string - - for from, vote := range q { - if vote.Expired() { - expired = append(expired, from) - } - } - - for _, e := range expired { - delete(q, e) - } -} diff --git a/internal/quorum/quorum.go b/internal/quorum/quorum.go new file mode 100644 index 0000000..b28e8a9 --- /dev/null +++ b/internal/quorum/quorum.go @@ -0,0 +1,72 @@ +package quorum + +import ( + "fmt" + "log" + "sort" + + "codeberg.org/snonux/gorum/internal/config" + "codeberg.org/snonux/gorum/internal/vote" +) + +type Quorum map[string]vote.Vote + +type Score struct { + ID string + Value int +} + +func (q Quorum) Vote(v vote.Vote) { + log.Printf("Adding vote %v", v) + q[v.From] = v +} + +func (q Quorum) Winner(conf config.Config) (string, error) { + scores := q.Score(conf) + if len(scores) == 0 { + return "", fmt.Errorf("unable to find a winner, empty score list") + } + return scores[0].ID, nil +} + +func (q Quorum) Score(conf config.Config) (scores []Score) { + scoreMap := make(map[string]int) + + for _, vote := range q { + for _, id := range vote.IDs { + score, _ := scoreMap[id] + scoreMap[id] = score + 1 + } + } + + for id, score_ := range scoreMap { + scores = append(scores, Score{id, score_}) + } + + sort.Slice(scores, func(i, j int) bool { + if scores[i].Value != scores[j].Value { + return scores[i].Value > scores[j].Value + } + + // Score tie, use participant number. + i_, _ := conf.ParticipantNumber(scores[i].ID) + j_, _ := conf.ParticipantNumber(scores[j].ID) + return i_ < j_ + }) + + return +} + +func (q Quorum) CleanExpired() { + var expired []string + + for from, vote := range q { + if vote.Expired() { + expired = append(expired, from) + } + } + + for _, e := range expired { + delete(q, e) + } +} diff --git a/internal/quorum/quorum_test.go b/internal/quorum/quorum_test.go new file mode 100644 index 0000000..3a0f186 --- /dev/null +++ b/internal/quorum/quorum_test.go @@ -0,0 +1,119 @@ +package quorum + +import ( + "testing" + "time" + + "codeberg.org/snonux/gorum/internal/config" + "codeberg.org/snonux/gorum/internal/vote" +) + +func TestScore(t *testing.T) { + quo := make(Quorum) + conf := config.Config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} + + vote1 := vote.New(conf, "foo:334234", "foo bar\n") + vote1.ExpiresAt = time.Now().Add(1 * time.Hour) + quo.Vote(vote1) + + vote2 := vote.New(conf, "bar:334234", "bar baz\n") + vote2.ExpiresAt = time.Now().Add(1 * time.Hour) + quo.Vote(vote2) + + vote3_dup := vote.New(conf, "bar:33234", "bar baz\n") + vote3_dup.ExpiresAt = time.Now().Add(1 * time.Hour) + quo.Vote(vote3_dup) + + vote4 := vote.New(conf, "baz:334234", "foo bar baz\n") + vote4.ExpiresAt = time.Now().Add(1 * time.Hour) + quo.Vote(vote4) + + scores := quo.Score(conf) + if len(scores) != 3 { + t.Errorf("Expected scores to be of length 3: %v", scores) + } + + if scores[0].ID != "bar" || scores[0].Value != 3 { + t.Errorf("Expected score[0] to be {bar,3}: %v", scores[0]) + } +} + +func TestTieScore(t *testing.T) { + addVotes := func(conf config.Config, quo Quorum) { + vote1 := vote.New(conf, "foo:334234", "foo bar baz\n") + vote1.ExpiresAt = time.Now().Add(1 * time.Hour) + quo.Vote(vote1) + + vote2 := vote.New(conf, "bar:334234", "foo bar baz\n") + vote2.ExpiresAt = time.Now().Add(1 * time.Hour) + quo.Vote(vote2) + + vote3 := vote.New(conf, "baz:334234", "foo bar baz\n") + vote3.ExpiresAt = time.Now().Add(1 * time.Hour) + quo.Vote(vote3) + } + + t.Run("First tie score test", func(t *testing.T) { + quo := make(Quorum) + // If it is a tie, the first particpant (here: "foo") will win. + conf := config.Config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} + + addVotes(conf, quo) + scores := quo.Score(conf) + + if len(scores) != 3 { + t.Errorf("Expected scores to be of length 3: %v", scores) + } + if scores[0].ID != "foo" || scores[0].Value != 3 { + t.Errorf("Expected score[0] to be {foo,3}: %v", scores[0]) + } + + winner, _ := quo.Winner(conf) + if winner != "foo" { + t.Errorf("Expected the winner to be foo but is: %s", winner) + } + }) + + t.Run("Second tie score test", func(t *testing.T) { + quo := make(Quorum) + // If it is a tie, the first particpant (here: "bar") will win. + conf := config.Config{Participants: []string{"bar:1234", "foo:4321", "baz:3444"}} + + addVotes(conf, quo) + scores := quo.Score(conf) + + if len(scores) != 3 { + t.Errorf("Expected scores to be of length 3: %v", scores) + } + if scores[0].ID != "bar" || scores[0].Value != 3 { + t.Errorf("Expected score[0] to be {bar,3}: %v", scores[0]) + } + + winner, _ := quo.Winner(conf) + if winner != "bar" { + t.Errorf("Expected the winner to be bar but is: %s", winner) + } + }) +} + +func TestCleanExpired(t *testing.T) { + quo := make(Quorum) + conf := config.Config{Participants: []string{"foo:1234", "bay:4321"}} + + vote1 := vote.New(conf, "foo:334234", " foo bar baz bay\n") + vote1.ExpiresAt = time.Now().Add(1 * time.Hour) + quo.Vote(vote1) + + vote2 := vote.New(conf, "bar:334234", " foo bar baz bay\n") + vote2.ExpiresAt = time.Now() + quo.Vote(vote2) + + if len(quo) != 2 { + t.Errorf("Expected to have two votes before expiry: %v", quo) + } + + quo.CleanExpired() + if len(quo) != 1 { + t.Errorf("Expected to have only one vote after expiry: %v", quo) + } +} diff --git a/internal/quorum_test.go b/internal/quorum_test.go deleted file mode 100644 index 2001937..0000000 --- a/internal/quorum_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package internal - -import ( - "testing" - "time" - - "codeberg.org/snonux/gorum/internal/config" - "codeberg.org/snonux/gorum/internal/vote" -) - -func TestScore(t *testing.T) { - quorum := make(quorumMap) - conf := config.Config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} - - vote1 := vote.New(conf, "foo:334234", "foo bar\n") - vote1.ExpiresAt = time.Now().Add(1 * time.Hour) - quorum.vote(vote1) - - vote2 := vote.New(conf, "bar:334234", "bar baz\n") - vote2.ExpiresAt = time.Now().Add(1 * time.Hour) - quorum.vote(vote2) - - vote3_dup := vote.New(conf, "bar:33234", "bar baz\n") - vote3_dup.ExpiresAt = time.Now().Add(1 * time.Hour) - quorum.vote(vote3_dup) - - vote4 := vote.New(conf, "baz:334234", "foo bar baz\n") - vote4.ExpiresAt = time.Now().Add(1 * time.Hour) - quorum.vote(vote4) - - scores := quorum.score(conf) - if len(scores) != 3 { - t.Errorf("Expected scores to be of length 3: %v", scores) - } - - if scores[0].id != "bar" || scores[0].score != 3 { - t.Errorf("Expected score[0] to be {bar,3}: %v", scores[0]) - } -} - -func TestTieScore(t *testing.T) { - addVotes := func(conf config.Config, quorum quorumMap) { - vote1 := vote.New(conf, "foo:334234", "foo bar baz\n") - vote1.ExpiresAt = time.Now().Add(1 * time.Hour) - quorum.vote(vote1) - - vote2 := vote.New(conf, "bar:334234", "foo bar baz\n") - vote2.ExpiresAt = time.Now().Add(1 * time.Hour) - quorum.vote(vote2) - - vote3 := vote.New(conf, "baz:334234", "foo bar baz\n") - vote3.ExpiresAt = time.Now().Add(1 * time.Hour) - quorum.vote(vote3) - } - - t.Run("First tie score test", func(t *testing.T) { - quorum := make(quorumMap) - // If it is a tie, the first particpant (here: "foo") will win. - conf := config.Config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} - - addVotes(conf, quorum) - scores := quorum.score(conf) - - if len(scores) != 3 { - t.Errorf("Expected scores to be of length 3: %v", scores) - } - if scores[0].id != "foo" || scores[0].score != 3 { - t.Errorf("Expected score[0] to be {foo,3}: %v", scores[0]) - } - - winner, _ := quorum.winner(conf) - if winner != "foo" { - t.Errorf("Expected the winner to be foo but is: %s", winner) - } - }) - - t.Run("Second tie score test", func(t *testing.T) { - quorum := make(quorumMap) - // If it is a tie, the first particpant (here: "bar") will win. - conf := config.Config{Participants: []string{"bar:1234", "foo:4321", "baz:3444"}} - - addVotes(conf, quorum) - scores := quorum.score(conf) - - if len(scores) != 3 { - t.Errorf("Expected scores to be of length 3: %v", scores) - } - if scores[0].id != "bar" || scores[0].score != 3 { - t.Errorf("Expected score[0] to be {bar,3}: %v", scores[0]) - } - - winner, _ := quorum.winner(conf) - if winner != "bar" { - t.Errorf("Expected the winner to be bar but is: %s", winner) - } - }) -} - -func TestCleanExpired(t *testing.T) { - quorum := make(quorumMap) - conf := config.Config{Participants: []string{"foo:1234", "bay:4321"}} - - vote1 := vote.New(conf, "foo:334234", " foo bar baz bay\n") - vote1.ExpiresAt = time.Now().Add(1 * time.Hour) - quorum.vote(vote1) - - vote2 := vote.New(conf, "bar:334234", " foo bar baz bay\n") - vote2.ExpiresAt = time.Now() - quorum.vote(vote2) - - if len(quorum) != 2 { - t.Errorf("Expected to have two votes before expiry: %v", quorum) - } - - quorum.cleanExpired() - if len(quorum) != 1 { - t.Errorf("Expected to have only one vote after expiry: %v", quorum) - } -} diff --git a/internal/server.go b/internal/server.go index 05babc0..3c37d6a 100644 --- a/internal/server.go +++ b/internal/server.go @@ -6,26 +6,27 @@ import ( "time" "codeberg.org/snonux/gorum/internal/config" + "codeberg.org/snonux/gorum/internal/quorum" "codeberg.org/snonux/gorum/internal/vote" ) func runServer(ctx context.Context, conf config.Config) { ch := make(chan vote.Vote) - quorum := make(quorumMap) + quo := make(quorum.Quorum) go func() { for { select { case vote := <-ch: - quorum.vote(vote) - winner, err := quorum.winner(conf) + quo.Vote(vote) + winner, err := quo.Winner(conf) if err != nil { log.Println(err.Error()) continue } log.Printf("The current leader node is %s", winner) case <-time.After(vote.Expiry): - quorum.cleanExpired() + quo.CleanExpired() case <-ctx.Done(): return } -- cgit v1.2.3