Browse code

Stable Networking: Keep the same network settings across container restarts.

This change will allocate network settings (IP and public ports) at
container creation rather than start and keep them throughout the
lifetime of the container (i.e. until it gets destroyed) instead of
discarding them when the container is stopped.

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>

Andrea Luzzardi authored on 2014/09/30 09:06:26
Showing 4 changed files
... ...
@@ -552,7 +552,7 @@ func (container *Container) RestoreNetwork() error {
552 552
 	}
553 553
 
554 554
 	// Re-allocate any previously allocated ports.
555
-	for port, _ := range container.NetworkSettings.Ports {
555
+	for port := range container.NetworkSettings.Ports {
556 556
 		if err := container.allocatePort(eng, port, container.NetworkSettings.Ports); err != nil {
557 557
 			return err
558 558
 		}
... ...
@@ -563,8 +563,6 @@ func (container *Container) RestoreNetwork() error {
563 563
 // cleanup releases any network resources allocated to the container along with any rules
564 564
 // around how containers are linked together.  It also unmounts the container's root filesystem.
565 565
 func (container *Container) cleanup() {
566
-	container.ReleaseNetwork()
567
-
568 566
 	// Disable all active links
569 567
 	if container.activeLinks != nil {
570 568
 		for _, link := range container.activeLinks {
... ...
@@ -1008,8 +1006,14 @@ func (container *Container) initializeNetworking() error {
1008 1008
 		container.Config.NetworkDisabled = true
1009 1009
 		return container.buildHostnameAndHostsFiles("127.0.1.1")
1010 1010
 	}
1011
-	if err := container.AllocateNetwork(); err != nil {
1012
-		return err
1011
+	// Backward compatibility:
1012
+	// Network allocation used to be done when containers started, not when they
1013
+	// were created, therefore we might be starting a legacy container that
1014
+	// doesn't have networking.
1015
+	if !container.isNetworkAllocated() {
1016
+		if err := container.AllocateNetwork(); err != nil {
1017
+			return err
1018
+		}
1013 1019
 	}
1014 1020
 	return container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
1015 1021
 }
... ...
@@ -83,6 +83,9 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
83 83
 	if container, err = daemon.newContainer(name, config, img); err != nil {
84 84
 		return nil, nil, err
85 85
 	}
86
+	if err := daemon.Register(container); err != nil {
87
+		return nil, nil, err
88
+	}
86 89
 	if err := daemon.createRootfs(container, img); err != nil {
87 90
 		return nil, nil, err
88 91
 	}
... ...
@@ -90,12 +93,13 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
90 90
 		if err := daemon.setHostConfig(container, hostConfig); err != nil {
91 91
 			return nil, nil, err
92 92
 		}
93
+		// We may only allocate the network if a host config was passed, otherwise we'll miss port mappings.
94
+		if err := container.AllocateNetwork(); err != nil {
95
+			return nil, nil, err
96
+		}
93 97
 	}
94 98
 	if err := container.ToDisk(); err != nil {
95 99
 		return nil, nil, err
96 100
 	}
97
-	if err := daemon.Register(container); err != nil {
98
-		return nil, nil, err
99
-	}
100 101
 	return container, warnings, nil
101 102
 }
... ...
@@ -94,6 +94,8 @@ func (daemon *Daemon) Destroy(container *Container) error {
94 94
 		return err
95 95
 	}
96 96
 
97
+	container.ReleaseNetwork()
98
+
97 99
 	// Deregister the container before removing its directory, to avoid race conditions
98 100
 	daemon.idIndex.Delete(container.ID)
99 101
 	daemon.containers.Delete(container.ID)
... ...
@@ -1868,57 +1868,87 @@ func TestRunMutableNetworkFiles(t *testing.T) {
1868 1868
 	}
1869 1869
 }
1870 1870
 
1871
-func TestRunHostsLinkedContainerUpdate(t *testing.T) {
1872
-	deleteAllContainers()
1873
-	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", "while true; do sleep 1; done"))
1874
-	if err != nil {
1875
-		t.Fatal(err, out)
1876
-	}
1871
+func TestRunStableIPAndPort(t *testing.T) {
1872
+	const nContainers = 2
1873
+	var ids, ips, ports [nContainers]string
1874
+
1875
+	// Setup: Create a couple of containers and collect their IPs and public ports.
1876
+	for i := 0; i < nContainers; i++ {
1877
+		runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "1234", "busybox", "top")
1878
+		out, _, err := runCommandWithOutput(runCmd)
1879
+		if err != nil {
1880
+			t.Fatal(err)
1881
+		}
1882
+		ids[i] = strings.TrimSpace(out)
1883
+		ips[i], err = inspectField(ids[i], "NetworkSettings.IPAddress")
1884
+		errorOut(err, t, out)
1885
+		if ips[i] == "" {
1886
+			t.Fatal("IP allocation failed")
1887
+		}
1877 1888
 
1878
-	// TODO fix docker cp and /etc/hosts
1879
-	out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--link", "c1:c1", "--name", "c2", "busybox", "sh", "-c", "while true;do sleep 1; done"))
1880
-	if err != nil {
1881
-		t.Fatal(err, out)
1889
+		portCmd := exec.Command(dockerBinary, "port", ids[i], "1234")
1890
+		ports[i], _, err = runCommandWithOutput(portCmd)
1891
+		errorOut(err, t, out)
1892
+		if ports[i] == "" {
1893
+			t.Fatal("Port allocation failed")
1894
+		}
1882 1895
 	}
1883 1896
 
1884
-	contID := strings.TrimSpace(out)
1885
-
1886
-	f, err := os.Open(filepath.Join("/var/lib/docker/containers", contID, "hosts"))
1887
-	if err != nil {
1888
-		t.Fatal(err)
1897
+	// Stop them all.
1898
+	for _, id := range ids {
1899
+		cmd := exec.Command(dockerBinary, "stop", id)
1900
+		out, _, err := runCommandWithOutput(cmd)
1901
+		if err != nil {
1902
+			t.Fatal(err, out)
1903
+		}
1889 1904
 	}
1890 1905
 
1891
-	originalContent, err := ioutil.ReadAll(f)
1892
-	f.Close()
1906
+	// Create a new container and ensure it's not getting the IP or port of some stopped container.
1907
+	{
1908
+		runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "1234", "busybox", "top")
1909
+		out, _, err := runCommandWithOutput(runCmd)
1910
+		errorOut(err, t, out)
1893 1911
 
1894
-	if err != nil {
1895
-		t.Fatal(err)
1896
-	}
1912
+		id := strings.TrimSpace(out)
1913
+		ip, err := inspectField(id, "NetworkSettings.IPAddress")
1914
+		errorOut(err, t, out)
1897 1915
 
1898
-	out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "restart", "-t", "0", "c1"))
1899
-	if err != nil {
1900
-		t.Fatal(err, out)
1901
-	}
1916
+		portCmd := exec.Command(dockerBinary, "port", id, "1234")
1917
+		port, _, err := runCommandWithOutput(portCmd)
1918
+		errorOut(err, t, out)
1902 1919
 
1903
-	f, err = os.Open(filepath.Join("/var/lib/docker/containers", contID, "hosts"))
1904
-	if err != nil {
1905
-		t.Fatal(err)
1920
+		for i := range ids {
1921
+			if ip == ips[i] {
1922
+				t.Fatalf("Conflicting IP: %s", ip)
1923
+			}
1924
+			if port == ports[i] {
1925
+				t.Fatalf("Conflicting port: %s", port)
1926
+			}
1927
+		}
1906 1928
 	}
1907 1929
 
1908
-	newContent, err := ioutil.ReadAll(f)
1909
-	f.Close()
1930
+	// Start the containers back, and ensure they are getting the same IPs and ports.
1931
+	for i, id := range ids {
1932
+		runCmd := exec.Command(dockerBinary, "start", id)
1933
+		out, _, err := runCommandWithOutput(runCmd)
1934
+		errorOut(err, t, out)
1910 1935
 
1911
-	if err != nil {
1912
-		t.Fatal(err)
1913
-	}
1936
+		ip, err := inspectField(id, "NetworkSettings.IPAddress")
1937
+		errorOut(err, t, out)
1938
+		portCmd := exec.Command(dockerBinary, "port", ids[i], "1234")
1939
+		port, _, err := runCommandWithOutput(portCmd)
1940
+		errorOut(err, t, out)
1914 1941
 
1915
-	if strings.TrimSpace(string(originalContent)) == strings.TrimSpace(string(newContent)) {
1916
-		t.Fatalf("expected /etc/hosts to be updated, but wasn't")
1942
+		if ips[i] != ip {
1943
+			t.Fatalf("Container started with a different IP: %s != %s", ip, ips[i])
1944
+		}
1945
+		if ports[i] != port {
1946
+			t.Fatalf("Container started with a different port: %s != %s", port, ports[i])
1947
+		}
1917 1948
 	}
1918 1949
 
1919 1950
 	deleteAllContainers()
1920
-
1921
-	logDone("run - /etc/hosts updated in parent when restart")
1951
+	logDone("run - ips and ports must not change")
1922 1952
 }
1923 1953
 
1924 1954
 // Ensure that CIDFile gets deleted if it's empty