Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add GetHostPort() and update unit tests
Browse files Browse the repository at this point in the history
chienhanlin committed Feb 15, 2023
1 parent 1776c10 commit 0e3556e
Showing 2 changed files with 122 additions and 3 deletions.
36 changes: 34 additions & 2 deletions agent/utils/ephemeral_ports.go
Original file line number Diff line number Diff line change
@@ -18,9 +18,11 @@ import (
"math/rand"
"net"
"strconv"
"strings"
"sync"
"time"

log "github.com/cihub/seelog"
"github.com/docker/go-connections/nat"
)

@@ -29,6 +31,9 @@ const (
EphemeralPortMin = 32768
EphemeralPortMax = 60999
maxPortSelectionAttempts = 100
portUnavailableMsg = "Port %v is unavailable or an error occurred while listening on the local %v network"
portNotFoundErrMsg = "a host port is unavailable"
portsNotFoundErrMsg = "%v contiguous host ports are unavailable"
)

var (
@@ -92,10 +97,32 @@ func (pt *safePortTracker) GetLastAssignedHostPort() int {
var tracker safePortTracker

// GetHostPortRange gets N contiguous host ports from the ephemeral host port range defined on the host.
// dynamicHostPortRange can be set by customers using ECS Agent environment variable ECS_DYNAMIC_HOST_PORT_RANGE;
// otherwise, ECS Agent will use the default value returned from GetDynamicHostPortRange() in the utils package.
func GetHostPortRange(numberOfPorts int, protocol string, dynamicHostPortRange string) (string, error) {
portLock.Lock()
defer portLock.Unlock()
result, err := getNumOfHostPorts(numberOfPorts, protocol, dynamicHostPortRange)
return result, err
}

// GetHostPort gets 1 host port from the ephemeral host port range defined on the host.
// dynamicHostPortRange can be set by customers using ECS Agent environment variable ECS_DYNAMIC_HOST_PORT_RANGE;
// otherwise, ECS Agent will use the default value returned from GetDynamicHostPortRange() in the utils package.
func GetHostPort(protocol string, dynamicHostPortRange string) (string, error) {
portLock.Lock()
defer portLock.Unlock()
numberOfPorts := 1
result, err := getNumOfHostPorts(numberOfPorts, protocol, dynamicHostPortRange)
if err == nil {
result = strings.Split(result, "-")[0]
}
return result, err
}

// getNumOfHostPorts returns the requested number of host ports using the given dynamic host port range
// and protocol. If no host port(s) was/were found, an empty string along with the error message will be returned.
func getNumOfHostPorts(numberOfPorts int, protocol, dynamicHostPortRange string) (string, error) {
// get ephemeral port range, either default or if custom-defined
startHostPortRange, endHostPortRange, _ := nat.ParsePortRangeToInt(dynamicHostPortRange)
start := startHostPortRange
@@ -125,7 +152,6 @@ func GetHostPortRange(numberOfPorts int, protocol string, dynamicHostPortRange s
} else {
tracker.SetLastAssignedHostPort(lastCheckedPort)
}

return result, err
}

@@ -139,6 +165,7 @@ func getHostPortRange(numberOfPorts, start, end int, protocol string) (string, i
ln, err := net.Listen(protocol, ":"+portStr)
// either port is unavailable or some error occurred while listening, we proceed to the next port
if err != nil {
log.Debugf(portUnavailableMsg, portStr, protocol)
continue
}
// let's close the listener first
@@ -151,6 +178,7 @@ func getHostPortRange(numberOfPorts, start, end int, protocol string) (string, i
ln, err := net.ListenPacket(protocol, ":"+portStr)
// either port is unavailable or some error occurred while listening, we proceed to the next port
if err != nil {
log.Debugf(portUnavailableMsg, portStr, protocol)
continue
}
// let's close the listener first
@@ -177,7 +205,11 @@ func getHostPortRange(numberOfPorts, start, end int, protocol string) (string, i
}

if n != numberOfPorts {
return "", resultEndPort, fmt.Errorf("%v contiguous host ports unavailable", numberOfPorts)
errMsg := fmt.Errorf(portNotFoundErrMsg)
if numberOfPorts > 1 {
errMsg = fmt.Errorf(portsNotFoundErrMsg, numberOfPorts)
}
return "", resultEndPort, errMsg
}

return fmt.Sprintf("%d-%d", resultStartPort, resultEndPort), resultEndPort, nil
89 changes: 88 additions & 1 deletion agent/utils/ephemeral_ports_test.go
Original file line number Diff line number Diff line change
@@ -124,7 +124,7 @@ func TestGetHostPortRange(t *testing.T) {
testDynamicHostPortRange: "40001-40005",
protocol: testTCPProtocol,
numberOfRequests: 1,
expectedError: errors.New("20 contiguous host ports unavailable"),
expectedError: errors.New("20 contiguous host ports are unavailable"),
},
}

@@ -155,10 +155,97 @@ func TestGetHostPortRange(t *testing.T) {
}
}

func TestGetHostPort(t *testing.T) {
numberOfPorts := 1
testCases := []struct {
testName string
testDynamicHostPortRange string
protocol string
numberOfRequests int
resetLastAssignedHostPort bool
}{
{
testName: "tcp protocol, a host port found",
testDynamicHostPortRange: "40090-40099",
protocol: testTCPProtocol,
resetLastAssignedHostPort: true,
numberOfRequests: 1,
},
{
testName: "udp protocol, a host port found",
testDynamicHostPortRange: "40090-40099",
protocol: testUDPProtocol,
resetLastAssignedHostPort: true,
numberOfRequests: 1,
},
{
testName: "5 requests for host port in succession, success",
testDynamicHostPortRange: "50090-50099",
protocol: testTCPProtocol,
resetLastAssignedHostPort: true,
numberOfRequests: 5,
},
{
testName: "5 requests for host port in succession, success",
testDynamicHostPortRange: "50090-50099",
protocol: testUDPProtocol,
resetLastAssignedHostPort: false,
numberOfRequests: 5,
},
}

for _, tc := range testCases {
if tc.resetLastAssignedHostPort {
tracker.SetLastAssignedHostPort(0)
}

t.Run(tc.testName, func(t *testing.T) {
for i := 0; i < tc.numberOfRequests; i++ {
hostPortRange, err := GetHostPort(tc.protocol, tc.testDynamicHostPortRange)
assert.NoError(t, err)
numberOfHostPorts, err := getPortRangeLength(hostPortRange)
assert.NoError(t, err)
assert.Equal(t, numberOfPorts, numberOfHostPorts)

actualResult := verifyPortsWithinRange(hostPortRange, tc.testDynamicHostPortRange)
assert.True(t, actualResult)
}
})
}
}

func getPortRangeLength(portRange string) (int, error) {
startPort, endPort, err := nat.ParsePortRangeToInt(portRange)
if err != nil {
return 0, err
}
return endPort - startPort + 1, nil
}

// verifyPortsWithinRange returns true if the actualPortRange is within the expectedPortRange;
// otherwise, returns false.
func verifyPortsWithinRange(actualPortRange, expectedPortRange string) bool {
// get the actual start port and end port
aStartPort, aEndPort, _ := nat.ParsePortRangeToInt(actualPortRange)
// get the expected start port and end port
eStartPort, eEndPort, _ := nat.ParsePortRangeToInt(expectedPortRange)
// check the actual start port is in the expected range or not
aStartIsInRange := portIsInRange(aStartPort, eStartPort, eEndPort)
// check the actual end port is in the expected range or not
aEndIsInRange := portIsInRange(aEndPort, eStartPort, eEndPort)

// return true if both actual start port and end port are in the expected range
if aStartIsInRange && aEndIsInRange {
return true
}

return false
}

// portIsInRange checks the given port is within the start-end range
func portIsInRange(port, start, end int) bool {
if (port >= start) && (port <= end) {
return true
}
return false
}

0 comments on commit 0e3556e

Please sign in to comment.