Browse code

Merge pull request #8297 from aluzzardi/f-stable-ip

Stable Networking: Keep the same network settings during the entire container lifecycle.

Victor Vieux authored on 2014/10/04 06:19:46
Showing 5 changed files
... ...
@@ -441,7 +441,7 @@ func (container *Container) buildHostnameAndHostsFiles(IP string) error {
441 441
 	return container.buildHostsFiles(IP)
442 442
 }
443 443
 
444
-func (container *Container) allocateNetwork() error {
444
+func (container *Container) AllocateNetwork() (err error) {
445 445
 	mode := container.hostConfig.NetworkMode
446 446
 	if container.Config.NetworkDisabled || !mode.IsPrivate() {
447 447
 		return nil
... ...
@@ -449,7 +449,6 @@ func (container *Container) allocateNetwork() error {
449 449
 
450 450
 	var (
451 451
 		env *engine.Env
452
-		err error
453 452
 		eng = container.daemon.eng
454 453
 	)
455 454
 
... ...
@@ -461,6 +460,15 @@ func (container *Container) allocateNetwork() error {
461 461
 		return err
462 462
 	}
463 463
 
464
+	// Error handling: At this point, the interface is allocated so we have to
465
+	// make sure that it is always released in case of error, otherwise we
466
+	// might leak resources.
467
+	defer func() {
468
+		if err != nil {
469
+			eng.Job("release_interface", container.ID).Run()
470
+		}
471
+	}()
472
+
464 473
 	if container.Config.PortSpecs != nil {
465 474
 		if err := migratePortMappings(container.Config, container.hostConfig); err != nil {
466 475
 			return err
... ...
@@ -511,7 +519,7 @@ func (container *Container) allocateNetwork() error {
511 511
 	return nil
512 512
 }
513 513
 
514
-func (container *Container) releaseNetwork() {
514
+func (container *Container) ReleaseNetwork() {
515 515
 	if container.Config.NetworkDisabled {
516 516
 		return
517 517
 	}
... ...
@@ -521,11 +529,41 @@ func (container *Container) releaseNetwork() {
521 521
 	container.NetworkSettings = &NetworkSettings{}
522 522
 }
523 523
 
524
+func (container *Container) isNetworkAllocated() bool {
525
+	return container.NetworkSettings.IPAddress != ""
526
+}
527
+
528
+func (container *Container) RestoreNetwork() error {
529
+	mode := container.hostConfig.NetworkMode
530
+	// Don't attempt a restore if we previously didn't allocate networking.
531
+	// This might be a legacy container with no network allocated, in which case the
532
+	// allocation will happen once and for all at start.
533
+	if !container.isNetworkAllocated() || container.Config.NetworkDisabled || !mode.IsPrivate() {
534
+		return nil
535
+	}
536
+
537
+	eng := container.daemon.eng
538
+
539
+	// Re-allocate the interface with the same IP and MAC address.
540
+	job := eng.Job("allocate_interface", container.ID)
541
+	job.Setenv("RequestedIP", container.NetworkSettings.IPAddress)
542
+	job.Setenv("RequestedMac", container.NetworkSettings.MacAddress)
543
+	if err := job.Run(); err != nil {
544
+		return err
545
+	}
546
+
547
+	// Re-allocate any previously allocated ports.
548
+	for port := range container.NetworkSettings.Ports {
549
+		if err := container.allocatePort(eng, port, container.NetworkSettings.Ports); err != nil {
550
+			return err
551
+		}
552
+	}
553
+	return nil
554
+}
555
+
524 556
 // cleanup releases any network resources allocated to the container along with any rules
525 557
 // around how containers are linked together.  It also unmounts the container's root filesystem.
526 558
 func (container *Container) cleanup() {
527
-	container.releaseNetwork()
528
-
529 559
 	// Disable all active links
530 560
 	if container.activeLinks != nil {
531 561
 		for _, link := range container.activeLinks {
... ...
@@ -969,8 +1007,14 @@ func (container *Container) initializeNetworking() error {
969 969
 		container.Config.NetworkDisabled = true
970 970
 		return container.buildHostnameAndHostsFiles("127.0.1.1")
971 971
 	}
972
-	if err := container.allocateNetwork(); err != nil {
973
-		return err
972
+	// Backward compatibility:
973
+	// Network allocation used to be done when containers started, not when they
974
+	// were created, therefore we might be starting a legacy container that
975
+	// doesn't have networking.
976
+	if !container.isNetworkAllocated() {
977
+		if err := container.AllocateNetwork(); err != nil {
978
+			return err
979
+		}
974 980
 	}
975 981
 	return container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
976 982
 }
... ...
@@ -1149,7 +1193,6 @@ func (container *Container) allocatePort(eng *engine.Engine, port nat.Port, bind
1149 1149
 			return err
1150 1150
 		}
1151 1151
 		if err := job.Run(); err != nil {
1152
-			eng.Job("release_interface", container.ID).Run()
1153 1152
 			return err
1154 1153
 		}
1155 1154
 		b.HostIp = portEnv.Get("HostIP")
... ...
@@ -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
 }
... ...
@@ -372,6 +372,16 @@ func (daemon *Daemon) restore() error {
372 372
 		registeredContainers = append(registeredContainers, container)
373 373
 	}
374 374
 
375
+	// Restore networking of registered containers.
376
+	// This must be performed prior to any IP allocation, otherwise we might
377
+	// end up giving away an already allocated address.
378
+	for _, container := range registeredContainers {
379
+		if err := container.RestoreNetwork(); err != nil {
380
+			log.Errorf("Failed to restore network for %v: %v", container.Name, err)
381
+			continue
382
+		}
383
+	}
384
+
375 385
 	// check the restart policy on the containers and restart any container with
376 386
 	// the restart policy of "always"
377 387
 	if daemon.config.AutoRestart {
... ...
@@ -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,98 @@ 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)
1871
+func TestRunStableIPAndPort(t *testing.T) {
1872
+	const nContainers = 2
1873
+	var ids, ips, macs, 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
+
1884
+		ips[i], err = inspectField(ids[i], "NetworkSettings.IPAddress")
1885
+		errorOut(err, t, out)
1886
+		if ips[i] == "" {
1887
+			t.Fatal("IP allocation failed")
1888
+		}
1889
+
1890
+		macs[i], err = inspectField(ids[i], "NetworkSettings.MacAddress")
1891
+		errorOut(err, t, out)
1892
+
1893
+		portCmd := exec.Command(dockerBinary, "port", ids[i], "1234")
1894
+		ports[i], _, err = runCommandWithOutput(portCmd)
1895
+		errorOut(err, t, out)
1896
+		if ports[i] == "" {
1897
+			t.Fatal("Port allocation failed")
1898
+		}
1876 1899
 	}
1877 1900
 
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)
1901
+	// Stop them all.
1902
+	for _, id := range ids {
1903
+		cmd := exec.Command(dockerBinary, "stop", id)
1904
+		out, _, err := runCommandWithOutput(cmd)
1905
+		if err != nil {
1906
+			t.Fatal(err, out)
1907
+		}
1882 1908
 	}
1883 1909
 
1884
-	contID := strings.TrimSpace(out)
1910
+	// Create a new container and ensure it's not getting the IP or port of some stopped container.
1911
+	{
1912
+		runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "1234", "busybox", "top")
1913
+		out, _, err := runCommandWithOutput(runCmd)
1914
+		errorOut(err, t, out)
1885 1915
 
1886
-	f, err := os.Open(filepath.Join("/var/lib/docker/containers", contID, "hosts"))
1887
-	if err != nil {
1888
-		t.Fatal(err)
1889
-	}
1916
+		id := strings.TrimSpace(out)
1917
+		ip, err := inspectField(id, "NetworkSettings.IPAddress")
1918
+		errorOut(err, t, out)
1890 1919
 
1891
-	originalContent, err := ioutil.ReadAll(f)
1892
-	f.Close()
1920
+		portCmd := exec.Command(dockerBinary, "port", id, "1234")
1921
+		port, _, err := runCommandWithOutput(portCmd)
1922
+		errorOut(err, t, out)
1893 1923
 
1894
-	if err != nil {
1895
-		t.Fatal(err)
1924
+		for i := range ids {
1925
+			if ip == ips[i] {
1926
+				t.Fatalf("Conflicting IP: %s", ip)
1927
+			}
1928
+			if port == ports[i] {
1929
+				t.Fatalf("Conflicting port: %s", port)
1930
+			}
1931
+		}
1896 1932
 	}
1897 1933
 
1898
-	out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "restart", "-t", "0", "c1"))
1899
-	if err != nil {
1900
-		t.Fatal(err, out)
1901
-	}
1934
+	// Start the containers back, and ensure they are getting the same IPs, MACs and ports.
1935
+	for i, id := range ids {
1936
+		runCmd := exec.Command(dockerBinary, "start", id)
1937
+		out, _, err := runCommandWithOutput(runCmd)
1938
+		errorOut(err, t, out)
1902 1939
 
1903
-	f, err = os.Open(filepath.Join("/var/lib/docker/containers", contID, "hosts"))
1904
-	if err != nil {
1905
-		t.Fatal(err)
1906
-	}
1940
+		ip, err := inspectField(id, "NetworkSettings.IPAddress")
1941
+		errorOut(err, t, out)
1907 1942
 
1908
-	newContent, err := ioutil.ReadAll(f)
1909
-	f.Close()
1943
+		mac, err := inspectField(id, "NetworkSettings.MacAddress")
1944
+		errorOut(err, t, out)
1910 1945
 
1911
-	if err != nil {
1912
-		t.Fatal(err)
1913
-	}
1946
+		portCmd := exec.Command(dockerBinary, "port", ids[i], "1234")
1947
+		port, _, err := runCommandWithOutput(portCmd)
1948
+		errorOut(err, t, out)
1914 1949
 
1915
-	if strings.TrimSpace(string(originalContent)) == strings.TrimSpace(string(newContent)) {
1916
-		t.Fatalf("expected /etc/hosts to be updated, but wasn't")
1950
+		if ips[i] != ip {
1951
+			t.Fatalf("Container started with a different IP: %s != %s", ip, ips[i])
1952
+		}
1953
+		if macs[i] != mac {
1954
+			t.Fatalf("Container started with a different MAC: %s != %s", mac, macs[i])
1955
+		}
1956
+		if ports[i] != port {
1957
+			t.Fatalf("Container started with a different port: %s != %s", port, ports[i])
1958
+		}
1917 1959
 	}
1918 1960
 
1919 1961
 	deleteAllContainers()
1920
-
1921
-	logDone("run - /etc/hosts updated in parent when restart")
1962
+	logDone("run - ips and ports must not change")
1922 1963
 }
1923 1964
 
1924 1965
 // Ensure that CIDFile gets deleted if it's empty