open-nomad/nomad/structs/network_test.go
Tim Gross ff9ba8ff73
scheduler: tolerate having only one dynamic port available (#17619)
If the dynamic port range for a node is set so that the min is equal to the max,
there's only one port available and this passes config validation. But the
scheduler panics when it tries to pick a random port. Only add the randomness
when there's more than one to pick from.

Adds a test for the behavior but also adjusts the commentary on a couple of the
existing tests that made it seem like this case was already covered if you
didn't look too closely.

Fixes: #17585
2023-06-20 13:29:25 -04:00

1098 lines
25 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package structs
import (
"fmt"
"net"
"reflect"
"testing"
"github.com/hashicorp/nomad/ci"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNetworkIndex_Copy(t *testing.T) {
ci.Parallel(t)
n := &Node{
NodeResources: &NodeResources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/32",
IP: "192.168.0.100",
MBits: 1000,
},
},
NodeNetworks: []*NodeNetworkResource{
{
Mode: "host",
Device: "eth0",
Speed: 1000,
Addresses: []NodeNetworkAddress{
{
Alias: "default",
Address: "192.168.0.100",
Family: NodeNetworkAF_IPv4,
},
},
},
},
},
Reserved: &Resources{
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
ReservedPorts: []Port{{Label: "ssh", Value: 22}},
MBits: 1,
},
},
},
ReservedResources: &NodeReservedResources{
Networks: NodeReservedNetworkResources{
ReservedHostPorts: "22",
},
},
}
allocs := []*Allocation{
{
AllocatedResources: &AllocatedResources{
Tasks: map[string]*AllocatedTaskResources{
"web": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 20,
ReservedPorts: []Port{{"one", 8000, 0, ""}, {"two", 9000, 0, ""}},
},
},
},
},
},
},
{
AllocatedResources: &AllocatedResources{
Tasks: map[string]*AllocatedTaskResources{
"api": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 50,
ReservedPorts: []Port{{"one", 10000, 0, ""}},
},
},
},
},
},
},
}
netIdx := NewNetworkIndex()
netIdx.SetNode(n)
netIdx.AddAllocs(allocs)
// Copy must be equal.
netIdxCopy := netIdx.Copy()
require.Equal(t, netIdx, netIdxCopy)
// Modifying copy should not affect original value.
n.NodeResources.Networks[0].Device = "eth1"
n.ReservedResources.Networks.ReservedHostPorts = "22,80"
allocs = append(allocs, &Allocation{
AllocatedResources: &AllocatedResources{
Tasks: map[string]*AllocatedTaskResources{
"db": {
Networks: []*NetworkResource{
{
Device: "eth1",
IP: "192.168.0.104",
MBits: 50,
ReservedPorts: []Port{{"one", 4567, 0, ""}},
},
},
},
},
},
})
netIdxCopy.SetNode(n)
netIdxCopy.AddAllocs(allocs)
netIdxCopy.MinDynamicPort = 1000
netIdxCopy.MaxDynamicPort = 2000
require.NotEqual(t, netIdx, netIdxCopy)
}
func TestNetworkIndex_Overcommitted(t *testing.T) {
t.Skip()
ci.Parallel(t)
idx := NewNetworkIndex()
// Consume some network
reserved := &NetworkResource{
Device: "eth0",
IP: "192.168.0.100",
MBits: 505,
ReservedPorts: []Port{{"one", 8000, 0, ""}, {"two", 9000, 0, ""}},
}
collide, reasons := idx.AddReserved(reserved)
if collide || len(reasons) != 0 {
t.Fatalf("bad")
}
if !idx.Overcommitted() {
t.Fatalf("have no resources")
}
// Add resources
n := &Node{
NodeResources: &NodeResources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/32",
MBits: 1000,
},
},
},
}
idx.SetNode(n)
if idx.Overcommitted() {
t.Fatalf("have resources")
}
// Double up our usage
idx.AddReserved(reserved)
if !idx.Overcommitted() {
t.Fatalf("should be overcommitted")
}
}
func TestNetworkIndex_SetNode(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
n := &Node{
NodeResources: &NodeResources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/32",
IP: "192.168.0.100",
MBits: 1000,
},
},
},
ReservedResources: &NodeReservedResources{
Networks: NodeReservedNetworkResources{
ReservedHostPorts: "22",
},
},
}
require.NoError(t, idx.SetNode(n))
require.Len(t, idx.TaskNetworks, 1)
require.Equal(t, 1000, idx.AvailBandwidth["eth0"])
require.True(t, idx.UsedPorts["192.168.0.100"].Check(22))
}
func TestNetworkIndex_AddAllocs(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
allocs := []*Allocation{
{
ClientStatus: AllocClientStatusRunning,
DesiredStatus: AllocDesiredStatusRun,
AllocatedResources: &AllocatedResources{
Tasks: map[string]*AllocatedTaskResources{
"web": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 20,
ReservedPorts: []Port{{"one", 8000, 0, ""}, {"two", 9000, 0, ""}},
},
},
},
},
},
},
{
ClientStatus: AllocClientStatusRunning,
DesiredStatus: AllocDesiredStatusRun,
AllocatedResources: &AllocatedResources{
Tasks: map[string]*AllocatedTaskResources{
"api": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 50,
ReservedPorts: []Port{{"one", 10000, 0, ""}},
},
},
},
},
},
},
{
// Allocations running on clients should have their
// ports counted even if their DesiredStatus=stop
ClientStatus: AllocClientStatusRunning,
DesiredStatus: AllocDesiredStatusStop,
AllocatedResources: &AllocatedResources{
Tasks: map[string]*AllocatedTaskResources{
"api": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 50,
ReservedPorts: []Port{{"one", 10001, 0, ""}},
},
},
},
},
},
},
{
// Allocations *not* running on clients should *not*
// have their ports counted even if their
// DesiredStatus=run
ClientStatus: AllocClientStatusFailed,
DesiredStatus: AllocDesiredStatusRun,
AllocatedResources: &AllocatedResources{
Tasks: map[string]*AllocatedTaskResources{
"api": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 50,
ReservedPorts: []Port{{"one", 10001, 0, ""}},
},
},
},
},
},
},
}
collide, reason := idx.AddAllocs(allocs)
assert.False(t, collide)
assert.Empty(t, reason)
assert.True(t, idx.UsedPorts["192.168.0.100"].Check(8000))
assert.True(t, idx.UsedPorts["192.168.0.100"].Check(9000))
assert.True(t, idx.UsedPorts["192.168.0.100"].Check(10000))
assert.True(t, idx.UsedPorts["192.168.0.100"].Check(10001))
}
func TestNetworkIndex_AddReserved(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
reserved := &NetworkResource{
Device: "eth0",
IP: "192.168.0.100",
MBits: 20,
ReservedPorts: []Port{{"one", 8000, 0, ""}, {"two", 9000, 0, ""}},
}
collide, reasons := idx.AddReserved(reserved)
if collide || len(reasons) > 0 {
t.Fatalf("bad")
}
if idx.UsedBandwidth["eth0"] != 20 {
t.Fatalf("Bad")
}
if !idx.UsedPorts["192.168.0.100"].Check(8000) {
t.Fatalf("Bad")
}
if !idx.UsedPorts["192.168.0.100"].Check(9000) {
t.Fatalf("Bad")
}
// Try to reserve the same network
collide, reasons = idx.AddReserved(reserved)
if !collide || len(reasons) == 0 {
t.Fatalf("bad")
}
}
// XXX Reserving ports doesn't work when yielding from a CIDR block. This is
// okay for now since we do not actually fingerprint CIDR blocks.
func TestNetworkIndex_yieldIP(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
n := &Node{
NodeResources: &NodeResources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/30",
MBits: 1000,
},
},
},
}
idx.SetNode(n)
var out []string
idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) {
out = append(out, ip.String())
return
})
expect := []string{"192.168.0.100", "192.168.0.101",
"192.168.0.102", "192.168.0.103"}
if !reflect.DeepEqual(out, expect) {
t.Fatalf("bad: %v", out)
}
}
// TestNetworkIndex_AssignPorts exercises assigning ports on group networks.
func TestNetworkIndex_AssignPorts(t *testing.T) {
ci.Parallel(t)
// Create a node that only two free dynamic ports
idx := NewNetworkIndex()
n := &Node{
NodeResources: &NodeResources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/32",
IP: "192.168.0.100",
MBits: 1000,
},
},
NodeNetworks: []*NodeNetworkResource{
{
Mode: "host",
Device: "eth0",
Speed: 1000,
Addresses: []NodeNetworkAddress{
{
Alias: "default",
Address: "192.168.0.100",
Family: NodeNetworkAF_IPv4,
},
},
},
},
},
ReservedResources: &NodeReservedResources{
Networks: NodeReservedNetworkResources{
ReservedHostPorts: fmt.Sprintf("%d-%d", idx.MinDynamicPort, idx.MaxDynamicPort-2),
},
},
}
idx.SetNode(n)
// Ask for 2 dynamic ports
ask := &NetworkResource{
ReservedPorts: []Port{{"static", 443, 443, "default"}},
DynamicPorts: []Port{{"http", 0, 80, "default"}, {"admin", 0, 8080, "default"}},
}
offer, err := idx.AssignPorts(ask)
must.NoError(t, err)
must.NotNil(t, offer, must.Sprint("did not get an offer"))
staticPortMapping, ok := offer.Get("static")
must.True(t, ok)
httpPortMapping, ok := offer.Get("http")
must.True(t, ok)
adminPortMapping, ok := offer.Get("admin")
must.True(t, ok)
must.NotEq(t, httpPortMapping.Value, adminPortMapping.Value,
must.Sprint("assigned dynamic ports must not conflict"))
must.Eq(t, 443, staticPortMapping.Value)
must.Between(t, idx.MaxDynamicPort-1, httpPortMapping.Value, idx.MaxDynamicPort)
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()
n := &Node{
NodeResources: &NodeResources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/30",
MBits: 1000,
},
},
},
}
idx.SetNode(n)
allocs := []*Allocation{
{
TaskResources: map[string]*Resources{
"web": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 20,
ReservedPorts: []Port{{"one", 8000, 0, ""}, {"two", 9000, 0, ""}},
},
},
},
},
},
{
TaskResources: map[string]*Resources{
"api": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 50,
ReservedPorts: []Port{{"main", 10000, 0, ""}},
},
},
},
},
},
}
idx.AddAllocs(allocs)
// Ask for a reserved port
ask := &NetworkResource{
ReservedPorts: []Port{{"main", 8000, 0, ""}},
}
offer, err := idx.AssignTaskNetwork(ask)
require.NoError(t, err)
require.NotNil(t, offer)
require.Equal(t, "192.168.0.101", offer.IP)
rp := Port{"main", 8000, 0, ""}
require.Len(t, offer.ReservedPorts, 1)
require.Exactly(t, rp, offer.ReservedPorts[0])
// Ask for dynamic ports
ask = &NetworkResource{
DynamicPorts: []Port{{"http", 0, 80, ""}, {"https", 0, 443, ""}, {"admin", 0, -1, ""}},
}
offer, err = idx.AssignTaskNetwork(ask)
require.NoError(t, err)
require.NotNil(t, offer)
require.Equal(t, "192.168.0.100", offer.IP)
require.Len(t, offer.DynamicPorts, 3)
var adminPort Port
for _, port := range offer.DynamicPorts {
require.NotZero(t, port.Value)
if port.Label == "admin" {
adminPort = port
}
}
require.Equal(t, adminPort.Value, adminPort.To)
// Ask for reserved + dynamic ports
ask = &NetworkResource{
ReservedPorts: []Port{{"main", 2345, 0, ""}},
DynamicPorts: []Port{{"http", 0, 80, ""}, {"https", 0, 443, ""}, {"admin", 0, 8080, ""}},
}
offer, err = idx.AssignTaskNetwork(ask)
require.NoError(t, err)
require.NotNil(t, offer)
require.Equal(t, "192.168.0.100", offer.IP)
rp = Port{"main", 2345, 0, ""}
require.Len(t, offer.ReservedPorts, 1)
require.Exactly(t, rp, offer.ReservedPorts[0])
// Ask for too much bandwidth
ask = &NetworkResource{
MBits: 1000,
}
offer, err = idx.AssignTaskNetwork(ask)
require.Error(t, err)
require.Equal(t, "bandwidth exceeded", err.Error())
require.Nil(t, offer)
}
// This test ensures that even with a small domain of available ports we are
// able to make a dynamic port allocation.
func TestNetworkIndex_AssignTaskNetwork_Dynamic_Contention(t *testing.T) {
ci.Parallel(t)
// Create a node that only has two free dynamic ports
idx := NewNetworkIndex()
n := &Node{
NodeResources: &NodeResources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/32",
IP: "192.168.0.100",
MBits: 1000,
},
},
},
ReservedResources: &NodeReservedResources{
Networks: NodeReservedNetworkResources{
// leave only 2 available ports
ReservedHostPorts: fmt.Sprintf("%d-%d", idx.MinDynamicPort, idx.MaxDynamicPort-2),
},
},
}
idx.SetNode(n)
// Ask for 2 dynamic ports
ask := &NetworkResource{
DynamicPorts: []Port{{"http", 0, 80, ""}, {"admin", 0, 443, ""}},
}
offer, err := idx.AssignTaskNetwork(ask)
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 two dynamic ports"))
must.NotEq(t, offer.DynamicPorts[0].Value, offer.DynamicPorts[1].Value,
must.Sprint("assigned dynamic ports must not conflict"))
must.Between(t, idx.MaxDynamicPort-1, offer.DynamicPorts[0].Value, idx.MaxDynamicPort)
must.Between(t, idx.MaxDynamicPort-1, offer.DynamicPorts[1].Value, idx.MaxDynamicPort)
}
// COMPAT(0.11): Remove in 0.11
func TestNetworkIndex_SetNode_Old(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
n := &Node{
Resources: &Resources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/32",
MBits: 1000,
},
},
},
Reserved: &Resources{
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
ReservedPorts: []Port{{"ssh", 22, 0, ""}},
MBits: 1,
},
},
},
}
require.NoError(t, idx.SetNode(n))
require.Len(t, idx.TaskNetworks, 1)
require.Equal(t, 1000, idx.AvailBandwidth["eth0"])
require.Equal(t, 1, idx.UsedBandwidth["eth0"])
require.True(t, idx.UsedPorts["192.168.0.100"].Check(22))
}
// COMPAT(0.11): Remove in 0.11
func TestNetworkIndex_AddAllocs_Old(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
allocs := []*Allocation{
{
TaskResources: map[string]*Resources{
"web": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 20,
ReservedPorts: []Port{{"one", 8000, 0, ""}, {"two", 9000, 0, ""}},
},
},
},
},
},
{
TaskResources: map[string]*Resources{
"api": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 50,
ReservedPorts: []Port{{"one", 10000, 0, ""}},
},
},
},
},
},
}
collide, reason := idx.AddAllocs(allocs)
if collide || reason != "" {
t.Fatalf("bad")
}
if idx.UsedBandwidth["eth0"] != 70 {
t.Fatalf("Bad")
}
if !idx.UsedPorts["192.168.0.100"].Check(8000) {
t.Fatalf("Bad")
}
if !idx.UsedPorts["192.168.0.100"].Check(9000) {
t.Fatalf("Bad")
}
if !idx.UsedPorts["192.168.0.100"].Check(10000) {
t.Fatalf("Bad")
}
}
// COMPAT(0.11): Remove in 0.11
func TestNetworkIndex_yieldIP_Old(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
n := &Node{
Resources: &Resources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/30",
MBits: 1000,
},
},
},
Reserved: &Resources{
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
ReservedPorts: []Port{{"ssh", 22, 0, ""}},
MBits: 1,
},
},
},
}
idx.SetNode(n)
var out []string
idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) {
out = append(out, ip.String())
return
})
expect := []string{"192.168.0.100", "192.168.0.101",
"192.168.0.102", "192.168.0.103"}
if !reflect.DeepEqual(out, expect) {
t.Fatalf("bad: %v", out)
}
}
// COMPAT(0.11): Remove in 0.11
func TestNetworkIndex_AssignTaskNetwork_Old(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
n := &Node{
Resources: &Resources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/30",
MBits: 1000,
},
},
},
Reserved: &Resources{
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
ReservedPorts: []Port{{"ssh", 22, 0, ""}},
MBits: 1,
},
},
},
}
idx.SetNode(n)
allocs := []*Allocation{
{
TaskResources: map[string]*Resources{
"web": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 20,
ReservedPorts: []Port{{"one", 8000, 0, ""}, {"two", 9000, 0, ""}},
},
},
},
},
},
{
TaskResources: map[string]*Resources{
"api": {
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 50,
ReservedPorts: []Port{{"main", 10000, 0, ""}},
},
},
},
},
},
}
idx.AddAllocs(allocs)
// Ask for a reserved port
ask := &NetworkResource{
ReservedPorts: []Port{{"main", 8000, 0, ""}},
}
offer, err := idx.AssignTaskNetwork(ask)
if err != nil {
t.Fatalf("err: %v", err)
}
if offer == nil {
t.Fatalf("bad")
}
if offer.IP != "192.168.0.101" {
t.Fatalf("bad: %#v", offer)
}
rp := Port{"main", 8000, 0, ""}
if len(offer.ReservedPorts) != 1 || offer.ReservedPorts[0] != rp {
t.Fatalf("bad: %#v", offer)
}
// Ask for dynamic ports
ask = &NetworkResource{
DynamicPorts: []Port{{"http", 0, 80, ""}, {"https", 0, 443, ""}, {"admin", 0, 8080, ""}},
}
offer, err = idx.AssignTaskNetwork(ask)
if err != nil {
t.Fatalf("err: %v", err)
}
if offer == nil {
t.Fatalf("bad")
}
if offer.IP != "192.168.0.100" {
t.Fatalf("bad: %#v", offer)
}
if len(offer.DynamicPorts) != 3 {
t.Fatalf("There should be three dynamic ports")
}
for _, port := range offer.DynamicPorts {
if port.Value == 0 {
t.Fatalf("Dynamic Port: %v should have been assigned a host port", port.Label)
}
}
// Ask for reserved + dynamic ports
ask = &NetworkResource{
ReservedPorts: []Port{{"main", 2345, 0, ""}},
DynamicPorts: []Port{{"http", 0, 80, ""}, {"https", 0, 443, ""}, {"admin", 0, 8080, ""}},
}
offer, err = idx.AssignTaskNetwork(ask)
if err != nil {
t.Fatalf("err: %v", err)
}
if offer == nil {
t.Fatalf("bad")
}
if offer.IP != "192.168.0.100" {
t.Fatalf("bad: %#v", offer)
}
rp = Port{"main", 2345, 0, ""}
if len(offer.ReservedPorts) != 1 || offer.ReservedPorts[0] != rp {
t.Fatalf("bad: %#v", offer)
}
// Ask for too much bandwidth
ask = &NetworkResource{
MBits: 1000,
}
offer, err = idx.AssignTaskNetwork(ask)
if err.Error() != "bandwidth exceeded" {
t.Fatalf("err: %v", err)
}
if offer != nil {
t.Fatalf("bad")
}
}
// COMPAT(0.11): Remove in 0.11
// This test ensures that even with a small domain of available ports we are
// able to make a dynamic port allocation.
func TestNetworkIndex_AssignTaskNetwork_Dynamic_Contention_Old(t *testing.T) {
ci.Parallel(t)
// Create a node that only has one free port
idx := NewNetworkIndex()
n := &Node{
Resources: &Resources{
Networks: []*NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/32",
MBits: 1000,
},
},
},
Reserved: &Resources{
Networks: []*NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
MBits: 1,
},
},
},
}
for i := idx.MinDynamicPort; i < idx.MaxDynamicPort; i++ {
n.Reserved.Networks[0].ReservedPorts = append(n.Reserved.Networks[0].ReservedPorts, Port{Value: i})
}
idx.SetNode(n)
// Ask for dynamic ports
ask := &NetworkResource{
DynamicPorts: []Port{{"http", 0, 80, ""}},
}
offer, err := idx.AssignTaskNetwork(ask)
if err != nil {
t.Fatalf("err: %v", err)
}
if offer == nil {
t.Fatalf("bad")
}
if offer.IP != "192.168.0.100" {
t.Fatalf("bad: %#v", offer)
}
if len(offer.DynamicPorts) != 1 {
t.Fatalf("There should be three dynamic ports")
}
if p := offer.DynamicPorts[0].Value; p != idx.MaxDynamicPort {
t.Fatalf("Dynamic Port: should have been assigned %d; got %d", p, idx.MaxDynamicPort)
}
}
func TestIntContains(t *testing.T) {
ci.Parallel(t)
l := []int{1, 2, 10, 20}
if isPortReserved(l, 50) {
t.Fatalf("bad")
}
if !isPortReserved(l, 20) {
t.Fatalf("bad")
}
if !isPortReserved(l, 1) {
t.Fatalf("bad")
}
}
func TestNetworkIndex_SetNode_HostNets(t *testing.T) {
ci.Parallel(t)
idx := NewNetworkIndex()
n := &Node{
NodeResources: &NodeResources{
Networks: []*NetworkResource{
// As of Nomad v1.3 bridge networks get
// registered with only their mode set.
{
Mode: "bridge",
},
// Localhost (agent interface)
{
CIDR: "127.0.0.1/32",
Device: "lo",
IP: "127.0.0.1",
MBits: 1000,
Mode: "host",
},
{
CIDR: "::1/128",
Device: "lo",
IP: "::1",
MBits: 1000,
Mode: "host",
},
// Node.NodeResources.Networks does *not*
// contain host_networks.
},
NodeNetworks: []*NodeNetworkResource{
// As of Nomad v1.3 bridge networks get
// registered with only their mode set.
{
Mode: "bridge",
},
{
Addresses: []NodeNetworkAddress{
{
Address: "127.0.0.1",
Alias: "default",
Family: "ipv4",
},
{
Address: "::1",
Alias: "default",
Family: "ipv6",
},
},
Device: "lo",
Mode: "host",
Speed: 1000,
},
{
Addresses: []NodeNetworkAddress{
{
Address: "192.168.0.1",
Alias: "eth0",
Family: "ipv4",
ReservedPorts: "22",
},
},
Device: "enxaaaaaaaaaaaa",
MacAddress: "aa:aa:aa:aa:aa:aa",
Mode: "host",
Speed: 1000,
},
{
Addresses: []NodeNetworkAddress{
{
Address: "192.168.1.1",
Alias: "eth1",
Family: "ipv4",
ReservedPorts: "80",
},
},
Device: "enxbbbbbbbbbbbb",
MacAddress: "bb:bb:bb:bb:bb:bb",
Mode: "host",
Speed: 1000,
},
},
},
ReservedResources: &NodeReservedResources{
Networks: NodeReservedNetworkResources{
ReservedHostPorts: "22",
},
},
}
require.NoError(t, idx.SetNode(n))
// TaskNetworks should only contain the bridge and agent network
require.Len(t, idx.TaskNetworks, 2)
// Ports should be used across all 4 IPs
require.Equal(t, 4, len(idx.UsedPorts))
// 22 should be reserved on all IPs
require.True(t, idx.UsedPorts["127.0.0.1"].Check(22))
require.True(t, idx.UsedPorts["::1"].Check(22))
require.True(t, idx.UsedPorts["192.168.0.1"].Check(22))
require.True(t, idx.UsedPorts["192.168.1.1"].Check(22))
// 80 should only be reserved on eth1's address
require.False(t, idx.UsedPorts["127.0.0.1"].Check(80))
require.False(t, idx.UsedPorts["::1"].Check(80))
require.False(t, idx.UsedPorts["192.168.0.1"].Check(80))
require.True(t, idx.UsedPorts["192.168.1.1"].Check(80))
}