Support network connect/disconnect to stopped container
| ... | ... |
@@ -696,13 +696,43 @@ func cleanOperationalData(es *networktypes.EndpointSettings) {
|
| 696 | 696 |
es.MacAddress = "" |
| 697 | 697 |
} |
| 698 | 698 |
|
| 699 |
+func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrName string, updateSettings bool) (libnetwork.Network, error) {
|
|
| 700 |
+ if container.HostConfig.NetworkMode.IsContainer() {
|
|
| 701 |
+ return nil, runconfig.ErrConflictSharedNetwork |
|
| 702 |
+ } |
|
| 703 |
+ |
|
| 704 |
+ if containertypes.NetworkMode(idOrName).IsBridge() && |
|
| 705 |
+ daemon.configStore.DisableBridge {
|
|
| 706 |
+ container.Config.NetworkDisabled = true |
|
| 707 |
+ return nil, nil |
|
| 708 |
+ } |
|
| 709 |
+ |
|
| 710 |
+ n, err := daemon.FindNetwork(idOrName) |
|
| 711 |
+ if err != nil {
|
|
| 712 |
+ return nil, err |
|
| 713 |
+ } |
|
| 714 |
+ |
|
| 715 |
+ if updateSettings {
|
|
| 716 |
+ if err := daemon.updateNetworkSettings(container, n); err != nil {
|
|
| 717 |
+ return nil, err |
|
| 718 |
+ } |
|
| 719 |
+ } |
|
| 720 |
+ return n, nil |
|
| 721 |
+} |
|
| 722 |
+ |
|
| 699 | 723 |
// ConnectToNetwork connects a container to a network |
| 700 | 724 |
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
|
| 701 | 725 |
if !container.Running {
|
| 702 |
- return derr.ErrorCodeNotRunning.WithArgs(container.ID) |
|
| 703 |
- } |
|
| 704 |
- if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
|
|
| 705 |
- return err |
|
| 726 |
+ if container.RemovalInProgress || container.Dead {
|
|
| 727 |
+ return derr.ErrorCodeRemovalContainer.WithArgs(container.ID) |
|
| 728 |
+ } |
|
| 729 |
+ if _, err := daemon.updateNetworkConfig(container, idOrName, true); err != nil {
|
|
| 730 |
+ return err |
|
| 731 |
+ } |
|
| 732 |
+ } else {
|
|
| 733 |
+ if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
|
|
| 734 |
+ return err |
|
| 735 |
+ } |
|
| 706 | 736 |
} |
| 707 | 737 |
if err := container.ToDiskLocking(); err != nil {
|
| 708 | 738 |
return fmt.Errorf("Error saving container to disk: %v", err)
|
| ... | ... |
@@ -711,37 +741,24 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName |
| 711 | 711 |
} |
| 712 | 712 |
|
| 713 | 713 |
func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
|
| 714 |
- if container.HostConfig.NetworkMode.IsContainer() {
|
|
| 715 |
- return runconfig.ErrConflictSharedNetwork |
|
| 714 |
+ n, err := daemon.updateNetworkConfig(container, idOrName, updateSettings) |
|
| 715 |
+ if err != nil {
|
|
| 716 |
+ return err |
|
| 717 |
+ } |
|
| 718 |
+ if n == nil {
|
|
| 719 |
+ return nil |
|
| 716 | 720 |
} |
| 717 | 721 |
|
| 718 | 722 |
if !containertypes.NetworkMode(idOrName).IsUserDefined() && hasUserDefinedIPAddress(endpointConfig) {
|
| 719 | 723 |
return runconfig.ErrUnsupportedNetworkAndIP |
| 720 | 724 |
} |
| 721 | 725 |
|
| 722 |
- if containertypes.NetworkMode(idOrName).IsBridge() && |
|
| 723 |
- daemon.configStore.DisableBridge {
|
|
| 724 |
- container.Config.NetworkDisabled = true |
|
| 725 |
- return nil |
|
| 726 |
- } |
|
| 727 |
- |
|
| 728 | 726 |
controller := daemon.netController |
| 729 | 727 |
|
| 730 |
- n, err := daemon.FindNetwork(idOrName) |
|
| 731 |
- if err != nil {
|
|
| 732 |
- return err |
|
| 733 |
- } |
|
| 734 |
- |
|
| 735 | 728 |
if err := validateNetworkingConfig(n, endpointConfig); err != nil {
|
| 736 | 729 |
return err |
| 737 | 730 |
} |
| 738 | 731 |
|
| 739 |
- if updateSettings {
|
|
| 740 |
- if err := daemon.updateNetworkSettings(container, n); err != nil {
|
|
| 741 |
- return err |
|
| 742 |
- } |
|
| 743 |
- } |
|
| 744 |
- |
|
| 745 | 732 |
if endpointConfig != nil {
|
| 746 | 733 |
container.NetworkSettings.Networks[n.Name()] = endpointConfig |
| 747 | 734 |
} |
| ... | ... |
@@ -805,16 +822,22 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName |
| 805 | 805 |
|
| 806 | 806 |
// DisconnectFromNetwork disconnects container from network n. |
| 807 | 807 |
func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n libnetwork.Network) error {
|
| 808 |
- if !container.Running {
|
|
| 809 |
- return derr.ErrorCodeNotRunning.WithArgs(container.ID) |
|
| 810 |
- } |
|
| 811 |
- |
|
| 812 | 808 |
if container.HostConfig.NetworkMode.IsHost() && containertypes.NetworkMode(n.Type()).IsHost() {
|
| 813 | 809 |
return runconfig.ErrConflictHostNetwork |
| 814 | 810 |
} |
| 815 |
- |
|
| 816 |
- if err := disconnectFromNetwork(container, n); err != nil {
|
|
| 817 |
- return err |
|
| 811 |
+ if !container.Running {
|
|
| 812 |
+ if container.RemovalInProgress || container.Dead {
|
|
| 813 |
+ return derr.ErrorCodeRemovalContainer.WithArgs(container.ID) |
|
| 814 |
+ } |
|
| 815 |
+ if _, ok := container.NetworkSettings.Networks[n.Name()]; ok {
|
|
| 816 |
+ delete(container.NetworkSettings.Networks, n.Name()) |
|
| 817 |
+ } else {
|
|
| 818 |
+ return fmt.Errorf("container %s is not connected to the network %s", container.ID, n.Name())
|
|
| 819 |
+ } |
|
| 820 |
+ } else {
|
|
| 821 |
+ if err := disconnectFromNetwork(container, n); err != nil {
|
|
| 822 |
+ return err |
|
| 823 |
+ } |
|
| 818 | 824 |
} |
| 819 | 825 |
|
| 820 | 826 |
if err := container.ToDiskLocking(); err != nil {
|
| ... | ... |
@@ -16,7 +16,7 @@ parent = "smn_cli" |
| 16 | 16 |
|
| 17 | 17 |
--help Print usage |
| 18 | 18 |
|
| 19 |
-Connects a running container to a network. You can connect a container by name |
|
| 19 |
+Connects a container to a network. You can connect a container by name |
|
| 20 | 20 |
or by ID. Once connected, the container can communicate with other containers in |
| 21 | 21 |
the same network. |
| 22 | 22 |
|
| ... | ... |
@@ -311,9 +311,8 @@ PING 172.17.0.2 (172.17.0.2): 56 data bytes |
| 311 | 311 |
|
| 312 | 312 |
``` |
| 313 | 313 |
|
| 314 |
-To connect a container to a network, the container must be running. If you stop |
|
| 315 |
-a container and inspect a network it belongs to, you won't see that container. |
|
| 316 |
-The `docker network inspect` command only shows running containers. |
|
| 314 |
+You can connect both running and non-running containers to a network. However, |
|
| 315 |
+`docker network inspect` only displays information on running containers. |
|
| 317 | 316 |
|
| 318 | 317 |
## Disconnecting containers |
| 319 | 318 |
|
| ... | ... |
@@ -46,6 +46,15 @@ var ( |
| 46 | 46 |
HTTPStatusCode: http.StatusInternalServerError, |
| 47 | 47 |
}) |
| 48 | 48 |
|
| 49 |
+ // ErrorCodeRemovalContainer is generated when we attempt to connect or disconnect a |
|
| 50 |
+ // container but it's marked for removal. |
|
| 51 |
+ ErrorCodeRemovalContainer = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
|
| 52 |
+ Value: "REMOVALCONTAINER", |
|
| 53 |
+ Message: "Container %s is marked for removal and cannot be connected or disconnected to the network", |
|
| 54 |
+ Description: "The specified container is marked for removal and cannot be connected or disconnected to the network", |
|
| 55 |
+ HTTPStatusCode: http.StatusInternalServerError, |
|
| 56 |
+ }) |
|
| 57 |
+ |
|
| 49 | 58 |
// ErrorCodePausedContainer is generated when we attempt to attach a |
| 50 | 59 |
// container but its paused. |
| 51 | 60 |
ErrorCodePausedContainer = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
| ... | ... |
@@ -448,11 +448,6 @@ func (s *DockerNetworkSuite) TestDockerNetworkConnectDisconnect(c *check.C) {
|
| 448 | 448 |
c.Assert(nr.Name, checker.Equals, "test") |
| 449 | 449 |
c.Assert(len(nr.Containers), checker.Equals, 0) |
| 450 | 450 |
|
| 451 |
- // check if network connect fails for inactive containers |
|
| 452 |
- dockerCmd(c, "stop", containerID) |
|
| 453 |
- _, _, err = dockerCmdWithError("network", "connect", "test", containerID)
|
|
| 454 |
- c.Assert(err, check.NotNil) |
|
| 455 |
- |
|
| 456 | 451 |
dockerCmd(c, "network", "rm", "test") |
| 457 | 452 |
assertNwNotAvailable(c, "test") |
| 458 | 453 |
} |
| ... | ... |
@@ -938,7 +933,44 @@ func (s *DockerNetworkSuite) TestDockerNetworkRestartWithMulipleNetworks(c *chec |
| 938 | 938 |
networks, err := inspectField("foo", "NetworkSettings.Networks")
|
| 939 | 939 |
c.Assert(err, checker.IsNil) |
| 940 | 940 |
c.Assert(networks, checker.Contains, "bridge", check.Commentf("Should contain 'bridge' network"))
|
| 941 |
- c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' netwokr"))
|
|
| 941 |
+ c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' network"))
|
|
| 942 |
+} |
|
| 943 |
+ |
|
| 944 |
+func (s *DockerNetworkSuite) TestDockerNetworkConnectDisconnectToStoppedContainer(c *check.C) {
|
|
| 945 |
+ dockerCmd(c, "network", "create", "test") |
|
| 946 |
+ dockerCmd(c, "create", "--name=foo", "busybox", "top") |
|
| 947 |
+ dockerCmd(c, "network", "connect", "test", "foo") |
|
| 948 |
+ networks, err := inspectField("foo", "NetworkSettings.Networks")
|
|
| 949 |
+ c.Assert(err, checker.IsNil) |
|
| 950 |
+ c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' network"))
|
|
| 951 |
+ |
|
| 952 |
+ // Restart docker daemon to test the config has persisted to disk |
|
| 953 |
+ s.d.Restart() |
|
| 954 |
+ networks, err = inspectField("foo", "NetworkSettings.Networks")
|
|
| 955 |
+ c.Assert(err, checker.IsNil) |
|
| 956 |
+ c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' network"))
|
|
| 957 |
+ |
|
| 958 |
+ // start the container and test if we can ping it from another container in the same network |
|
| 959 |
+ dockerCmd(c, "start", "foo") |
|
| 960 |
+ c.Assert(waitRun("foo"), checker.IsNil)
|
|
| 961 |
+ ip, err := inspectField("foo", "NetworkSettings.Networks.test.IPAddress")
|
|
| 962 |
+ ip = strings.TrimSpace(ip) |
|
| 963 |
+ dockerCmd(c, "run", "--net=test", "busybox", "sh", "-c", fmt.Sprintf("ping -c 1 %s", ip))
|
|
| 964 |
+ |
|
| 965 |
+ dockerCmd(c, "stop", "foo") |
|
| 966 |
+ |
|
| 967 |
+ // Test disconnect |
|
| 968 |
+ dockerCmd(c, "network", "disconnect", "test", "foo") |
|
| 969 |
+ networks, err = inspectField("foo", "NetworkSettings.Networks")
|
|
| 970 |
+ c.Assert(err, checker.IsNil) |
|
| 971 |
+ c.Assert(networks, checker.Not(checker.Contains), "test", check.Commentf("Should not contain 'test' network"))
|
|
| 972 |
+ |
|
| 973 |
+ // Restart docker daemon to test the config has persisted to disk |
|
| 974 |
+ s.d.Restart() |
|
| 975 |
+ networks, err = inspectField("foo", "NetworkSettings.Networks")
|
|
| 976 |
+ c.Assert(err, checker.IsNil) |
|
| 977 |
+ c.Assert(networks, checker.Not(checker.Contains), "test", check.Commentf("Should not contain 'test' network"))
|
|
| 978 |
+ |
|
| 942 | 979 |
} |
| 943 | 980 |
|
| 944 | 981 |
func (s *DockerNetworkSuite) TestDockerNetworkConnectPreferredIP(c *check.C) {
|
| ... | ... |
@@ -11,7 +11,7 @@ NETWORK CONTAINER |
| 11 | 11 |
|
| 12 | 12 |
# DESCRIPTION |
| 13 | 13 |
|
| 14 |
-Connects a running container to a network. You can connect a container by name |
|
| 14 |
+Connects a container to a network. You can connect a container by name |
|
| 15 | 15 |
or by ID. Once connected, the container can communicate with other containers in |
| 16 | 16 |
the same network. |
| 17 | 17 |
|
| ... | ... |
@@ -11,7 +11,7 @@ NETWORK CONTAINER |
| 11 | 11 |
|
| 12 | 12 |
# DESCRIPTION |
| 13 | 13 |
|
| 14 |
-Disconnects a container from a network. The container must be running to disconnect it from the network. |
|
| 14 |
+Disconnects a container from a network. |
|
| 15 | 15 |
|
| 16 | 16 |
```bash |
| 17 | 17 |
$ docker network disconnect multi-host-network container1 |