-
Notifications
You must be signed in to change notification settings - Fork 22
Add Random Load Balancing Strategy #590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -130,4 +130,5 @@ const ( | |
| // Load balancing strategies. | ||
| const ( | ||
| RoundRobinStrategy = "ROUND_ROBIN" | ||
| RANDOMStrategy = "RANDOM" | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package network | ||
|
|
||
| import ( | ||
| "crypto/rand" | ||
| "errors" | ||
| "fmt" | ||
| "math/big" | ||
| "sync" | ||
|
|
||
| gerr "github.com/gatewayd-io/gatewayd/errors" | ||
| ) | ||
|
|
||
| // Random is a struct that holds a list of proxies and a mutex for thread safety. | ||
| type Random struct { | ||
| mu sync.Mutex | ||
| proxies []IProxy | ||
| } | ||
|
|
||
| // NewRandom creates a new Random instance with the given server's proxies. | ||
| func NewRandom(server *Server) *Random { | ||
| return &Random{ | ||
| proxies: server.Proxies, | ||
| } | ||
| } | ||
|
|
||
| // NextProxy returns a random proxy from the list. | ||
| func (r *Random) NextProxy() (IProxy, *gerr.GatewayDError) { | ||
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
|
|
||
| proxiesLen := len(r.proxies) | ||
| if proxiesLen == 0 { | ||
| return nil, gerr.ErrNoProxiesAvailable.Wrap(errors.New("proxy list is empty")) | ||
| } | ||
|
|
||
| randomIndex, err := randInt(proxiesLen) | ||
| if err != nil { | ||
| return nil, gerr.ErrNoProxiesAvailable.Wrap(err) | ||
| } | ||
|
|
||
| return r.proxies[randomIndex], nil | ||
| } | ||
|
|
||
| // randInt generates a random integer between 0 and max-1 using crypto/rand. | ||
| func randInt(max int) (int, error) { | ||
| // Generate a secure random number | ||
| n, err := rand.Int(rand.Reader, big.NewInt(int64(max))) | ||
| if err != nil { | ||
| return 0, fmt.Errorf("failed to generate random integer: %w", err) | ||
| } | ||
| return int(n.Int64()), nil | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,95 @@ | ||||||||||||||
| package network | ||||||||||||||
|
|
||||||||||||||
| import ( | ||||||||||||||
| "sync" | ||||||||||||||
| "testing" | ||||||||||||||
|
|
||||||||||||||
| gerr "github.com/gatewayd-io/gatewayd/errors" | ||||||||||||||
| "github.com/stretchr/testify/assert" | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| // TestNewRandom verifies that the NewRandom function properly initializes | ||||||||||||||
| // a Random object with the provided server and its associated proxies. | ||||||||||||||
| // The test ensures that the Random object is not nil and that the number of | ||||||||||||||
| // proxies in the Random object matches the number of proxies in the Server. | ||||||||||||||
| func TestNewRandom(t *testing.T) { | ||||||||||||||
| proxies := []IProxy{&MockProxy{}, &MockProxy{}} | ||||||||||||||
| server := &Server{Proxies: proxies} | ||||||||||||||
| random := NewRandom(server) | ||||||||||||||
|
|
||||||||||||||
| assert.NotNil(t, random) | ||||||||||||||
| assert.Equal(t, len(proxies), len(random.proxies)) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // TestGetNextProxy checks the behavior of the NextProxy method in various | ||||||||||||||
| // scenarios, including when proxies are available, when no proxies are available, | ||||||||||||||
| // and the randomness of proxy selection. | ||||||||||||||
| // | ||||||||||||||
| // - The first sub-test confirms that NextProxy returns a valid proxy when available. | ||||||||||||||
| // - The second sub-test ensures that an error is returned when there are no proxies available. | ||||||||||||||
| // - The third sub-test checks if the proxy selection is random by comparing two subsequent calls. | ||||||||||||||
| func TestGetNextProxy(t *testing.T) { | ||||||||||||||
| t.Run("Returns a proxy when proxies are available", func(t *testing.T) { | ||||||||||||||
| proxies := []IProxy{&MockProxy{}, &MockProxy{}} | ||||||||||||||
| server := &Server{Proxies: proxies} | ||||||||||||||
| random := NewRandom(server) | ||||||||||||||
|
|
||||||||||||||
| proxy, err := random.NextProxy() | ||||||||||||||
|
|
||||||||||||||
| assert.Nil(t, err) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
| assert.Contains(t, proxies, proxy) | ||||||||||||||
| }) | ||||||||||||||
|
|
||||||||||||||
| t.Run("Returns error when no proxies are available", func(t *testing.T) { | ||||||||||||||
| server := &Server{Proxies: []IProxy{}} | ||||||||||||||
| random := NewRandom(server) | ||||||||||||||
|
|
||||||||||||||
| proxy, err := random.NextProxy() | ||||||||||||||
|
|
||||||||||||||
| assert.Nil(t, proxy) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Otherwise next line will panic when there is no error I'm unsure if the gerr implents Error() If so, I would use
Suggested change
|
||||||||||||||
| assert.Equal(t, gerr.ErrNoProxiesAvailable.Message, err.Message) | ||||||||||||||
| }) | ||||||||||||||
| t.Run("Random selection of proxies", func(t *testing.T) { | ||||||||||||||
| proxies := []IProxy{&MockProxy{}, &MockProxy{}} | ||||||||||||||
| server := &Server{Proxies: proxies} | ||||||||||||||
| random := NewRandom(server) | ||||||||||||||
|
|
||||||||||||||
| proxy1, _ := random.NextProxy() | ||||||||||||||
| proxy2, _ := random.NextProxy() | ||||||||||||||
|
Comment on lines
+57
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| assert.Contains(t, proxies, proxy1) | ||||||||||||||
| assert.Contains(t, proxies, proxy2) | ||||||||||||||
| // It's possible that proxy1 and proxy2 are the same, but if we run this | ||||||||||||||
| // test enough times, they should occasionally be different. | ||||||||||||||
| }) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // TestConcurrencySafety ensures that the Random object is safe for concurrent | ||||||||||||||
| // use by multiple goroutines. The test launches multiple goroutines that | ||||||||||||||
| // concurrently call the NextProxy method. It then verifies that all returned | ||||||||||||||
| // proxies are part of the expected set of proxies, ensuring thread safety. | ||||||||||||||
| func TestConcurrencySafety(t *testing.T) { | ||||||||||||||
| proxies := []IProxy{&MockProxy{}, &MockProxy{}} | ||||||||||||||
| server := &Server{Proxies: proxies} | ||||||||||||||
| random := NewRandom(server) | ||||||||||||||
|
|
||||||||||||||
| var waitGroup sync.WaitGroup | ||||||||||||||
| numGoroutines := 100 | ||||||||||||||
| proxyChan := make(chan IProxy, numGoroutines) | ||||||||||||||
|
|
||||||||||||||
| for range numGoroutines { | ||||||||||||||
| waitGroup.Add(1) | ||||||||||||||
| go func() { | ||||||||||||||
| defer waitGroup.Done() | ||||||||||||||
| proxy, _ := random.NextProxy() | ||||||||||||||
| proxyChan <- proxy | ||||||||||||||
| }() | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| waitGroup.Wait() | ||||||||||||||
| close(proxyChan) | ||||||||||||||
|
|
||||||||||||||
| for proxy := range proxyChan { | ||||||||||||||
| assert.Contains(t, proxies, proxy) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.