Signed-off-by: Don Kjer <don.kjer@gmail.com>
Changing vendor/src/github.com/docker/libnetwork to match lindenlab/libnetwork custom-host-port-ranges-1.7 branch
| ... | ... |
@@ -729,10 +729,15 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO |
| 729 | 729 |
for i := 0; i < len(binding); i++ {
|
| 730 | 730 |
pbCopy := pb.GetCopy() |
| 731 | 731 |
newP, err := nat.NewPort(nat.SplitProtoPort(binding[i].HostPort)) |
| 732 |
+ var portStart, portEnd int |
|
| 733 |
+ if err == nil {
|
|
| 734 |
+ portStart, portEnd, err = newP.Range() |
|
| 735 |
+ } |
|
| 732 | 736 |
if err != nil {
|
| 733 | 737 |
return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding[i].HostPort, err)
|
| 734 | 738 |
} |
| 735 |
- pbCopy.HostPort = uint16(newP.Int()) |
|
| 739 |
+ pbCopy.HostPort = uint16(portStart) |
|
| 740 |
+ pbCopy.HostPortEnd = uint16(portEnd) |
|
| 736 | 741 |
pbCopy.HostIP = net.ParseIP(binding[i].HostIP) |
| 737 | 742 |
pbList = append(pbList, pbCopy) |
| 738 | 743 |
} |
| ... | ... |
@@ -984,6 +984,7 @@ or override the Dockerfile's exposed defaults: |
| 984 | 984 |
format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort |
| 985 | 985 |
Both hostPort and containerPort can be specified as a range of ports. |
| 986 | 986 |
When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. (e.g., `-p 1234-1236:1234-1236/tcp`) |
| 987 |
+ When specifying a range for hostPort only, the containerPort must not be a range. In this case the container port is published somewhere within the specified hostPort range. (e.g., `-p 1234-1236:1234/tcp`) |
|
| 987 | 988 |
(use 'docker port' to see the actual mapping) |
| 988 | 989 |
--link="" : Add link to another container (<name or id>:alias or <name or id>) |
| 989 | 990 |
|
| ... | ... |
@@ -50,6 +50,14 @@ container: |
| 50 | 50 |
And you saw why this isn't such a great idea because it constrains you to |
| 51 | 51 |
only one container on that specific port. |
| 52 | 52 |
|
| 53 |
+Instead, you may specify a range of host ports to bind a container port to |
|
| 54 |
+that is different than the default *ephemeral port range*: |
|
| 55 |
+ |
|
| 56 |
+ $ docker run -d -p 8000-9000:5000 training/webapp python app.py |
|
| 57 |
+ |
|
| 58 |
+This would bind port 5000 in the container to a randomly available port |
|
| 59 |
+between 8000 and 9000 on the host. |
|
| 60 |
+ |
|
| 53 | 61 |
There are also a few other ways you can configure the `-p` flag. By |
| 54 | 62 |
default the `-p` flag will bind the specified port to all interfaces on |
| 55 | 63 |
the host machine. But you can also specify a binding to a specific |
| ... | ... |
@@ -78,6 +78,81 @@ func (s *DockerSuite) TestPortList(c *check.C) {
|
| 78 | 78 |
} |
| 79 | 79 |
dockerCmd(c, "rm", "-f", ID) |
| 80 | 80 |
|
| 81 |
+ testRange := func() {
|
|
| 82 |
+ // host port ranges used |
|
| 83 |
+ IDs := make([]string, 3) |
|
| 84 |
+ for i := 0; i < 3; i++ {
|
|
| 85 |
+ out, _ = dockerCmd(c, "run", "-d", |
|
| 86 |
+ "-p", "9090-9092:80", |
|
| 87 |
+ "busybox", "top") |
|
| 88 |
+ IDs[i] = strings.TrimSpace(out) |
|
| 89 |
+ |
|
| 90 |
+ out, _ = dockerCmd(c, "port", IDs[i]) |
|
| 91 |
+ |
|
| 92 |
+ if !assertPortList(c, out, []string{
|
|
| 93 |
+ fmt.Sprintf("80/tcp -> 0.0.0.0:%d", 9090+i)}) {
|
|
| 94 |
+ c.Error("Port list is not correct\n", out)
|
|
| 95 |
+ } |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ // test port range exhaustion |
|
| 99 |
+ out, _, err := dockerCmdWithError("run", "-d",
|
|
| 100 |
+ "-p", "9090-9092:80", |
|
| 101 |
+ "busybox", "top") |
|
| 102 |
+ if err == nil {
|
|
| 103 |
+ c.Errorf("Exhausted port range did not return an error. Out: %s", out)
|
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ for i := 0; i < 3; i++ {
|
|
| 107 |
+ dockerCmd(c, "rm", "-f", IDs[i]) |
|
| 108 |
+ } |
|
| 109 |
+ } |
|
| 110 |
+ testRange() |
|
| 111 |
+ // Verify we ran re-use port ranges after they are no longer in use. |
|
| 112 |
+ testRange() |
|
| 113 |
+ |
|
| 114 |
+ // test invalid port ranges |
|
| 115 |
+ for _, invalidRange := range []string{"9090-9089:80", "9090-:80", "-9090:80"} {
|
|
| 116 |
+ out, _, err := dockerCmdWithError("run", "-d",
|
|
| 117 |
+ "-p", invalidRange, |
|
| 118 |
+ "busybox", "top") |
|
| 119 |
+ if err == nil {
|
|
| 120 |
+ c.Errorf("Port range should have returned an error. Out: %s", out)
|
|
| 121 |
+ } |
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ // test host range:container range spec. |
|
| 125 |
+ out, _ = dockerCmd(c, "run", "-d", |
|
| 126 |
+ "-p", "9800-9803:80-83", |
|
| 127 |
+ "busybox", "top") |
|
| 128 |
+ ID = strings.TrimSpace(out) |
|
| 129 |
+ |
|
| 130 |
+ out, _ = dockerCmd(c, "port", ID) |
|
| 131 |
+ |
|
| 132 |
+ if !assertPortList(c, out, []string{
|
|
| 133 |
+ "80/tcp -> 0.0.0.0:9800", |
|
| 134 |
+ "81/tcp -> 0.0.0.0:9801", |
|
| 135 |
+ "82/tcp -> 0.0.0.0:9802", |
|
| 136 |
+ "83/tcp -> 0.0.0.0:9803"}) {
|
|
| 137 |
+ c.Error("Port list is not correct\n", out)
|
|
| 138 |
+ } |
|
| 139 |
+ dockerCmd(c, "rm", "-f", ID) |
|
| 140 |
+ |
|
| 141 |
+ // test mixing protocols in same port range |
|
| 142 |
+ out, _ = dockerCmd(c, "run", "-d", |
|
| 143 |
+ "-p", "8000-8080:80", |
|
| 144 |
+ "-p", "8000-8080:80/udp", |
|
| 145 |
+ "busybox", "top") |
|
| 146 |
+ ID = strings.TrimSpace(out) |
|
| 147 |
+ |
|
| 148 |
+ out, _ = dockerCmd(c, "port", ID) |
|
| 149 |
+ |
|
| 150 |
+ if !assertPortList(c, out, []string{
|
|
| 151 |
+ "80/tcp -> 0.0.0.0:8000", |
|
| 152 |
+ "80/udp -> 0.0.0.0:8000"}) {
|
|
| 153 |
+ c.Error("Port list is not correct\n", out)
|
|
| 154 |
+ } |
|
| 155 |
+ dockerCmd(c, "rm", "-f", ID) |
|
| 81 | 156 |
} |
| 82 | 157 |
|
| 83 | 158 |
func assertPortList(c *check.C, out string, expected []string) bool {
|
| ... | ... |
@@ -34,17 +34,20 @@ type PortSet map[Port]struct{}
|
| 34 | 34 |
// Port is a string containing port number and protocol in the format "80/tcp" |
| 35 | 35 |
type Port string |
| 36 | 36 |
|
| 37 |
-// NewPort creates a new instance of a Port given a protocol and port number |
|
| 37 |
+// NewPort creates a new instance of a Port given a protocol and port number or port range |
|
| 38 | 38 |
func NewPort(proto, port string) (Port, error) {
|
| 39 | 39 |
// Check for parsing issues on "port" now so we can avoid having |
| 40 | 40 |
// to check it later on. |
| 41 | 41 |
|
| 42 |
- portInt, err := ParsePort(port) |
|
| 42 |
+ portStartInt, portEndInt, err := ParsePortRange(port) |
|
| 43 | 43 |
if err != nil {
|
| 44 | 44 |
return "", err |
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 |
- return Port(fmt.Sprintf("%d/%s", portInt, proto)), nil
|
|
| 47 |
+ if portStartInt == portEndInt {
|
|
| 48 |
+ return Port(fmt.Sprintf("%d/%s", portStartInt, proto)), nil
|
|
| 49 |
+ } |
|
| 50 |
+ return Port(fmt.Sprintf("%d-%d/%s", portStartInt, portEndInt, proto)), nil
|
|
| 48 | 51 |
} |
| 49 | 52 |
|
| 50 | 53 |
// ParsePort parses the port number string and returns an int |
| ... | ... |
@@ -59,6 +62,18 @@ func ParsePort(rawPort string) (int, error) {
|
| 59 | 59 |
return int(port), nil |
| 60 | 60 |
} |
| 61 | 61 |
|
| 62 |
+// ParsePortRange parses the port range string and returns start/end ints |
|
| 63 |
+func ParsePortRange(rawPort string) (int, int, error) {
|
|
| 64 |
+ if len(rawPort) == 0 {
|
|
| 65 |
+ return 0, 0, nil |
|
| 66 |
+ } |
|
| 67 |
+ start, end, err := parsers.ParsePortRange(rawPort) |
|
| 68 |
+ if err != nil {
|
|
| 69 |
+ return 0, 0, err |
|
| 70 |
+ } |
|
| 71 |
+ return int(start), int(end), nil |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 62 | 74 |
// Proto returns the protocol of a Port |
| 63 | 75 |
func (p Port) Proto() string {
|
| 64 | 76 |
proto, _ := SplitProtoPort(string(p)) |
| ... | ... |
@@ -84,6 +99,11 @@ func (p Port) Int() int {
|
| 84 | 84 |
return int(port) |
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 |
+// Range returns the start/end port numbers of a Port range as ints |
|
| 88 |
+func (p Port) Range() (int, int, error) {
|
|
| 89 |
+ return ParsePortRange(p.Port()) |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 87 | 92 |
// SplitProtoPort splits a port in the format of proto/port |
| 88 | 93 |
func SplitProtoPort(rawPort string) (string, string) {
|
| 89 | 94 |
parts := strings.Split(rawPort, "/") |
| ... | ... |
@@ -162,7 +182,12 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
|
| 162 | 162 |
} |
| 163 | 163 |
|
| 164 | 164 |
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
|
| 165 |
- return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
|
|
| 165 |
+ // Allow host port range iff containerPort is not a range. |
|
| 166 |
+ // In this case, use the host port range as the dynamic |
|
| 167 |
+ // host port range to allocate into. |
|
| 168 |
+ if endPort != startPort {
|
|
| 169 |
+ return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
|
|
| 170 |
+ } |
|
| 166 | 171 |
} |
| 167 | 172 |
|
| 168 | 173 |
if !validateProto(strings.ToLower(proto)) {
|
| ... | ... |
@@ -174,6 +199,11 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
|
| 174 | 174 |
if len(hostPort) > 0 {
|
| 175 | 175 |
hostPort = strconv.FormatUint(startHostPort+i, 10) |
| 176 | 176 |
} |
| 177 |
+ // Set hostPort to a range only if there is a single container port |
|
| 178 |
+ // and a dynamic host port. |
|
| 179 |
+ if startPort == endPort && startHostPort != endHostPort {
|
|
| 180 |
+ hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10))
|
|
| 181 |
+ } |
|
| 177 | 182 |
port, err := NewPort(strings.ToLower(proto), containerPort) |
| 178 | 183 |
if err != nil {
|
| 179 | 184 |
return nil, nil, err |
| ... | ... |
@@ -41,6 +41,56 @@ func TestParsePort(t *testing.T) {
|
| 41 | 41 |
} |
| 42 | 42 |
} |
| 43 | 43 |
|
| 44 |
+func TestParsePortRange(t *testing.T) {
|
|
| 45 |
+ var ( |
|
| 46 |
+ begin int |
|
| 47 |
+ end int |
|
| 48 |
+ err error |
|
| 49 |
+ ) |
|
| 50 |
+ |
|
| 51 |
+ type TestRange struct {
|
|
| 52 |
+ Range string |
|
| 53 |
+ Begin int |
|
| 54 |
+ End int |
|
| 55 |
+ } |
|
| 56 |
+ validRanges := []TestRange{
|
|
| 57 |
+ {"1234", 1234, 1234},
|
|
| 58 |
+ {"1234-1234", 1234, 1234},
|
|
| 59 |
+ {"1234-1235", 1234, 1235},
|
|
| 60 |
+ {"8000-9000", 8000, 9000},
|
|
| 61 |
+ {"0", 0, 0},
|
|
| 62 |
+ {"0-0", 0, 0},
|
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ for _, r := range validRanges {
|
|
| 66 |
+ begin, end, err = ParsePortRange(r.Range) |
|
| 67 |
+ |
|
| 68 |
+ if err != nil || begin != r.Begin {
|
|
| 69 |
+ t.Fatalf("Parsing port range '%s' did not succeed. Expected begin %d, got %d", r.Range, r.Begin, begin)
|
|
| 70 |
+ } |
|
| 71 |
+ if err != nil || end != r.End {
|
|
| 72 |
+ t.Fatalf("Parsing port range '%s' did not succeed. Expected end %d, got %d", r.Range, r.End, end)
|
|
| 73 |
+ } |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ invalidRanges := []string{
|
|
| 77 |
+ "asdf", |
|
| 78 |
+ "1asdf", |
|
| 79 |
+ "9000-8000", |
|
| 80 |
+ "9000-", |
|
| 81 |
+ "-8000", |
|
| 82 |
+ "-8000-", |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ for _, r := range invalidRanges {
|
|
| 86 |
+ begin, end, err = ParsePortRange(r) |
|
| 87 |
+ |
|
| 88 |
+ if err == nil || begin != 0 || end != 0 {
|
|
| 89 |
+ t.Fatalf("Parsing port range '%s' succeeded", r)
|
|
| 90 |
+ } |
|
| 91 |
+ } |
|
| 92 |
+} |
|
| 93 |
+ |
|
| 44 | 94 |
func TestPort(t *testing.T) {
|
| 45 | 95 |
p, err := NewPort("tcp", "1234")
|
| 46 | 96 |
|
| ... | ... |
@@ -68,6 +118,20 @@ func TestPort(t *testing.T) {
|
| 68 | 68 |
if err == nil {
|
| 69 | 69 |
t.Fatal("tcp, asd1234 was supposed to fail")
|
| 70 | 70 |
} |
| 71 |
+ |
|
| 72 |
+ p, err = NewPort("tcp", "1234-1230")
|
|
| 73 |
+ if err == nil {
|
|
| 74 |
+ t.Fatal("tcp, 1234-1230 was supposed to fail")
|
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ p, err = NewPort("tcp", "1234-1242")
|
|
| 78 |
+ if err != nil {
|
|
| 79 |
+ t.Fatalf("tcp, 1234-1242 had a parsing issue: %v", err)
|
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ if string(p) != "1234-1242/tcp" {
|
|
| 83 |
+ t.Fatal("tcp, 1234-1242 did not result in the string 1234-1242/tcp")
|
|
| 84 |
+ } |
|
| 71 | 85 |
} |
| 72 | 86 |
|
| 73 | 87 |
func TestSplitProtoPort(t *testing.T) {
|
| ... | ... |
@@ -2,8 +2,9 @@ package nat |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"sort" |
| 5 |
- "strconv" |
|
| 6 | 5 |
"strings" |
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 7 | 8 |
) |
| 8 | 9 |
|
| 9 | 10 |
type portSorter struct {
|
| ... | ... |
@@ -88,8 +89,8 @@ func SortPortMap(ports []Port, bindings PortMap) {
|
| 88 | 88 |
} |
| 89 | 89 |
} |
| 90 | 90 |
|
| 91 |
-func toInt(s string) int64 {
|
|
| 92 |
- i, err := strconv.ParseInt(s, 10, 64) |
|
| 91 |
+func toInt(s string) uint64 {
|
|
| 92 |
+ i, _, err := parsers.ParsePortRange(s) |
|
| 93 | 93 |
if err != nil {
|
| 94 | 94 |
i = 0 |
| 95 | 95 |
} |