Merge pull request #14297 from pwaller/move-nat-pkg
Move /nat to /pkg/nat
This commit is contained in:
commit
c3d404f172
4 changed files with 644 additions and 0 deletions
172
nat/nat.go
Normal file
172
nat/nat.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package nat
|
||||
|
||||
// nat is a convenience package for docker's manipulation of strings describing
|
||||
// network ports.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
)
|
||||
|
||||
const (
|
||||
PortSpecTemplate = "ip:hostPort:containerPort"
|
||||
PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort"
|
||||
)
|
||||
|
||||
type PortBinding struct {
|
||||
HostIp string
|
||||
HostPort string
|
||||
}
|
||||
|
||||
type PortMap map[Port][]PortBinding
|
||||
|
||||
type PortSet map[Port]struct{}
|
||||
|
||||
// 80/tcp
|
||||
type Port string
|
||||
|
||||
func NewPort(proto, port string) Port {
|
||||
return Port(fmt.Sprintf("%s/%s", port, proto))
|
||||
}
|
||||
|
||||
func ParsePort(rawPort string) (int, error) {
|
||||
if len(rawPort) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
port, err := strconv.ParseUint(rawPort, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(port), nil
|
||||
}
|
||||
|
||||
func (p Port) Proto() string {
|
||||
proto, _ := SplitProtoPort(string(p))
|
||||
return proto
|
||||
}
|
||||
|
||||
func (p Port) Port() string {
|
||||
_, port := SplitProtoPort(string(p))
|
||||
return port
|
||||
}
|
||||
|
||||
func (p Port) Int() int {
|
||||
port, err := ParsePort(p.Port())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
// Splits a port in the format of proto/port
|
||||
func SplitProtoPort(rawPort string) (string, string) {
|
||||
parts := strings.Split(rawPort, "/")
|
||||
l := len(parts)
|
||||
if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
if l == 1 {
|
||||
return "tcp", rawPort
|
||||
}
|
||||
if len(parts[1]) == 0 {
|
||||
return "tcp", parts[0]
|
||||
}
|
||||
return parts[1], parts[0]
|
||||
}
|
||||
|
||||
func validateProto(proto string) bool {
|
||||
for _, availableProto := range []string{"tcp", "udp"} {
|
||||
if availableProto == proto {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// We will receive port specs in the format of ip:public:private/proto and these need to be
|
||||
// parsed in the internal types
|
||||
func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
|
||||
var (
|
||||
exposedPorts = make(map[Port]struct{}, len(ports))
|
||||
bindings = make(map[Port][]PortBinding)
|
||||
)
|
||||
|
||||
for _, rawPort := range ports {
|
||||
proto := "tcp"
|
||||
|
||||
if i := strings.LastIndex(rawPort, "/"); i != -1 {
|
||||
proto = rawPort[i+1:]
|
||||
rawPort = rawPort[:i]
|
||||
}
|
||||
if !strings.Contains(rawPort, ":") {
|
||||
rawPort = fmt.Sprintf("::%s", rawPort)
|
||||
} else if len(strings.Split(rawPort, ":")) == 2 {
|
||||
rawPort = fmt.Sprintf(":%s", rawPort)
|
||||
}
|
||||
|
||||
parts, err := parsers.PartParser(PortSpecTemplate, rawPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
containerPort = parts["containerPort"]
|
||||
rawIp = parts["ip"]
|
||||
hostPort = parts["hostPort"]
|
||||
)
|
||||
|
||||
if rawIp != "" && net.ParseIP(rawIp) == nil {
|
||||
return nil, nil, fmt.Errorf("Invalid ip address: %s", rawIp)
|
||||
}
|
||||
if containerPort == "" {
|
||||
return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
|
||||
}
|
||||
|
||||
startPort, endPort, err := parsers.ParsePortRange(containerPort)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
|
||||
}
|
||||
|
||||
var startHostPort, endHostPort uint64 = 0, 0
|
||||
if len(hostPort) > 0 {
|
||||
startHostPort, endHostPort, err = parsers.ParsePortRange(hostPort)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
|
||||
}
|
||||
}
|
||||
|
||||
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
|
||||
return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
|
||||
}
|
||||
|
||||
if !validateProto(strings.ToLower(proto)) {
|
||||
return nil, nil, fmt.Errorf("Invalid proto: %s", proto)
|
||||
}
|
||||
|
||||
for i := uint64(0); i <= (endPort - startPort); i++ {
|
||||
containerPort = strconv.FormatUint(startPort+i, 10)
|
||||
if len(hostPort) > 0 {
|
||||
hostPort = strconv.FormatUint(startHostPort+i, 10)
|
||||
}
|
||||
port := NewPort(strings.ToLower(proto), containerPort)
|
||||
if _, exists := exposedPorts[port]; !exists {
|
||||
exposedPorts[port] = struct{}{}
|
||||
}
|
||||
|
||||
binding := PortBinding{
|
||||
HostIp: rawIp,
|
||||
HostPort: hostPort,
|
||||
}
|
||||
bslice, exists := bindings[port]
|
||||
if !exists {
|
||||
bslice = []PortBinding{}
|
||||
}
|
||||
bindings[port] = append(bslice, binding)
|
||||
}
|
||||
}
|
||||
return exposedPorts, bindings, nil
|
||||
}
|
293
nat/nat_test.go
Normal file
293
nat/nat_test.go
Normal file
|
@ -0,0 +1,293 @@
|
|||
package nat
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParsePort(t *testing.T) {
|
||||
var (
|
||||
p int
|
||||
err error
|
||||
)
|
||||
|
||||
p, err = ParsePort("1234")
|
||||
|
||||
if err != nil || p != 1234 {
|
||||
t.Fatal("Parsing '1234' did not succeed")
|
||||
}
|
||||
|
||||
// FIXME currently this is a valid port. I don't think it should be.
|
||||
// I'm leaving this test commented out until we make a decision.
|
||||
// - erikh
|
||||
|
||||
/*
|
||||
p, err = ParsePort("0123")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Successfully parsed port '0123' to '123'")
|
||||
}
|
||||
*/
|
||||
|
||||
p, err = ParsePort("asdf")
|
||||
|
||||
if err == nil || p != 0 {
|
||||
t.Fatal("Parsing port 'asdf' succeeded")
|
||||
}
|
||||
|
||||
p, err = ParsePort("1asdf")
|
||||
|
||||
if err == nil || p != 0 {
|
||||
t.Fatal("Parsing port '1asdf' succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPort(t *testing.T) {
|
||||
p := NewPort("tcp", "1234")
|
||||
|
||||
if string(p) != "1234/tcp" {
|
||||
t.Fatal("tcp, 1234 did not result in the string 1234/tcp")
|
||||
}
|
||||
|
||||
if p.Proto() != "tcp" {
|
||||
t.Fatal("protocol was not tcp")
|
||||
}
|
||||
|
||||
if p.Port() != "1234" {
|
||||
t.Fatal("port string value was not 1234")
|
||||
}
|
||||
|
||||
if p.Int() != 1234 {
|
||||
t.Fatal("port int value was not 1234")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitProtoPort(t *testing.T) {
|
||||
var (
|
||||
proto string
|
||||
port string
|
||||
)
|
||||
|
||||
proto, port = SplitProtoPort("1234/tcp")
|
||||
|
||||
if proto != "tcp" || port != "1234" {
|
||||
t.Fatal("Could not split 1234/tcp properly")
|
||||
}
|
||||
|
||||
proto, port = SplitProtoPort("")
|
||||
|
||||
if proto != "" || port != "" {
|
||||
t.Fatal("parsing an empty string yielded surprising results", proto, port)
|
||||
}
|
||||
|
||||
proto, port = SplitProtoPort("1234")
|
||||
|
||||
if proto != "tcp" || port != "1234" {
|
||||
t.Fatal("tcp is not the default protocol for portspec '1234'", proto, port)
|
||||
}
|
||||
|
||||
proto, port = SplitProtoPort("1234/")
|
||||
|
||||
if proto != "tcp" || port != "1234" {
|
||||
t.Fatal("parsing '1234/' yielded:" + port + "/" + proto)
|
||||
}
|
||||
|
||||
proto, port = SplitProtoPort("/tcp")
|
||||
|
||||
if proto != "" || port != "" {
|
||||
t.Fatal("parsing '/tcp' yielded:" + port + "/" + proto)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePortSpecs(t *testing.T) {
|
||||
var (
|
||||
portMap map[Port]struct{}
|
||||
bindingMap map[Port][]PortBinding
|
||||
err error
|
||||
)
|
||||
|
||||
portMap, bindingMap, err = ParsePortSpecs([]string{"1234/tcp", "2345/udp"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("1234/tcp")]; !ok {
|
||||
t.Fatal("1234/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("2345/udp")]; !ok {
|
||||
t.Fatal("2345/udp was not parsed properly")
|
||||
}
|
||||
|
||||
for portspec, bindings := range bindingMap {
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIp != "" {
|
||||
t.Fatalf("HostIp should not be set for %s", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != "" {
|
||||
t.Fatalf("HostPort should not be set for %s", portspec)
|
||||
}
|
||||
}
|
||||
|
||||
portMap, bindingMap, err = ParsePortSpecs([]string{"1234:1234/tcp", "2345:2345/udp"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("1234/tcp")]; !ok {
|
||||
t.Fatal("1234/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("2345/udp")]; !ok {
|
||||
t.Fatal("2345/udp was not parsed properly")
|
||||
}
|
||||
|
||||
for portspec, bindings := range bindingMap {
|
||||
_, port := SplitProtoPort(string(portspec))
|
||||
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIp != "" {
|
||||
t.Fatalf("HostIp should not be set for %s", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != port {
|
||||
t.Fatalf("HostPort should be %s for %s", port, portspec)
|
||||
}
|
||||
}
|
||||
|
||||
portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234:1234/tcp", "0.0.0.0:2345:2345/udp"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("1234/tcp")]; !ok {
|
||||
t.Fatal("1234/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("2345/udp")]; !ok {
|
||||
t.Fatal("2345/udp was not parsed properly")
|
||||
}
|
||||
|
||||
for portspec, bindings := range bindingMap {
|
||||
_, port := SplitProtoPort(string(portspec))
|
||||
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIp != "0.0.0.0" {
|
||||
t.Fatalf("HostIp is not 0.0.0.0 for %s", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != port {
|
||||
t.Fatalf("HostPort should be %s for %s", port, portspec)
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err = ParsePortSpecs([]string{"localhost:1234:1234/tcp"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("Received no error while trying to parse a hostname instead of ip")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePortSpecsWithRange(t *testing.T) {
|
||||
var (
|
||||
portMap map[Port]struct{}
|
||||
bindingMap map[Port][]PortBinding
|
||||
err error
|
||||
)
|
||||
|
||||
portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236/tcp", "2345-2347/udp"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("1235/tcp")]; !ok {
|
||||
t.Fatal("1234/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("2346/udp")]; !ok {
|
||||
t.Fatal("2345/udp was not parsed properly")
|
||||
}
|
||||
|
||||
for portspec, bindings := range bindingMap {
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIp != "" {
|
||||
t.Fatalf("HostIp should not be set for %s", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != "" {
|
||||
t.Fatalf("HostPort should not be set for %s", portspec)
|
||||
}
|
||||
}
|
||||
|
||||
portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236:1234-1236/tcp", "2345-2347:2345-2347/udp"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("1235/tcp")]; !ok {
|
||||
t.Fatal("1234/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("2346/udp")]; !ok {
|
||||
t.Fatal("2345/udp was not parsed properly")
|
||||
}
|
||||
|
||||
for portspec, bindings := range bindingMap {
|
||||
_, port := SplitProtoPort(string(portspec))
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIp != "" {
|
||||
t.Fatalf("HostIp should not be set for %s", portspec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != port {
|
||||
t.Fatalf("HostPort should be %s for %s", port, portspec)
|
||||
}
|
||||
}
|
||||
|
||||
portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234-1236:1234-1236/tcp", "0.0.0.0:2345-2347:2345-2347/udp"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("1235/tcp")]; !ok {
|
||||
t.Fatal("1234/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[Port("2346/udp")]; !ok {
|
||||
t.Fatal("2345/udp was not parsed properly")
|
||||
}
|
||||
|
||||
for portspec, bindings := range bindingMap {
|
||||
_, port := SplitProtoPort(string(portspec))
|
||||
if len(bindings) != 1 || bindings[0].HostIp != "0.0.0.0" || bindings[0].HostPort != port {
|
||||
t.Fatalf("Expect single binding to port %s but found %s", port, bindings)
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err = ParsePortSpecs([]string{"localhost:1234-1236:1234-1236/tcp"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("Received no error while trying to parse a hostname instead of ip")
|
||||
}
|
||||
}
|
94
nat/sort.go
Normal file
94
nat/sort.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package nat
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type portSorter struct {
|
||||
ports []Port
|
||||
by func(i, j Port) bool
|
||||
}
|
||||
|
||||
func (s *portSorter) Len() int {
|
||||
return len(s.ports)
|
||||
}
|
||||
|
||||
func (s *portSorter) Swap(i, j int) {
|
||||
s.ports[i], s.ports[j] = s.ports[j], s.ports[i]
|
||||
}
|
||||
|
||||
func (s *portSorter) Less(i, j int) bool {
|
||||
ip := s.ports[i]
|
||||
jp := s.ports[j]
|
||||
|
||||
return s.by(ip, jp)
|
||||
}
|
||||
|
||||
func Sort(ports []Port, predicate func(i, j Port) bool) {
|
||||
s := &portSorter{ports, predicate}
|
||||
sort.Sort(s)
|
||||
}
|
||||
|
||||
type portMapEntry struct {
|
||||
port Port
|
||||
binding PortBinding
|
||||
}
|
||||
|
||||
type portMapSorter []portMapEntry
|
||||
|
||||
func (s portMapSorter) Len() int { return len(s) }
|
||||
func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// sort the port so that the order is:
|
||||
// 1. port with larger specified bindings
|
||||
// 2. larger port
|
||||
// 3. port with tcp protocol
|
||||
func (s portMapSorter) Less(i, j int) bool {
|
||||
pi, pj := s[i].port, s[j].port
|
||||
hpi, hpj := toInt(s[i].binding.HostPort), toInt(s[j].binding.HostPort)
|
||||
return hpi > hpj || pi.Int() > pj.Int() || (pi.Int() == pj.Int() && strings.ToLower(pi.Proto()) == "tcp")
|
||||
}
|
||||
|
||||
// SortPortMap sorts the list of ports and their respected mapping. The ports
|
||||
// will explicit HostPort will be placed first.
|
||||
func SortPortMap(ports []Port, bindings PortMap) {
|
||||
s := portMapSorter{}
|
||||
for _, p := range ports {
|
||||
if binding, ok := bindings[p]; ok {
|
||||
for _, b := range binding {
|
||||
s = append(s, portMapEntry{port: p, binding: b})
|
||||
}
|
||||
bindings[p] = []PortBinding{}
|
||||
} else {
|
||||
s = append(s, portMapEntry{port: p})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(s)
|
||||
var (
|
||||
i int
|
||||
pm = make(map[Port]struct{})
|
||||
)
|
||||
// reorder ports
|
||||
for _, entry := range s {
|
||||
if _, ok := pm[entry.port]; !ok {
|
||||
ports[i] = entry.port
|
||||
pm[entry.port] = struct{}{}
|
||||
i++
|
||||
}
|
||||
// reorder bindings for this port
|
||||
if _, ok := bindings[entry.port]; ok {
|
||||
bindings[entry.port] = append(bindings[entry.port], entry.binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toInt(s string) int64 {
|
||||
i, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
i = 0
|
||||
}
|
||||
return i
|
||||
}
|
85
nat/sort_test.go
Normal file
85
nat/sort_test.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package nat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSortUniquePorts(t *testing.T) {
|
||||
ports := []Port{
|
||||
Port("6379/tcp"),
|
||||
Port("22/tcp"),
|
||||
}
|
||||
|
||||
Sort(ports, func(ip, jp Port) bool {
|
||||
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
|
||||
})
|
||||
|
||||
first := ports[0]
|
||||
if fmt.Sprint(first) != "22/tcp" {
|
||||
t.Log(fmt.Sprint(first))
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortSamePortWithDifferentProto(t *testing.T) {
|
||||
ports := []Port{
|
||||
Port("8888/tcp"),
|
||||
Port("8888/udp"),
|
||||
Port("6379/tcp"),
|
||||
Port("6379/udp"),
|
||||
}
|
||||
|
||||
Sort(ports, func(ip, jp Port) bool {
|
||||
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
|
||||
})
|
||||
|
||||
first := ports[0]
|
||||
if fmt.Sprint(first) != "6379/tcp" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortPortMap(t *testing.T) {
|
||||
ports := []Port{
|
||||
Port("22/tcp"),
|
||||
Port("22/udp"),
|
||||
Port("8000/tcp"),
|
||||
Port("6379/tcp"),
|
||||
Port("9999/tcp"),
|
||||
}
|
||||
|
||||
portMap := PortMap{
|
||||
Port("22/tcp"): []PortBinding{
|
||||
{},
|
||||
},
|
||||
Port("8000/tcp"): []PortBinding{
|
||||
{},
|
||||
},
|
||||
Port("6379/tcp"): []PortBinding{
|
||||
{},
|
||||
{HostIp: "0.0.0.0", HostPort: "32749"},
|
||||
},
|
||||
Port("9999/tcp"): []PortBinding{
|
||||
{HostIp: "0.0.0.0", HostPort: "40000"},
|
||||
},
|
||||
}
|
||||
|
||||
SortPortMap(ports, portMap)
|
||||
if !reflect.DeepEqual(ports, []Port{
|
||||
Port("9999/tcp"),
|
||||
Port("6379/tcp"),
|
||||
Port("8000/tcp"),
|
||||
Port("22/tcp"),
|
||||
Port("22/udp"),
|
||||
}) {
|
||||
t.Errorf("failed to prioritize port with explicit mappings, got %v", ports)
|
||||
}
|
||||
if pm := portMap[Port("6379/tcp")]; !reflect.DeepEqual(pm, []PortBinding{
|
||||
{HostIp: "0.0.0.0", HostPort: "32749"},
|
||||
{},
|
||||
}) {
|
||||
t.Errorf("failed to prioritize bindings with explicit mappings, got %v", pm)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue