From e958dc957a2339228bfe74865b4e1728484f4fd0 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 21 May 2023 23:49:03 +0300 Subject: refactor config to separate package --- internal/client.go | 10 +++++ internal/config.go | 86 ------------------------------------------ internal/config/config.go | 82 ++++++++++++++++++++++++++++++++++++++++ internal/config/config_test.go | 71 ++++++++++++++++++++++++++++++++++ internal/config_test.go | 77 ------------------------------------- internal/quorum.go | 12 +++--- internal/quorum_test.go | 44 ++++++++++----------- internal/run.go | 7 +++- internal/server.go | 8 ++-- internal/tcpserver.go | 18 +++++---- internal/utils/string.go | 10 +++++ internal/utils/string_test.go | 11 ++++++ internal/vote.go | 9 +++-- internal/vote_test.go | 10 +++-- 14 files changed, 247 insertions(+), 208 deletions(-) create mode 100644 internal/client.go delete mode 100644 internal/config.go create mode 100644 internal/config/config.go create mode 100644 internal/config/config_test.go delete mode 100644 internal/config_test.go create mode 100644 internal/utils/string.go create mode 100644 internal/utils/string_test.go diff --git a/internal/client.go b/internal/client.go new file mode 100644 index 0000000..6775ab2 --- /dev/null +++ b/internal/client.go @@ -0,0 +1,10 @@ +package internal + +import ( + "context" + + "codeberg.org/snonux/gorum/internal/config" +) + +func runClient(ctx context.Context, conf config.Config) { +} diff --git a/internal/config.go b/internal/config.go deleted file mode 100644 index 21df3b9..0000000 --- a/internal/config.go +++ /dev/null @@ -1,86 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net" - "os" - "strings" -) - -type config struct { - StateDir string - Address string - Participants []string -} - -func newConfig(configFile string) (config, error) { - var config config - - file, err := os.Open(configFile) - if err != nil { - return config, err - } - defer file.Close() - - bytes, err := ioutil.ReadAll(file) - if err != nil { - return config, err - } - - err = json.Unmarshal(bytes, &config) - if err != nil { - return config, err - } - - return config, nil -} - -func (c config) participantNumber(participant string) (int, error) { - for i, participant_ := range c.Participants { - if participant == stripPort(participant_) { - return i, nil - } - } - - return 0, fmt.Errorf("participant %s not found", participant) -} - -func (c config) isParticipant(remoteAddr string) bool { - remoteAddr = stripPort(remoteAddr) - - for _, participant := range c.Participants { - if remoteAddr == stripPort(participant) { - return true - } - } - - return false -} - -func (c config) isParticipantWithLookup(remoteAddr string, lookupIP func(string) ([]net.IP, error)) bool { - remoteAddr = stripPort(remoteAddr) - - for _, participant := range c.Participants { - ips, err := lookupIP(stripPort(participant)) - if err != nil { - log.Println(err) - continue - } - - for _, ip := range ips { - if remoteAddr == ip.String() { - return true - } - } - } - - return false -} - -func stripPort(addr string) string { - parts := strings.Split(addr, ":") - return parts[0] -} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..1e2705c --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,82 @@ +package config + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net" + "os" + + "codeberg.org/snonux/gorum/internal/utils" +) + +type Config struct { + StateDir string + Address string + Participants []string +} + +func New(configFile string) (Config, error) { + var c Config + + file, err := os.Open(configFile) + if err != nil { + return c, err + } + defer file.Close() + + bytes, err := ioutil.ReadAll(file) + if err != nil { + return c, err + } + + err = json.Unmarshal(bytes, &c) + if err != nil { + return c, err + } + + return c, nil +} + +func (c Config) ParticipantNumber(participant string) (int, error) { + for i, participant_ := range c.Participants { + if participant == utils.StripPort(participant_) { + return i, nil + } + } + + return 0, fmt.Errorf("participant %s not found", participant) +} + +func (c Config) IsParticipant(remoteAddr string) bool { + remoteAddr = utils.StripPort(remoteAddr) + + for _, participant := range c.Participants { + if remoteAddr == utils.StripPort(participant) { + return true + } + } + + return false +} + +func (c Config) IsParticipantWithLookup(remoteAddr string, lookupIP func(string) ([]net.IP, error)) bool { + remoteAddr = utils.StripPort(remoteAddr) + + for _, participant := range c.Participants { + ips, err := lookupIP(utils.StripPort(participant)) + if err != nil { + log.Println(err) + continue + } + + for _, ip := range ips { + if remoteAddr == ip.String() { + return true + } + } + } + + return false +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..bccfb2e --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,71 @@ +package config + +import ( + "fmt" + "net" + "testing" +) + +func TestParticipantNumber(t *testing.T) { + conf := Config{Participants: []string{"localhost:1234", "hamburger:4321"}} + + num, err := conf.ParticipantNumber("localhost") + if err != nil { + t.Errorf(err.Error()) + } + if num != 0 { + t.Errorf("localhost should be participant number 0 but is %d", num) + } + + num, err = conf.ParticipantNumber("hamburger") + if err != nil { + t.Errorf(err.Error()) + } + if num != 1 { + t.Errorf("hamburger should be participant number 1 but is %d", num) + } + + _, err = conf.ParticipantNumber("doener") + if err == nil { + t.Errorf("doener is not a participant") + } +} + +func TestIsParticipant(t *testing.T) { + conf := Config{Participants: []string{"localhost:1234", "hamburger:4321"}} + + remoteAddr := "localhost:323232" + if !conf.IsParticipant(remoteAddr) { + t.Errorf("%s should be participant of %v", remoteAddr, conf.Participants) + } + + remoteAddr = "foo.zone:2345" + if conf.IsParticipant(remoteAddr) { + t.Errorf("%s should not be participant of %v", remoteAddr, conf.Participants) + } +} + +func TestIsParticipantWithLookup(t *testing.T) { + conf := Config{Participants: []string{"localhost:1234", "hamburger:4321"}} + + lookupIP := func(addr string) ([]net.IP, error) { + switch addr { + case "localhost": + return []net.IP{{127, 0, 0, 1}}, nil + case "hamburger": + return []net.IP{{8, 8, 8, 8}}, nil + default: + return []net.IP{}, fmt.Errorf("Can't resolve %s", addr) + } + } + + remoteAddr := "127.0.0.1:323232" + if !conf.IsParticipantWithLookup(remoteAddr, lookupIP) { + t.Errorf("%s should be participant of %v", remoteAddr, conf.Participants) + } + + remoteAddr = "9.9.9.9:2345" + if conf.IsParticipantWithLookup(remoteAddr, lookupIP) { + t.Errorf("%s should not be participant of %v", remoteAddr, conf.Participants) + } +} diff --git a/internal/config_test.go b/internal/config_test.go deleted file mode 100644 index d1974b5..0000000 --- a/internal/config_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package internal - -import ( - "fmt" - "net" - "testing" -) - -func TestStripPort(t *testing.T) { - if "localhost" != stripPort("localhost:1234") { - t.Errorf("Unable to split port from \"localhost:1234\"") - } -} - -func TestParticipantNumber(t *testing.T) { - config := config{Participants: []string{"localhost:1234", "hamburger:4321"}} - - num, err := config.participantNumber("localhost") - if err != nil { - t.Errorf(err.Error()) - } - if num != 0 { - t.Errorf("localhost should be participant number 0 but is %d", num) - } - - num, err = config.participantNumber("hamburger") - if err != nil { - t.Errorf(err.Error()) - } - if num != 1 { - t.Errorf("hamburger should be participant number 1 but is %d", num) - } - - _, err = config.participantNumber("doener") - if err == nil { - t.Errorf("doener is not a participant") - } -} - -func TestIsParticipant(t *testing.T) { - config := config{Participants: []string{"localhost:1234", "hamburger:4321"}} - - remoteAddr := "localhost:323232" - if !config.isParticipant(remoteAddr) { - t.Errorf("%s should be participant of %v", remoteAddr, config.Participants) - } - - remoteAddr = "foo.zone:2345" - if config.isParticipant(remoteAddr) { - t.Errorf("%s should not be participant of %v", remoteAddr, config.Participants) - } -} - -func TestIsParticipantWithLookup(t *testing.T) { - config := config{Participants: []string{"localhost:1234", "hamburger:4321"}} - - lookupIP := func(addr string) ([]net.IP, error) { - switch addr { - case "localhost": - return []net.IP{{127, 0, 0, 1}}, nil - case "hamburger": - return []net.IP{{8, 8, 8, 8}}, nil - default: - return []net.IP{}, fmt.Errorf("Can't resolve %s", addr) - } - } - - remoteAddr := "127.0.0.1:323232" - if !config.isParticipantWithLookup(remoteAddr, lookupIP) { - t.Errorf("%s should be participant of %v", remoteAddr, config.Participants) - } - - remoteAddr = "9.9.9.9:2345" - if config.isParticipantWithLookup(remoteAddr, lookupIP) { - t.Errorf("%s should not be participant of %v", remoteAddr, config.Participants) - } -} diff --git a/internal/quorum.go b/internal/quorum.go index 179efb1..b680378 100644 --- a/internal/quorum.go +++ b/internal/quorum.go @@ -4,6 +4,8 @@ import ( "fmt" "log" "sort" + + "codeberg.org/snonux/gorum/internal/config" ) type quorumMap map[string]vote @@ -18,15 +20,15 @@ func (q quorumMap) vote(v vote) { q[v.from] = v } -func (q quorumMap) winner(config config) (string, error) { - scores := q.score(config) +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(config config) (scores []score) { +func (q quorumMap) score(conf config.Config) (scores []score) { scoreMap := make(map[string]int) for _, vote := range q { @@ -46,8 +48,8 @@ func (q quorumMap) score(config config) (scores []score) { } // Score tie, use participant number. - i_, _ := config.participantNumber(scores[i].id) - j_, _ := config.participantNumber(scores[j].id) + i_, _ := conf.ParticipantNumber(scores[i].id) + j_, _ := conf.ParticipantNumber(scores[j].id) return i_ < j_ }) diff --git a/internal/quorum_test.go b/internal/quorum_test.go index d4790f9..6da4956 100644 --- a/internal/quorum_test.go +++ b/internal/quorum_test.go @@ -3,29 +3,31 @@ package internal import ( "testing" "time" + + "codeberg.org/snonux/gorum/internal/config" ) func TestScore(t *testing.T) { quorum := make(quorumMap) - config := config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} + conf := config.Config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} - vote1 := newVote(config, "foo:334234", "foo bar\n") + vote1 := newVote(conf, "foo:334234", "foo bar\n") vote1.expiresAt = time.Now().Add(1 * time.Hour) quorum.vote(vote1) - vote2 := newVote(config, "bar:334234", "bar baz\n") + vote2 := newVote(conf, "bar:334234", "bar baz\n") vote2.expiresAt = time.Now().Add(1 * time.Hour) quorum.vote(vote2) - vote3_dup := newVote(config, "bar:33234", "bar baz\n") + vote3_dup := newVote(conf, "bar:33234", "bar baz\n") vote3_dup.expiresAt = time.Now().Add(1 * time.Hour) quorum.vote(vote3_dup) - vote4 := newVote(config, "baz:334234", "foo bar baz\n") + vote4 := newVote(conf, "baz:334234", "foo bar baz\n") vote4.expiresAt = time.Now().Add(1 * time.Hour) quorum.vote(vote4) - scores := quorum.score(config) + scores := quorum.score(conf) if len(scores) != 3 { t.Errorf("Expected scores to be of length 3: %v", scores) } @@ -36,16 +38,16 @@ func TestScore(t *testing.T) { } func TestTieScore(t *testing.T) { - addVotes := func(config config, quorum quorumMap) { - vote1 := newVote(config, "foo:334234", "foo bar baz\n") + addVotes := func(conf config.Config, quorum quorumMap) { + vote1 := newVote(conf, "foo:334234", "foo bar baz\n") vote1.expiresAt = time.Now().Add(1 * time.Hour) quorum.vote(vote1) - vote2 := newVote(config, "bar:334234", "foo bar baz\n") + vote2 := newVote(conf, "bar:334234", "foo bar baz\n") vote2.expiresAt = time.Now().Add(1 * time.Hour) quorum.vote(vote2) - vote3 := newVote(config, "baz:334234", "foo bar baz\n") + vote3 := newVote(conf, "baz:334234", "foo bar baz\n") vote3.expiresAt = time.Now().Add(1 * time.Hour) quorum.vote(vote3) } @@ -53,10 +55,10 @@ func TestTieScore(t *testing.T) { 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. - config := config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} + conf := config.Config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} - addVotes(config, quorum) - scores := quorum.score(config) + addVotes(conf, quorum) + scores := quorum.score(conf) if len(scores) != 3 { t.Errorf("Expected scores to be of length 3: %v", scores) @@ -65,7 +67,7 @@ func TestTieScore(t *testing.T) { t.Errorf("Expected score[0] to be {foo,3}: %v", scores[0]) } - winner, _ := quorum.winner(config) + winner, _ := quorum.winner(conf) if winner != "foo" { t.Errorf("Expected the winner to be foo but is: %s", winner) } @@ -74,10 +76,10 @@ func TestTieScore(t *testing.T) { 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. - config := config{Participants: []string{"bar:1234", "foo:4321", "baz:3444"}} + conf := config.Config{Participants: []string{"bar:1234", "foo:4321", "baz:3444"}} - addVotes(config, quorum) - scores := quorum.score(config) + addVotes(conf, quorum) + scores := quorum.score(conf) if len(scores) != 3 { t.Errorf("Expected scores to be of length 3: %v", scores) @@ -86,7 +88,7 @@ func TestTieScore(t *testing.T) { t.Errorf("Expected score[0] to be {bar,3}: %v", scores[0]) } - winner, _ := quorum.winner(config) + winner, _ := quorum.winner(conf) if winner != "bar" { t.Errorf("Expected the winner to be bar but is: %s", winner) } @@ -95,13 +97,13 @@ func TestTieScore(t *testing.T) { func TestCleanExpired(t *testing.T) { quorum := make(quorumMap) - config := config{Participants: []string{"foo:1234", "bay:4321"}} + conf := config.Config{Participants: []string{"foo:1234", "bay:4321"}} - vote1 := newVote(config, "foo:334234", " foo bar baz bay\n") + vote1 := newVote(conf, "foo:334234", " foo bar baz bay\n") vote1.expiresAt = time.Now().Add(1 * time.Hour) quorum.vote(vote1) - vote2 := newVote(config, "bar:334234", " foo bar baz bay\n") + vote2 := newVote(conf, "bar:334234", " foo bar baz bay\n") vote2.expiresAt = time.Now() quorum.vote(vote2) diff --git a/internal/run.go b/internal/run.go index a8be7ca..d575ded 100644 --- a/internal/run.go +++ b/internal/run.go @@ -2,13 +2,16 @@ package internal import ( "context" + + "codeberg.org/snonux/gorum/internal/config" ) func Run(ctx context.Context, configFile string) { - config, err := newConfig(configFile) + conf, err := config.New(configFile) if err != nil { panic(err) } - runServer(ctx, config) + go runClient(ctx, conf) + runServer(ctx, conf) } diff --git a/internal/server.go b/internal/server.go index 2024b88..e5e55d1 100644 --- a/internal/server.go +++ b/internal/server.go @@ -4,9 +4,11 @@ import ( "context" "log" "time" + + "codeberg.org/snonux/gorum/internal/config" ) -func runServer(ctx context.Context, config config) { +func runServer(ctx context.Context, conf config.Config) { ch := make(chan vote) quorum := make(quorumMap) @@ -15,7 +17,7 @@ func runServer(ctx context.Context, config config) { select { case vote := <-ch: quorum.vote(vote) - winner, err := quorum.winner(config) + winner, err := quorum.winner(conf) if err != nil { log.Println(err.Error()) continue @@ -29,7 +31,7 @@ func runServer(ctx context.Context, config config) { } }() - if err := startTcpServer(ctx, config, ch); err != nil { + if err := startTcpServer(ctx, conf, ch); err != nil { panic(err) } } diff --git a/internal/tcpserver.go b/internal/tcpserver.go index 024f563..0f9c006 100644 --- a/internal/tcpserver.go +++ b/internal/tcpserver.go @@ -6,16 +6,18 @@ import ( "fmt" "log" "net" + + "codeberg.org/snonux/gorum/internal/config" ) -func startTcpServer(ctx context.Context, config config, ch chan<- vote) error { - listener, err := net.Listen("tcp", config.Address) +func startTcpServer(ctx context.Context, conf config.Config, ch chan<- vote) error { + listener, err := net.Listen("tcp", conf.Address) if err != nil { return fmt.Errorf("Error starting TCP server: %s", err.Error()) } defer listener.Close() - log.Printf("TCP server started on %s\n", config.Address) + log.Printf("TCP server started on %s\n", conf.Address) for { conn, err := listener.Accept() @@ -24,18 +26,20 @@ func startTcpServer(ctx context.Context, config config, ch chan<- vote) error { continue } - if !config.isParticipantWithLookup(conn.RemoteAddr().String(), net.LookupIP) { + if !conf.IsParticipantWithLookup(conn.RemoteAddr().String(), net.LookupIP) { log.Printf("Denying connection, peer not a participant: %v\n", conn.RemoteAddr().String()) conn.Close() continue } log.Printf("Client connected: %s\n", conn.RemoteAddr().String()) - go handleConnection(ctx, config, conn, ch) + go handleConnection(ctx, conf, conn, ch) } } -func handleConnection(ctx context.Context, config config, conn net.Conn, ch chan<- vote) { +func handleConnection(ctx context.Context, conf config.Config, + conn net.Conn, ch chan<- vote) { + defer conn.Close() remoteAddr := conn.RemoteAddr().String() @@ -53,7 +57,7 @@ func handleConnection(ctx context.Context, config config, conn net.Conn, ch chan } log.Printf("Received message from %s: %s", remoteAddr, message) - ch <- newVote(config, remoteAddr, message) + ch <- newVote(conf, remoteAddr, message) conn.Write([]byte(message)) } diff --git a/internal/utils/string.go b/internal/utils/string.go new file mode 100644 index 0000000..58456ff --- /dev/null +++ b/internal/utils/string.go @@ -0,0 +1,10 @@ +package utils + +import ( + "strings" +) + +func StripPort(addr string) string { + parts := strings.Split(addr, ":") + return parts[0] +} diff --git a/internal/utils/string_test.go b/internal/utils/string_test.go new file mode 100644 index 0000000..ca3c0dd --- /dev/null +++ b/internal/utils/string_test.go @@ -0,0 +1,11 @@ +package utils + +import ( + "testing" +) + +func TestStripPort(t *testing.T) { + if "localhost" != StripPort("localhost:1234") { + t.Errorf("Unable to split port from \"localhost:1234\"") + } +} diff --git a/internal/vote.go b/internal/vote.go index b2db2d9..a9c93f0 100644 --- a/internal/vote.go +++ b/internal/vote.go @@ -4,6 +4,9 @@ import ( "log" "strings" "time" + + "codeberg.org/snonux/gorum/internal/config" + "codeberg.org/snonux/gorum/internal/utils" ) const voteExpiry = 20 * time.Second @@ -14,17 +17,17 @@ type vote struct { expiresAt time.Time } -func newVote(config config, from, message string) vote { +func newVote(conf config.Config, from, message string) vote { var ids []string for _, id := range strings.Split(strings.TrimSpace(message), " ") { - if !config.isParticipant(id) { + if !conf.IsParticipant(id) { log.Printf("%s is not a participant, excluding from the vote", id) continue } ids = append(ids, id) } - return vote{stripPort(from), ids, time.Now().Add(voteExpiry)} + return vote{utils.StripPort(from), ids, time.Now().Add(voteExpiry)} } func (v vote) expired() bool { diff --git a/internal/vote_test.go b/internal/vote_test.go index 9ebb57e..77e54c8 100644 --- a/internal/vote_test.go +++ b/internal/vote_test.go @@ -3,11 +3,13 @@ package internal import ( "testing" "time" + + "codeberg.org/snonux/gorum/internal/config" ) func TestVote(t *testing.T) { - config := config{Participants: []string{"foo:1234", "bay:4321"}} - v := newVote(config, "earth:334234", " foo bar baz bay\n") + conf := config.Config{Participants: []string{"foo:1234", "bay:4321"}} + v := newVote(conf, "earth:334234", " foo bar baz bay\n") if v.from != "earth" { t.Errorf("Expected vote to come from earth but came from %s", v.from) @@ -27,8 +29,8 @@ func TestVote(t *testing.T) { } func TestVoteExpiry(t *testing.T) { - config := config{Participants: []string{"foo:1234", "bay:4321"}} - v := newVote(config, "earth:334234", " foo bar baz bay\n") + conf := config.Config{Participants: []string{"foo:1234", "bay:4321"}} + v := newVote(conf, "earth:334234", " foo bar baz bay\n") // Set expiry 1h into the future v.expiresAt = time.Now().Add(1 * time.Hour) -- cgit v1.2.3