package generate import ( "testing" "github.com/containers/common/libnetwork/types" "github.com/stretchr/testify/assert" ) func TestParsePortMappingWithHostPort(t *testing.T) { tests := []struct { name string arg []types.PortMapping arg2 map[uint16][]string want []types.PortMapping }{ { name: "no ports", arg: nil, want: nil, }, { name: "one tcp port", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 1, }, }, }, { name: "one tcp port no proto", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 1, }, }, }, { name: "one udp port", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "udp", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "udp", Range: 1, }, }, }, { name: "one sctp port", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "sctp", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "sctp", Range: 1, }, }, }, { name: "one port two protocols", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp,udp", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 1, }, { HostPort: 8080, ContainerPort: 80, Protocol: "udp", Range: 1, }, }, }, { name: "one port three protocols", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp,udp,sctp", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 1, }, { HostPort: 8080, ContainerPort: 80, Protocol: "udp", Range: 1, }, { HostPort: 8080, ContainerPort: 80, Protocol: "sctp", Range: 1, }, }, }, { name: "one port with range 1", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 1, }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 1, }, }, }, { name: "one port with range 5", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 5, }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 5, }, }, }, { name: "two ports joined", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", }, { HostPort: 8081, ContainerPort: 81, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 2, }, }, }, { name: "two ports joined with range", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 2, }, { HostPort: 8081, ContainerPort: 81, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 2, }, }, }, { name: "two ports with no overlapping range", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 10, }, { HostPort: 9090, ContainerPort: 9090, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 9090, ContainerPort: 9090, Protocol: "tcp", Range: 1, }, { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 10, }, }, }, { name: "four ports with two overlapping ranges", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 10, }, { HostPort: 8085, ContainerPort: 85, Protocol: "tcp", Range: 10, }, { HostPort: 100, ContainerPort: 5, Protocol: "tcp", }, { HostPort: 101, ContainerPort: 6, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 15, }, { HostPort: 100, ContainerPort: 5, Protocol: "tcp", Range: 2, }, }, }, { name: "two overlapping ranges", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 10, }, { HostPort: 8085, ContainerPort: 85, Protocol: "tcp", Range: 2, }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 10, }, }, }, { name: "four overlapping ranges", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 10, }, { HostPort: 8085, ContainerPort: 85, Protocol: "tcp", Range: 2, }, { HostPort: 8090, ContainerPort: 90, Protocol: "tcp", Range: 7, }, { HostPort: 8095, ContainerPort: 95, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 17, }, }, }, { name: "one port range overlaps 5 ports", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Range: 20, }, { HostPort: 8085, ContainerPort: 85, Range: 2, }, { HostPort: 8090, ContainerPort: 90, }, { HostPort: 8095, ContainerPort: 95, }, { HostPort: 8096, ContainerPort: 96, }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", Range: 20, }, }, }, { name: "different host ip same port", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", HostIP: "192.168.1.1", }, { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", HostIP: "192.168.2.1", }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", HostIP: "192.168.1.1", Range: 1, }, { HostPort: 8080, ContainerPort: 80, Protocol: "tcp", HostIP: "192.168.2.1", Range: 1, }, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { got, err := ParsePortMapping(tt.arg, tt.arg2) assert.NoError(t, err, "error is not nil") // use ElementsMatch instead of Equal because the order is not consistent assert.ElementsMatch(t, tt.want, got, "got unexpected port mapping") }) } } func TestParsePortMappingWithoutHostPort(t *testing.T) { tests := []struct { name string arg []types.PortMapping arg2 map[uint16][]string want []types.PortMapping }{ { name: "one tcp port", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 1, }, }, }, { name: "one port with two protocols", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp,udp", }, }, want: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 80, Protocol: "udp", Range: 1, }, }, }, { name: "same port twice", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", }, { HostPort: 0, ContainerPort: 80, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 1, }, }, }, { name: "neighbor ports are not joined", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", }, { HostPort: 0, ContainerPort: 81, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 81, Protocol: "tcp", Range: 1, }, }, }, { name: "overlapping range ports are joined", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 2, }, { HostPort: 0, ContainerPort: 81, Protocol: "tcp", }, }, want: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 2, }, }, }, { name: "four overlapping range ports are joined", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 3, }, { HostPort: 0, ContainerPort: 81, Protocol: "tcp", }, { HostPort: 0, ContainerPort: 82, Protocol: "tcp", Range: 10, }, { HostPort: 0, ContainerPort: 90, Protocol: "tcp", Range: 5, }, }, want: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 15, }, }, }, { name: "expose one tcp port", arg2: map[uint16][]string{ 8080: {"tcp"}, }, want: []types.PortMapping{ { HostPort: 0, ContainerPort: 8080, Protocol: "tcp", Range: 1, }, }, }, { name: "expose already defined port", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 8080, Protocol: "tcp", }, }, arg2: map[uint16][]string{ 8080: {"tcp"}, }, want: []types.PortMapping{ { HostPort: 0, ContainerPort: 8080, Protocol: "tcp", Range: 1, }, }, }, { name: "expose different proto", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 8080, Protocol: "tcp", }, }, arg2: map[uint16][]string{ 8080: {"udp"}, }, want: []types.PortMapping{ { HostPort: 0, ContainerPort: 8080, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 8080, Protocol: "udp", Range: 1, }, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { got, err := ParsePortMapping(tt.arg, tt.arg2) assert.NoError(t, err, "error is not nil") // because we always get random host ports when it is set to 0 we cannot check that exactly // check if it is not 0 and set to to 0 afterwards for i := range got { assert.Greater(t, got[i].HostPort, uint16(0), "host port is zero") got[i].HostPort = 0 } // use ElementsMatch instead of Equal because the order is not consistent assert.ElementsMatch(t, tt.want, got, "got unexpected port mapping") }) } } func TestParsePortMappingMixedHostPort(t *testing.T) { tests := []struct { name string arg []types.PortMapping want []types.PortMapping resetHostPorts []int }{ { name: "two ports one without a hostport set", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, }, { HostPort: 8080, ContainerPort: 8080, }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 8080, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 1, }, }, resetHostPorts: []int{1}, }, { name: "two ports one without a hostport set, inverted order", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 8080, }, { HostPort: 0, ContainerPort: 80, }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 8080, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 1, }, }, resetHostPorts: []int{1}, }, { name: "three ports without host ports, one with a hostport set, , inverted order", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, }, { HostPort: 0, ContainerPort: 85, }, { HostPort: 0, ContainerPort: 90, }, { HostPort: 8080, ContainerPort: 8080, }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 8080, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 85, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 90, Protocol: "tcp", Range: 1, }, }, resetHostPorts: []int{1, 2, 3}, }, { name: "three ports without host ports, one with a hostport set", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 8080, }, { HostPort: 0, ContainerPort: 90, }, { HostPort: 0, ContainerPort: 85, }, { HostPort: 0, ContainerPort: 80, }, }, want: []types.PortMapping{ { HostPort: 8080, ContainerPort: 8080, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 80, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 85, Protocol: "tcp", Range: 1, }, { HostPort: 0, ContainerPort: 90, Protocol: "tcp", Range: 1, }, }, resetHostPorts: []int{1, 2, 3}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { got, err := ParsePortMapping(tt.arg, nil) assert.NoError(t, err, "error is not nil") // because we always get random host ports when it is set to 0 we cannot check that exactly // use resetHostPorts to know which port element is 0 for _, num := range tt.resetHostPorts { assert.Greater(t, got[num].HostPort, uint16(0), "host port is zero") got[num].HostPort = 0 } assert.Equal(t, tt.want, got, "got unexpected port mapping") }) } } func TestParsePortMappingError(t *testing.T) { tests := []struct { name string arg []types.PortMapping err string }{ { name: "container port is 0", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 0, Protocol: "tcp", }, }, err: "container port number must be non-0", }, { name: "container port range exceeds max", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 65000, Protocol: "tcp", Range: 10000, }, }, err: "container port range exceeds maximum allowable port number", }, { name: "host port range exceeds max", arg: []types.PortMapping{ { HostPort: 60000, ContainerPort: 1, Protocol: "tcp", Range: 10000, }, }, err: "host port range exceeds maximum allowable port number", }, { name: "invalid protocol", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "1", }, }, err: "unrecognized protocol \"1\" in port mapping", }, { name: "invalid protocol 2", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Protocol: "udp,u", }, }, err: "unrecognized protocol \"u\" in port mapping", }, { name: "invalid ip address", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, HostIP: "blah", }, }, err: "invalid IP address \"blah\" in port mapping", }, { name: "invalid overalpping range", arg: []types.PortMapping{ { HostPort: 8080, ContainerPort: 80, Range: 5, }, { HostPort: 8081, ContainerPort: 60, }, }, err: "conflicting port mappings for host port 8081 (protocol tcp)", }, { name: "big port range with host port zero does not fit", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 1, Range: 65535, }, }, err: "failed to find an open port to expose container port 1 with range 65535 on the host", }, { name: "big port range with host port zero does not fit", arg: []types.PortMapping{ { HostPort: 0, ContainerPort: 80, Range: 1, }, { HostPort: 0, ContainerPort: 1000, Range: 64535, }, }, err: "failed to find an open port to expose container port 1000 with range 64535 on the host", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { _, err := ParsePortMapping(tt.arg, nil) assert.EqualError(t, err, tt.err, "error does not match") }) } }