Skip to content
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

Improve dynamic port selection #1494

Closed
wants to merge 13 commits into from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,5 @@ TODO.md
.terraform
*.tfstate*
rkt-*

.idea/
36 changes: 13 additions & 23 deletions nomad/structs/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package structs

import (
"fmt"
"math/rand"
"net"
"sync"
)
Expand Down Expand Up @@ -183,6 +182,8 @@ func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResour
return
}

used := idx.UsedPorts[ipStr]

// Check if any of the reserved ports are in use
for _, port := range ask.ReservedPorts {
// Guard against invalid port
Expand All @@ -192,7 +193,6 @@ func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResour
}

// Check if in use
used := idx.UsedPorts[ipStr]
if used != nil && used.Check(uint(port.Value)) {
err = fmt.Errorf("reserved port collision")
return
Expand All @@ -208,28 +208,19 @@ func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResour
DynamicPorts: ask.DynamicPorts,
}

// Check if we need to generate any ports
for i := 0; i < len(ask.DynamicPorts); i++ {
attempts := 0
PICK:
attempts++
if attempts > maxRandPortAttempts {
err = fmt.Errorf("dynamic port selection failed")
return
}
portRange := PortsFromRange(MinDynamicPort, MaxDynamicPort)
usedPorts := PortsFromBitmap(used, 0, maxValidPort-1)
availablePorts := portRange.Difference(usedPorts)
availablePorts = availablePorts.Difference(ask.ReservedPorts)
availablePorts = availablePorts.ShufflePorts()

randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort)
used := idx.UsedPorts[ipStr]
if used != nil && used.Check(uint(randPort)) {
goto PICK
}
if len(availablePorts) < len(offer.DynamicPorts) {
err = fmt.Errorf("dynamic port selection failed - insufficient available ports")
return
}

for _, ports := range [][]Port{offer.ReservedPorts, offer.DynamicPorts} {
if isPortReserved(ports, randPort) {
goto PICK
}
}
offer.DynamicPorts[i].Value = randPort
for i := 0; i < len(offer.DynamicPorts); i++ {
offer.DynamicPorts[i].Value = availablePorts[i].Value
}

// Stop, we have an offer!
Expand All @@ -240,7 +231,6 @@ func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResour
return
}

// IntContains scans an integer slice for a value
func isPortReserved(haystack []Port, needle int) bool {
for _, item := range haystack {
if item.Value == needle {
Expand Down
77 changes: 75 additions & 2 deletions nomad/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"io"
"math/rand"
"path/filepath"
"reflect"
"regexp"
Expand Down Expand Up @@ -835,15 +836,87 @@ type Port struct {
Value int `mapstructure:"static"`
}

// implement sort interface for Ports
type Ports []Port

func (slice Ports) Len() int {
return len(slice)
}

func (slice Ports) Less(i, j int) bool {
return slice[i].Value < slice[j].Value
}

func (slice Ports) Swap(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}

func (slice Ports) ShufflePorts() Ports {
dest := make(Ports, len(slice))
perm := rand.Perm(len(slice))
for i, v := range perm {
dest[v] = slice[i]
}
return dest
}

func (minuend Ports) Difference(subtrahend Ports) Ports {
makeSet := func(ports Ports) map[int]Port {
portMap := make(map[int]Port, len(ports))
for _, port := range ports {
portMap[port.Value] = port
}

return portMap
}

var results Ports

minuendSet := makeSet(minuend)
subtrahendSet := makeSet(subtrahend)

for value, port := range minuendSet {
if _, ok := subtrahendSet[value]; !ok {
results = append(results, port)
}
}

return results
}

func PortsFromRange(i, j int) Ports {
dest := make(Ports, j-i)
for k := 0; k < j-i; k++ {
dest[k] = Port{Label: string(i + k), Value: i + k}
}
return dest
}

func PortsFromBitmap(portmap Bitmap, i, j int) Ports {
var results Ports

for k := i; k <= j; k++ {
if portmap.Check(uint(k)) {
newPort := Port{
Label: string(k),
Value: k,
}
results = append(results, newPort)
}
}

return results
}

// NetworkResource is used to represent available network
// resources
type NetworkResource struct {
Device string // Name of the device
CIDR string // CIDR block of addresses
IP string // IP address
MBits int // Throughput
ReservedPorts []Port // Reserved ports
DynamicPorts []Port // Dynamically assigned ports
ReservedPorts Ports // Reserved ports
DynamicPorts Ports // Dynamically assigned ports
}

// MeetsMinResources returns an error if the resources specified are less than
Expand Down