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

scheduler: tolerate having only one dynamic port available #17619

Merged
merged 1 commit into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/17619.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
scheduler: Fixed a panic when a node has only one configured dynamic port
```
6 changes: 5 additions & 1 deletion nomad/structs/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,11 @@ func getDynamicPortsStochastic(nodeUsed Bitmap, portsInOffer []int, minDynamicPo
return nil, fmt.Errorf("stochastic dynamic port selection failed")
}

randPort := minDynamicPort + rand.Intn(maxDynamicPort-minDynamicPort)
randPort := minDynamicPort
if maxDynamicPort-minDynamicPort > 0 {
randPort = randPort + rand.Intn(maxDynamicPort-minDynamicPort)
}

if nodeUsed != nil && nodeUsed.Check(uint(randPort)) {
goto PICK
}
Expand Down
85 changes: 82 additions & 3 deletions nomad/structs/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ func TestNetworkIndex_yieldIP(t *testing.T) {
func TestNetworkIndex_AssignPorts(t *testing.T) {
ci.Parallel(t)

// Create a node that only has one free port
// Create a node that only two free dynamic ports
idx := NewNetworkIndex()
n := &Node{
NodeResources: &NodeResources{
Expand Down Expand Up @@ -424,6 +424,84 @@ func TestNetworkIndex_AssignPorts(t *testing.T) {
must.Between(t, idx.MaxDynamicPort-1, adminPortMapping.Value, idx.MaxDynamicPort)
}

// TestNetworkIndex_AssignPorts_SmallRange exercises assigning ports on group
// networks with small dynamic port ranges configured
func TestNetworkIndex_AssignPortss_SmallRange(t *testing.T) {
ci.Parallel(t)

n := &Node{
NodeResources: &NodeResources{
NodeNetworks: []*NodeNetworkResource{
{
Mode: "host",
Device: "eth0",
Speed: 1000,
Addresses: []NodeNetworkAddress{
{
Alias: "default",
Address: "192.168.0.100",
Family: NodeNetworkAF_IPv4,
},
},
},
},
},
}

testCases := []struct {
name string
min int
max int
ask []Port
expectErr string
}{
{
name: "1 dynamic port avail and 1 port requested",
min: 20000,
max: 20000,
ask: []Port{{"http", 0, 80, "default"}},
expectErr: "",
},
{
name: "1 dynamic port avail and 2 ports requested",
min: 20000,
max: 20000,
ask: []Port{{"http", 0, 80, "default"}, {"admin", 0, 80, "default"}},
expectErr: "dynamic port selection failed",
},
{
name: "2 dynamic ports avail and 2 ports requested",
min: 20000,
max: 20001,
ask: []Port{{"http", 0, 80, "default"}, {"admin", 0, 80, "default"}},
expectErr: "",
},
}

for _, tc := range testCases {

idx := NewNetworkIndex()
idx.MinDynamicPort = tc.min
idx.MaxDynamicPort = tc.max
idx.SetNode(n)

ask := &NetworkResource{DynamicPorts: tc.ask}
offer, err := idx.AssignPorts(ask)
if tc.expectErr != "" {
must.EqError(t, err, tc.expectErr)
} else {
must.NoError(t, err)
must.NotNil(t, offer, must.Sprint("did not get an offer"))

for _, port := range tc.ask {
_, ok := offer.Get(port.Label)
must.True(t, ok)
}
}
}

}

func TestNetworkIndex_AssignTaskNetwork(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
Expand Down Expand Up @@ -531,7 +609,7 @@ func TestNetworkIndex_AssignTaskNetwork(t *testing.T) {
func TestNetworkIndex_AssignTaskNetwork_Dynamic_Contention(t *testing.T) {
ci.Parallel(t)

// Create a node that only has one free port
// Create a node that only has two free dynamic ports
idx := NewNetworkIndex()
n := &Node{
NodeResources: &NodeResources{
Expand All @@ -546,6 +624,7 @@ func TestNetworkIndex_AssignTaskNetwork_Dynamic_Contention(t *testing.T) {
},
ReservedResources: &NodeReservedResources{
Networks: NodeReservedNetworkResources{
// leave only 2 available ports
ReservedHostPorts: fmt.Sprintf("%d-%d", idx.MinDynamicPort, idx.MaxDynamicPort-2),
},
},
Expand All @@ -561,7 +640,7 @@ func TestNetworkIndex_AssignTaskNetwork_Dynamic_Contention(t *testing.T) {
must.NoError(t, err)
must.NotNil(t, offer, must.Sprint("did not get an offer"))
must.Eq(t, "192.168.0.100", offer.IP)
must.Len(t, 2, offer.DynamicPorts, must.Sprint("There should be one dynamic ports"))
must.Len(t, 2, offer.DynamicPorts, must.Sprint("There should be two dynamic ports"))

must.NotEq(t, offer.DynamicPorts[0].Value, offer.DynamicPorts[1].Value,
must.Sprint("assigned dynamic ports must not conflict"))
Expand Down
Loading