Skip to content

Commit

Permalink
feat: availableport enhancements (#3621)
Browse files Browse the repository at this point in the history
* availableport extended for more params

* typo private -> public OptionaParameters

* Tests for updates in availableport

* Corrections by Pantani

* Removing excessive conditions

* Changelog added

* Avoids nil conn error.

* Tests corrected for updated code.

* Param update in `genAddr` to avoid type error

* Faucet.Port int to uint

* Editing 0 int to uint for Faucet.Port type

* add table tests

* remove unused double for

* remove unused double for

* format the code

* Update changelog.md

---------

Co-authored-by: Danilo Pantani <[email protected]>
Co-authored-by: Jerónimo Albi <[email protected]>
  • Loading branch information
3 people authored Oct 13, 2023
1 parent a86dd33 commit 3c9f058
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 20 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

### Changes

- [#3621](https://github.com/ignite/cli/pull/3621) Change `pkg/availableport` to allow custom parameters in `Find` function and handle duplicated ports
- [#3559](https://github.com/ignite/cli/pull/3559) Bump network plugin version to `v0.1.1`
- [#3581](https://github.com/ignite/cli/pull/3581) Bump cometbft and cometbft-db in the template
- [#3522](https://github.com/ignite/cli/pull/3522) Remove indentation from `chain serve` output
Expand Down
2 changes: 1 addition & 1 deletion ignite/config/chain/base/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ type Faucet struct {
Host string `yaml:"host,omitempty"`

// Port number for faucet server to listen at.
Port int `yaml:"port,omitempty"`
Port uint `yaml:"port,omitempty"`
}

// Init overwrites sdk configurations with given values.
Expand Down
2 changes: 1 addition & 1 deletion ignite/config/chain/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func FaucetHost(cfg *Config) string {
// We keep supporting Port option for backward compatibility
// TODO: drop this option in the future
host := cfg.Faucet.Host
if cfg.Faucet.Port != 0 {
if cfg.Faucet.Port != uint(0) {
host = fmt.Sprintf(":%d", cfg.Faucet.Port)
}

Expand Down
90 changes: 73 additions & 17 deletions ignite/pkg/availableport/availableport.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,85 @@ import (
"fmt"
"math/rand"
"net"
"time"
)

type availablePortOptions struct {
randomizer *rand.Rand
minPort uint
maxPort uint
}

type Options func(o *availablePortOptions)

func WithRandomizer(r *rand.Rand) Options {
return func(o *availablePortOptions) {
o.randomizer = r
}
}

func WithMaxPort(maxPort uint) Options {
return func(o *availablePortOptions) {
o.maxPort = maxPort
}
}

func WithMinPort(minPort uint) Options {
return func(o *availablePortOptions) {
o.minPort = minPort
}
}

// Find finds n number of unused ports.
// it is not guaranteed that these ports will not be allocated to
// another program in the time of calling Find().
func Find(n int) (ports []int, err error) {
min := 44000
max := 55000

for i := 0; i < n; i++ {
for {
port := rand.Intn(max-min+1) + min

conn, err := net.Dial("tcp", fmt.Sprintf(":%d", port))
// if there is an error, this might mean that no one is listening from this port
// which is what we need.
if err == nil {
conn.Close()
continue
}
ports = append(ports, port)
break
func Find(n uint, options ...Options) (ports []uint, err error) {
// Defining them before so we can set a value depending on the AvailablePortOptions
opts := availablePortOptions{
minPort: 44000,
maxPort: 55000,
randomizer: rand.New(rand.NewSource(time.Now().UnixNano())),
}

for _, apply := range options {
apply(&opts)
}
// If the number of ports required is bigger than the range, this stops it
if opts.maxPort < opts.minPort {
return nil, fmt.Errorf("invalid ports range: max < min (%d < %d)", opts.maxPort, opts.minPort)
}

// If the number of ports required is bigger than the range, this stops it
if n > (opts.maxPort - opts.minPort) {
return nil, fmt.Errorf("invalid amount of ports requested: limit is %d", opts.maxPort-opts.minPort)
}

// Marker to point if a port is already added in the list
registered := make(map[uint]bool)
for len(registered) < int(n) {
// Greater or equal to min and lower than max
totalPorts := opts.maxPort - opts.minPort + 1
randomPort := opts.randomizer.Intn(int(totalPorts))
port := uint(randomPort) + opts.minPort

conn, err := net.Dial("tcp", fmt.Sprintf(":%d", port))
// if there is an error, this might mean that no one is listening from this port
// which is what we need.
if err == nil {
conn.Close()
continue
}
if conn != nil {
defer conn.Close()
}

// if the port is already registered we skip it to the next one
// otherwise it's added to the ports list and pointed in our map
if registered[port] {
continue
}
ports = append(ports, port)
registered[port] = true
}
return ports, nil
}
78 changes: 78 additions & 0 deletions ignite/pkg/availableport/availableport_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package availableport_test

import (
"fmt"
"math/rand"
"testing"

"github.com/stretchr/testify/require"

"github.com/ignite/cli/ignite/pkg/availableport"
)

func TestFind(t *testing.T) {
tests := []struct {
name string
n uint
options []availableport.Options
err error
}{
{
name: "test 10 ports",
n: 10,
},
{
name: "invalid port range",
n: 10,
options: []availableport.Options{
availableport.WithMinPort(5),
availableport.WithMaxPort(1),
},
err: fmt.Errorf("invalid ports range: max < min (1 < 5)"),
},
{
name: "invalid maximum port range",
n: 10,
options: []availableport.Options{
availableport.WithMinPort(55001),
availableport.WithMaxPort(1),
},
err: fmt.Errorf("invalid ports range: max < min (1 < 55001)"),
},
{
name: "only invalid maximum port range",
n: 10,
options: []availableport.Options{
availableport.WithMaxPort(43999),
},
err: fmt.Errorf("invalid ports range: max < min (43999 < 44000)"),
},
{
name: "with randomizer",
n: 100,
options: []availableport.Options{
availableport.WithRandomizer(rand.New(rand.NewSource(2023))),
availableport.WithMinPort(100),
availableport.WithMaxPort(200),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := availableport.Find(tt.n, tt.options...)
if tt.err != nil {
require.Equal(t, tt.err, err)
return
}
require.NoError(t, err)
require.Len(t, got, int(tt.n))

seen := make(map[uint]struct{})
for _, val := range got {
_, ok := seen[val]
require.Falsef(t, ok, "duplicated port %d", val)
seen[val] = struct{}{}
}
})
}
}
2 changes: 1 addition & 1 deletion integration/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func (a App) RandomizeServerPorts() Hosts {
ports, err := availableport.Find(7)
require.NoError(a.env.t, err)

genAddr := func(port int) string {
genAddr := func(port uint) string {
return fmt.Sprintf("127.0.0.1:%d", port)
}

Expand Down

0 comments on commit 3c9f058

Please sign in to comment.