diff --git a/container.go b/container.go index 316e0d259..968c5f51d 100644 --- a/container.go +++ b/container.go @@ -16,7 +16,6 @@ import ( "os/exec" "path" "path/filepath" - "strconv" "strings" "syscall" "time" @@ -70,7 +69,8 @@ type Config struct { AttachStdin bool AttachStdout bool AttachStderr bool - PortSpecs []string + PortSpecs []string // Deprecated - Can be in the format of 8080/tcp + ExposedPorts map[Port]struct{} Tty bool // Attach standard streams to a tty, including stdin if it is not closed. OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. @@ -90,6 +90,7 @@ type HostConfig struct { Binds []string ContainerIDFile string LxcConf []KeyValuePair + PortBindings map[Port][]PortBinding } type BindMap struct { @@ -107,6 +108,22 @@ type KeyValuePair struct { Value string } +type PortBinding struct { + HostIp string + HostPort string +} + +// tcp/80 +type Port string + +func (p Port) Proto() string { + return strings.Split(string(p), "/")[0] +} + +func (p Port) Port() string { + return strings.Split(string(p), "/")[1] +} + func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) { cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container") if os.Getenv("TEST") != "" { @@ -220,10 +237,17 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, hostname = parts[0] domainname = parts[1] } + + ports, portBindings, err := parsePortSpecs(flPorts) + if err != nil { + return nil, nil, cmd, err + } + config := &Config{ - Hostname: hostname, + Hostname: *flHostname, Domainname: domainname, - PortSpecs: flPorts, + PortSpecs: nil, // Deprecated + ExposedPorts: ports, User: *flUser, Tty: *flTty, NetworkDisabled: !*flNetwork, @@ -243,10 +267,12 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, Privileged: *flPrivileged, WorkingDir: *flWorkingDir, } + hostConfig := &HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, LxcConf: lxcConf, + PortBindings: portBindings, } if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { @@ -261,36 +287,29 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, return config, hostConfig, cmd, nil } -type PortMapping map[string]string +type PortMapping map[string]string // Deprecated type NetworkSettings struct { IPAddress string IPPrefixLen int Gateway string Bridge string - PortMapping map[string]PortMapping + PortMapping map[string]PortMapping // Deprecated + Ports map[Port][]PortBinding } -// returns a more easy to process description of the port mapping defined in the settings func (settings *NetworkSettings) PortMappingAPI() []APIPort { var mapping []APIPort - for private, public := range settings.PortMapping["Tcp"] { - pubint, _ := strconv.ParseInt(public, 0, 0) - privint, _ := strconv.ParseInt(private, 0, 0) - mapping = append(mapping, APIPort{ - PrivatePort: privint, - PublicPort: pubint, - Type: "tcp", - }) - } - for private, public := range settings.PortMapping["Udp"] { - pubint, _ := strconv.ParseInt(public, 0, 0) - privint, _ := strconv.ParseInt(private, 0, 0) - mapping = append(mapping, APIPort{ - PrivatePort: privint, - PublicPort: pubint, - Type: "udp", - }) + for port, bindings := range settings.Ports { + for _, binding := range bindings { + p, _ := parsePort(port.Port()) + h, _ := parsePort(binding.HostPort) + mapping = append(mapping, APIPort{ + PrivatePort: int64(p), + PublicPort: int64(h), + Type: port.Proto(), + }) + } } return mapping } @@ -580,7 +599,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { if container.runtime.networkManager.disabled { container.Config.NetworkDisabled = true } else { - if err := container.allocateNetwork(); err != nil { + if err := container.allocateNetwork(hostConfig); err != nil { return err } } @@ -868,7 +887,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { return utils.NewBufReader(reader), nil } -func (container *Container) allocateNetwork() error { +func (container *Container) allocateNetwork(hostConfig *HostConfig) error { if container.Config.NetworkDisabled { return nil } @@ -895,36 +914,58 @@ func (container *Container) allocateNetwork() error { } } - var portSpecs []string - if !container.State.Ghost { - portSpecs = container.Config.PortSpecs - } else { - for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend)) + if container.Config.PortSpecs != nil { + if err := migratePortMappings(container.Config); err != nil { + return err } - for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend)) + container.Config.PortSpecs = nil + } + + portSpecs := make(map[Port]struct{}) + bindings := make(map[Port][]PortBinding) + + if !container.State.Ghost { + if container.Config.ExposedPorts != nil { + portSpecs = container.Config.ExposedPorts + } + if hostConfig.PortBindings != nil { + bindings = hostConfig.PortBindings + } + } else { + if container.NetworkSettings.Ports != nil { + for port, binding := range container.NetworkSettings.Ports { + portSpecs[port] = struct{}{} + bindings[port] = binding + } } } - container.NetworkSettings.PortMapping = make(map[string]PortMapping) - container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping) - container.NetworkSettings.PortMapping["Udp"] = make(PortMapping) - for _, spec := range portSpecs { - nat, err := iface.AllocatePort(spec) - if err != nil { - iface.Release() - return err + container.NetworkSettings.PortMapping = nil + + for port := range portSpecs { + binding := bindings[port] + for i := 0; i < len(binding); i++ { + b := binding[i] + nat, err := iface.AllocatePort(port, b) + if err != nil { + iface.Release() + return err + } + utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort) + binding[i] = nat.Binding } - proto := strings.Title(nat.Proto) - backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend) - container.NetworkSettings.PortMapping[proto][backend] = frontend + bindings[port] = binding } + container.SaveHostConfig(hostConfig) + + container.NetworkSettings.Ports = bindings container.network = iface + container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface container.NetworkSettings.IPAddress = iface.IPNet.IP.String() container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size() container.NetworkSettings.Gateway = iface.Gateway.String() + return nil } diff --git a/network.go b/network.go index 029b18a67..8d2bd9392 100644 --- a/network.go +++ b/network.go @@ -214,7 +214,7 @@ type PortMapper struct { defaultIp net.IP } -func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { +func (mapper *PortMapper) Map(port int, backendAddr net.Addr, proxyIp net.IP) error { if _, isTCP := backendAddr.(*net.TCPAddr); isTCP { backendPort := backendAddr.(*net.TCPAddr).Port backendIP := backendAddr.(*net.TCPAddr).IP @@ -224,7 +224,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { } } mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr) - proxy, err := proxy.NewProxy(&net.TCPAddr{IP: mapper.defaultIp, Port: port}, backendAddr) + proxy, err := proxy.NewProxy(&net.TCPAddr{IP: proxyIp, Port: port}, backendAddr) if err != nil { mapper.Unmap(port, "tcp") return err @@ -240,7 +240,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { } } mapper.udpMapping[port] = backendAddr.(*net.UDPAddr) - proxy, err := proxy.NewProxy(&net.UDPAddr{IP: mapper.defaultIp, Port: port}, backendAddr) + proxy, err := proxy.NewProxy(&net.UDPAddr{IP: proxyIp, Port: port}, backendAddr) if err != nil { mapper.Unmap(port, "udp") return err @@ -469,40 +469,56 @@ type NetworkInterface struct { disabled bool } -// Allocate an external TCP port and map it to the interface -func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { +// Allocate an external port and map it to the interface +func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Nat, error) { if iface.disabled { return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME } - nat, err := parseNat(spec) + ip := iface.manager.portMapper.defaultIp + + if binding.HostIp != "" { + ip = net.ParseIP(binding.HostIp) + } else { + binding.HostIp = ip.String() + } + + nat := &Nat{ + Port: port, + Binding: binding, + } + + containerPort, err := parsePort(port.Port()) if err != nil { return nil, err } - if nat.Proto == "tcp" { - extPort, err := iface.manager.tcpPortAllocator.Acquire(nat.Frontend) + hostPort, _ := parsePort(nat.Binding.HostPort) + + if nat.Port.Proto() == "tcp" { + extPort, err := iface.manager.tcpPortAllocator.Acquire(hostPort) if err != nil { return nil, err } - backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend} - if err := iface.manager.portMapper.Map(extPort, backend); err != nil { + + backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort} + if err := iface.manager.portMapper.Map(extPort, backend, ip); err != nil { iface.manager.tcpPortAllocator.Release(extPort) return nil, err } - nat.Frontend = extPort + nat.Binding.HostPort = strconv.Itoa(extPort) } else { - extPort, err := iface.manager.udpPortAllocator.Acquire(nat.Frontend) + extPort, err := iface.manager.udpPortAllocator.Acquire(hostPort) if err != nil { return nil, err } - backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: nat.Backend} - if err := iface.manager.portMapper.Map(extPort, backend); err != nil { + backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort} + if err := iface.manager.portMapper.Map(extPort, backend, ip); err != nil { iface.manager.udpPortAllocator.Release(extPort) return nil, err } - nat.Frontend = extPort + nat.Binding.HostPort = strconv.Itoa(extPort) } iface.extPorts = append(iface.extPorts, nat) @@ -510,83 +526,36 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { } type Nat struct { - Proto string - Frontend int - Backend int + Port Port + Binding PortBinding } -func parseNat(spec string) (*Nat, error) { - var nat Nat - - if strings.Contains(spec, "/") { - specParts := strings.Split(spec, "/") - if len(specParts) != 2 { - return nil, fmt.Errorf("Invalid port format.") - } - proto := specParts[1] - spec = specParts[0] - if proto != "tcp" && proto != "udp" { - return nil, fmt.Errorf("Invalid port format: unknown protocol %v.", proto) - } - nat.Proto = proto - } else { - nat.Proto = "tcp" - } - - if strings.Contains(spec, ":") { - specParts := strings.Split(spec, ":") - if len(specParts) != 2 { - return nil, fmt.Errorf("Invalid port format.") - } - // If spec starts with ':', external and internal ports must be the same. - // This might fail if the requested external port is not available. - var sameFrontend bool - if len(specParts[0]) == 0 { - sameFrontend = true - } else { - front, err := strconv.ParseUint(specParts[0], 10, 16) - if err != nil { - return nil, err - } - nat.Frontend = int(front) - } - back, err := strconv.ParseUint(specParts[1], 10, 16) - if err != nil { - return nil, err - } - nat.Backend = int(back) - if sameFrontend { - nat.Frontend = nat.Backend - } - } else { - port, err := strconv.ParseUint(spec, 10, 16) - if err != nil { - return nil, err - } - nat.Backend = int(port) - } - - return &nat, nil +func (n *Nat) String() string { + return fmt.Sprintf("%s:%d:%d/%s", n.Binding.HostIp, n.Binding.HostPort, n.Port.Port(), n.Port.Proto()) } // Release: Network cleanup - release all resources func (iface *NetworkInterface) Release() { - if iface.disabled { return } for _, nat := range iface.extPorts { - utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend) - if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil { - log.Printf("Unable to unmap port %v/%v: %v", nat.Proto, nat.Frontend, err) + hostPort, err := parsePort(nat.Binding.HostPort) + if err != nil { + log.Printf("Unable to get host port: %s", err) + continue } - if nat.Proto == "tcp" { - if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil { - log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err) + utils.Debugf("Unmaping %s/%s", nat.Port.Proto, nat.Binding.HostPort) + if err := iface.manager.portMapper.Unmap(hostPort, nat.Port.Proto()); err != nil { + log.Printf("Unable to unmap port %s: %s", nat, err) + } + if nat.Port.Proto() == "tcp" { + if err := iface.manager.tcpPortAllocator.Release(hostPort); err != nil { + log.Printf("Unable to release port %s", nat) } - } else if err := iface.manager.udpPortAllocator.Release(nat.Frontend); err != nil { - log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err) + } else if err := iface.manager.udpPortAllocator.Release(hostPort); err != nil { + log.Printf("Unable to release port %s: %s", nat, err) } } diff --git a/network_test.go b/network_test.go index e54785092..de469d93c 100644 --- a/network_test.go +++ b/network_test.go @@ -5,100 +5,100 @@ import ( "testing" ) -func TestParseNat(t *testing.T) { - if nat, err := parseNat("4500"); err == nil { - if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" { - t.Errorf("-p 4500 should produce 0->4500/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat(":4501"); err == nil { - if nat.Frontend != 4501 || nat.Backend != 4501 || nat.Proto != "tcp" { - t.Errorf("-p :4501 should produce 4501->4501/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4502:4503"); err == nil { - if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { - t.Errorf("-p 4502:4503 should produce 4502->4503/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4502:4503/tcp"); err == nil { - if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { - t.Errorf("-p 4502:4503/tcp should produce 4502->4503/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4502:4503/udp"); err == nil { - if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "udp" { - t.Errorf("-p 4502:4503/udp should produce 4502->4503/udp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat(":4503/udp"); err == nil { - if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "udp" { - t.Errorf("-p :4503/udp should produce 4503->4503/udp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat(":4503/tcp"); err == nil { - if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "tcp" { - t.Errorf("-p :4503/tcp should produce 4503->4503/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4503/tcp"); err == nil { - if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "tcp" { - t.Errorf("-p 4503/tcp should produce 0->4503/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4503/udp"); err == nil { - if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "udp" { - t.Errorf("-p 4503/udp should produce 0->4503/udp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if _, err := parseNat("4503/tcpgarbage"); err == nil { - t.Fatal(err) - } - - if _, err := parseNat("4503/tcp/udp"); err == nil { - t.Fatal(err) - } - - if _, err := parseNat("4503/"); err == nil { - t.Fatal(err) - } -} +//func TestParseNat(t *testing.T) { +// if nat, err := parseNat("4500"); err == nil { +// if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" { +// t.Errorf("-p 4500 should produce 0->4500/tcp, got %d->%d/%s", +// nat.Frontend, nat.Backend, nat.Proto) +// } +// } else { +// t.Fatal(err) +// } +// +// if nat, err := parseNat(":4501"); err == nil { +// if nat.Frontend != 4501 || nat.Backend != 4501 || nat.Proto != "tcp" { +// t.Errorf("-p :4501 should produce 4501->4501/tcp, got %d->%d/%s", +// nat.Frontend, nat.Backend, nat.Proto) +// } +// } else { +// t.Fatal(err) +// } +// +// if nat, err := parseNat("4502:4503"); err == nil { +// if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { +// t.Errorf("-p 4502:4503 should produce 4502->4503/tcp, got %d->%d/%s", +// nat.Frontend, nat.Backend, nat.Proto) +// } +// } else { +// t.Fatal(err) +// } +// +// if nat, err := parseNat("4502:4503/tcp"); err == nil { +// if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { +// t.Errorf("-p 4502:4503/tcp should produce 4502->4503/tcp, got %d->%d/%s", +// nat.Frontend, nat.Backend, nat.Proto) +// } +// } else { +// t.Fatal(err) +// } +// +// if nat, err := parseNat("4502:4503/udp"); err == nil { +// if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "udp" { +// t.Errorf("-p 4502:4503/udp should produce 4502->4503/udp, got %d->%d/%s", +// nat.Frontend, nat.Backend, nat.Proto) +// } +// } else { +// t.Fatal(err) +// } +// +// if nat, err := parseNat(":4503/udp"); err == nil { +// if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "udp" { +// t.Errorf("-p :4503/udp should produce 4503->4503/udp, got %d->%d/%s", +// nat.Frontend, nat.Backend, nat.Proto) +// } +// } else { +// t.Fatal(err) +// } +// +// if nat, err := parseNat(":4503/tcp"); err == nil { +// if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "tcp" { +// t.Errorf("-p :4503/tcp should produce 4503->4503/tcp, got %d->%d/%s", +// nat.Frontend, nat.Backend, nat.Proto) +// } +// } else { +// t.Fatal(err) +// } +// +// if nat, err := parseNat("4503/tcp"); err == nil { +// if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "tcp" { +// t.Errorf("-p 4503/tcp should produce 0->4503/tcp, got %d->%d/%s", +// nat.Frontend, nat.Backend, nat.Proto) +// } +// } else { +// t.Fatal(err) +// } +// +// if nat, err := parseNat("4503/udp"); err == nil { +// if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "udp" { +// t.Errorf("-p 4503/udp should produce 0->4503/udp, got %d->%d/%s", +// nat.Frontend, nat.Backend, nat.Proto) +// } +// } else { +// t.Fatal(err) +// } +// +// if _, err := parseNat("4503/tcpgarbage"); err == nil { +// t.Fatal(err) +// } +// +// if _, err := parseNat("4503/tcp/udp"); err == nil { +// t.Fatal(err) +// } +// +// if _, err := parseNat("4503/"); err == nil { +// t.Fatal(err) +// } +//} func TestPortAllocation(t *testing.T) { allocator, err := newPortAllocator() diff --git a/runtime.go b/runtime.go index 349dacd91..cd36e9913 100644 --- a/runtime.go +++ b/runtime.go @@ -171,9 +171,9 @@ func (runtime *Runtime) Register(container *Container) error { if !container.State.Running { close(container.waitLock) } else if !nomonitor { - container.allocateNetwork() - // hostConfig isn't needed here and can be nil - go container.monitor(nil) + hostConfig, _ := container.ReadHostConfig() + container.allocateNetwork(hostConfig) + go container.monitor() } return nil } diff --git a/runtime_test.go b/runtime_test.go index 5cb360915..979f34385 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -284,6 +284,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, port := 5554 var container *Container var strPort string + var p Port for { port += 1 strPort = strconv.Itoa(port) @@ -296,22 +297,33 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) } t.Log("Trying port", strPort) + ep := make(map[Port]struct{}, 1) + p = Port(fmt.Sprintf("%s/%s", proto, strPort)) + ep[p] = struct{}{} + container, err = runtime.Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"sh", "-c", cmd}, - PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, + Image: GetTestImage(runtime).ID, + Cmd: []string{"sh", "-c", cmd}, + PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, + ExposedPorts: ep, }) - if container != nil { - break - } if err != nil { nuke(runtime) t.Fatal(err) } + + if container != nil { + break + } t.Logf("Port %v already in use", strPort) } - hostConfig := &HostConfig{} + hostConfig := &HostConfig{ + PortBindings: make(map[Port][]PortBinding), + } + hostConfig.PortBindings[p] = []PortBinding{ + {}, + } if err := container.Start(hostConfig); err != nil { nuke(runtime) t.Fatal(err) @@ -326,7 +338,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, // Even if the state is running, lets give some time to lxc to spawn the process container.WaitTimeout(500 * time.Millisecond) - strPort = container.NetworkSettings.PortMapping[strings.Title(proto)][strPort] + strPort = container.NetworkSettings.Ports[p][0].HostPort return runtime, container, strPort } diff --git a/utils.go b/utils.go index aed8ffdd7..16fe4da4c 100644 --- a/utils.go +++ b/utils.go @@ -2,6 +2,8 @@ package docker import ( "fmt" + "github.com/dotcloud/docker/utils" + "strconv" "strings" ) @@ -78,20 +80,36 @@ func MergeConfig(userConf, imageConf *Config) { if userConf.CpuShares == 0 { userConf.CpuShares = imageConf.CpuShares } - if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 { - userConf.PortSpecs = imageConf.PortSpecs - } else { - for _, imagePortSpec := range imageConf.PortSpecs { - found := false - imageNat, _ := parseNat(imagePortSpec) - for _, userPortSpec := range userConf.PortSpecs { - userNat, _ := parseNat(userPortSpec) - if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend { - found = true - } + if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 { + userConf.ExposedPorts = imageConf.ExposedPorts + } + + if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 { + if userConf.ExposedPorts == nil { + userConf.ExposedPorts = make(map[Port]struct{}) + } + ports, _, err := parsePortSpecs(userConf.PortSpecs) + if err != nil { + panic(err) + } + for port := range ports { + if _, exists := userConf.ExposedPorts[port]; !exists { + userConf.ExposedPorts[port] = struct{}{} } - if !found { - userConf.PortSpecs = append(userConf.PortSpecs, imagePortSpec) + } + } + if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 { + if userConf.ExposedPorts == nil { + userConf.ExposedPorts = make(map[Port]struct{}) + } + + ports, _, err := parsePortSpecs(imageConf.PortSpecs) + if err != nil { + panic(err) + } + for port := range ports { + if _, exists := userConf.ExposedPorts[port]; !exists { + userConf.ExposedPorts[port] = struct{}{} } } } @@ -167,3 +185,72 @@ func parseLxcOpt(opt string) (string, string, error) { } return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } + +// 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) { + 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 := utils.PartParser("ip:hostPort:containerPort", rawPort) + if err != nil { + return nil, nil, err + } + containerPort := parts["containerPort"] + rawIp := parts["ip"] + hostPort := parts["hostPort"] + + if containerPort == "" { + return nil, nil, fmt.Errorf("No port specified: %s", rawPort) + } + port := Port(fmt.Sprintf("%s/%s", 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 +} + +func parsePort(rawPort string) (int, error) { + port, err := strconv.ParseUint(rawPort, 10, 16) + if err != nil { + return 0, err + } + return int(port), nil +} + +func migratePortMappings(config *Config) error { + if config.PortSpecs != nil { + // We don't have to worry about migrating the bindings to the host + // This is our breaking change + ports, _, err := parsePortSpecs(config.PortSpecs) + if err != nil { + return err + } + config.PortSpecs = nil + config.ExposedPorts = ports + } + return nil +} diff --git a/utils/utils.go b/utils/utils.go index d417690c0..f54811b58 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1021,3 +1021,22 @@ type StatusError struct { func (e *StatusError) Error() string { return fmt.Sprintf("Status: %d", e.Status) } + +func PartParser(template, data string) (map[string]string, error) { + // ip:public:private + templateParts := strings.Split(template, ":") + parts := strings.Split(data, ":") + if len(parts) != len(templateParts) { + return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template) + } + out := make(map[string]string, len(templateParts)) + + for i, t := range templateParts { + value := "" + if len(parts) > i { + value = parts[i] + } + out[t] = value + } + return out, nil +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 9a55e7f62..cc7037ceb 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -421,3 +421,20 @@ func TestDependencyGraph(t *testing.T) { t.Fatalf("Expected [d], found %v instead", res[2]) } } + +func TestParsePortMapping(t *testing.T) { + data := PartParser("ip:public:private", "192.168.1.1:80:8080") + + if len(data) != 3 { + t.FailNow() + } + if data["ip"] != "192.168.1.1" { + t.Fail() + } + if data["public"] != "80" { + t.Fail() + } + if data["private"] != "8080" { + t.Fail() + } +} diff --git a/utils_test.go b/utils_test.go index 2d05a37b9..efce0d572 100644 --- a/utils_test.go +++ b/utils_test.go @@ -233,12 +233,12 @@ func TestMergeConfig(t *testing.T) { } } - if len(configUser.PortSpecs) != 3 { - t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs)) + if len(configUser.ExposedPorts) != 3 { + t.Fatalf("Expected 3 portSpecs, 1111, 2222 and 3333, found %d", len(configUser.PortSpecs)) } - for _, portSpecs := range configUser.PortSpecs { - if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" { - t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs) + for portSpecs := range configUser.ExposedPorts { + if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" { + t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs) } } if len(configUser.Env) != 3 { @@ -264,48 +264,6 @@ func TestMergeConfig(t *testing.T) { } } -func TestMergeConfigPublicPortNotHonored(t *testing.T) { - volumesImage := make(map[string]struct{}) - volumesImage["/test1"] = struct{}{} - volumesImage["/test2"] = struct{}{} - configImage := &Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - PortSpecs: []string{"1111", "2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - Volumes: volumesImage, - } - - volumesUser := make(map[string]struct{}) - volumesUser["/test3"] = struct{}{} - configUser := &Config{ - Dns: []string{"3.3.3.3"}, - PortSpecs: []string{"1111:3333"}, - Env: []string{"VAR2=3", "VAR3=3"}, - Volumes: volumesUser, - } - - MergeConfig(configUser, configImage) - - contains := func(a []string, expect string) bool { - for _, p := range a { - if p == expect { - return true - } - } - return false - } - - if !contains(configUser.PortSpecs, "2222") { - t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs) - t.Fail() - } - - if !contains(configUser.PortSpecs, "1111:3333") { - t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs) - t.Fail() - } -} - func TestParseLxcConfOpt(t *testing.T) { opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}