Browse code

Support `--link` for user-defined networks

This brings in the container-local alias functionality for containers
connected to u ser-defined networks.

Signed-off-by: Madhu Venugopal <madhu@docker.com>

Madhu Venugopal authored on 2016/01/06 04:20:47
Showing 8 changed files
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/docker/docker/opts"
11 11
 	flag "github.com/docker/docker/pkg/mflag"
12 12
 	"github.com/docker/docker/pkg/stringid"
13
+	runconfigopts "github.com/docker/docker/runconfig/opts"
13 14
 	"github.com/docker/engine-api/types"
14 15
 	"github.com/docker/engine-api/types/filters"
15 16
 	"github.com/docker/engine-api/types/network"
... ...
@@ -112,6 +113,8 @@ func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
112 112
 	cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
113 113
 	flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
114 114
 	flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
115
+	flLinks := opts.NewListOpts(runconfigopts.ValidateLink)
116
+	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
115 117
 	cmd.Require(flag.Min, 2)
116 118
 	if err := cmd.ParseFlags(args, true); err != nil {
117 119
 		return err
... ...
@@ -121,6 +124,7 @@ func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
121 121
 			IPv4Address: *flIPAddress,
122 122
 			IPv6Address: *flIPv6Address,
123 123
 		},
124
+		Links: flLinks.GetAll(),
124 125
 	}
125 126
 	return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), epConfig)
126 127
 }
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	"github.com/docker/docker/pkg/chrootarchive"
19 19
 	"github.com/docker/docker/pkg/symlink"
20 20
 	"github.com/docker/docker/pkg/system"
21
+	runconfigopts "github.com/docker/docker/runconfig/opts"
21 22
 	"github.com/docker/docker/utils"
22 23
 	"github.com/docker/docker/volume"
23 24
 	"github.com/docker/engine-api/types/container"
... ...
@@ -247,6 +248,21 @@ func (container *Container) UpdateSandboxNetworkSettings(sb libnetwork.Sandbox)
247 247
 	return nil
248 248
 }
249 249
 
250
+// BuildJoinOptions builds endpoint Join options from a given network.
251
+func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) {
252
+	var joinOptions []libnetwork.EndpointOption
253
+	if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
254
+		for _, str := range epConfig.Links {
255
+			name, alias, err := runconfigopts.ParseLink(str)
256
+			if err != nil {
257
+				return nil, err
258
+			}
259
+			joinOptions = append(joinOptions, libnetwork.CreateOptionAlias(name, alias))
260
+		}
261
+	}
262
+	return joinOptions, nil
263
+}
264
+
250 265
 // BuildCreateEndpointOptions builds endpoint options from a given network.
251 266
 func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) {
252 267
 	var (
... ...
@@ -586,7 +586,7 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
586 586
 	if container.NetworkSettings == nil {
587 587
 		container.NetworkSettings = &network.Settings{}
588 588
 	}
589
-	if endpointsConfig != nil {
589
+	if len(endpointsConfig) > 0 {
590 590
 		container.NetworkSettings.Networks = endpointsConfig
591 591
 	}
592 592
 	if container.NetworkSettings.Networks == nil {
... ...
@@ -816,7 +816,12 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
816 816
 		container.UpdateSandboxNetworkSettings(sb)
817 817
 	}
818 818
 
819
-	if err := ep.Join(sb); err != nil {
819
+	joinOptions, err := container.BuildJoinOptions(n)
820
+	if err != nil {
821
+		return err
822
+	}
823
+
824
+	if err := ep.Join(sb, joinOptions...); err != nil {
820 825
 		return err
821 826
 	}
822 827
 
... ...
@@ -868,7 +868,7 @@ func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error
868 868
 
869 869
 // registerLinks writes the links to a file.
870 870
 func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
871
-	if hostConfig == nil {
871
+	if hostConfig == nil || hostConfig.NetworkMode.IsUserDefined() {
872 872
 		return nil
873 873
 	}
874 874
 
... ...
@@ -1040,3 +1040,44 @@ func verifyIPAddresses(c *check.C, cName, nwname, ipv4, ipv6 string) {
1040 1040
 	out, _ = dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.GlobalIPv6Address }}'", nwname), cName)
1041 1041
 	c.Assert(strings.TrimSpace(out), check.Equals, ipv6)
1042 1042
 }
1043
+
1044
+func (s *DockerSuite) TestUserDefinedNetworkConnectDisconnectLink(c *check.C) {
1045
+	testRequires(c, DaemonIsLinux, NotUserNamespace)
1046
+	dockerCmd(c, "network", "create", "-d", "bridge", "foo1")
1047
+	dockerCmd(c, "network", "create", "-d", "bridge", "foo2")
1048
+
1049
+	dockerCmd(c, "run", "-d", "--net=foo1", "--name=first", "busybox", "top")
1050
+	c.Assert(waitRun("first"), check.IsNil)
1051
+
1052
+	// run a container in user-defined network udlinkNet with a link for an existing container
1053
+	// and a link for a container that doesnt exist
1054
+	dockerCmd(c, "run", "-d", "--net=foo1", "--name=second", "--link=first:FirstInFoo1",
1055
+		"--link=third:bar", "busybox", "top")
1056
+	c.Assert(waitRun("second"), check.IsNil)
1057
+
1058
+	// ping to first and its alias FirstInFoo1 must succeed
1059
+	_, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
1060
+	c.Assert(err, check.IsNil)
1061
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo1")
1062
+	c.Assert(err, check.IsNil)
1063
+
1064
+	// connect first container to foo2 network
1065
+	dockerCmd(c, "network", "connect", "foo2", "first")
1066
+	// connect second container to foo2 network with a different alias for first container
1067
+	dockerCmd(c, "network", "connect", "--link=first:FirstInFoo2", "foo2", "second")
1068
+
1069
+	// ping the new alias in network foo2
1070
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo2")
1071
+	c.Assert(err, check.IsNil)
1072
+
1073
+	// disconnect first container from foo1 network
1074
+	dockerCmd(c, "network", "disconnect", "foo1", "first")
1075
+
1076
+	// link in foo1 network must fail
1077
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo1")
1078
+	c.Assert(err, check.NotNil)
1079
+
1080
+	// link in foo2 network must succeed
1081
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo2")
1082
+	c.Assert(err, check.IsNil)
1083
+}
... ...
@@ -199,6 +199,80 @@ func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) {
199 199
 	}
200 200
 }
201 201
 
202
+func (s *DockerSuite) TestUserDefinedNetworkLinks(c *check.C) {
203
+	testRequires(c, DaemonIsLinux, NotUserNamespace)
204
+	dockerCmd(c, "network", "create", "-d", "bridge", "udlinkNet")
205
+
206
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=first", "busybox", "top")
207
+	c.Assert(waitRun("first"), check.IsNil)
208
+
209
+	// run a container in user-defined network udlinkNet with a link for an existing container
210
+	// and a link for a container that doesnt exist
211
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=second", "--link=first:foo",
212
+		"--link=third:bar", "busybox", "top")
213
+	c.Assert(waitRun("second"), check.IsNil)
214
+
215
+	// ping to first and its alias foo must succeed
216
+	_, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
217
+	c.Assert(err, check.IsNil)
218
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo")
219
+	c.Assert(err, check.IsNil)
220
+
221
+	// ping to third and its alias must fail
222
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "third")
223
+	c.Assert(err, check.NotNil)
224
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "bar")
225
+	c.Assert(err, check.NotNil)
226
+
227
+	// start third container now
228
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=third", "busybox", "top")
229
+	c.Assert(waitRun("third"), check.IsNil)
230
+
231
+	// ping to third and its alias must succeed now
232
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "third")
233
+	c.Assert(err, check.IsNil)
234
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "bar")
235
+	c.Assert(err, check.IsNil)
236
+}
237
+
238
+func (s *DockerSuite) TestUserDefinedNetworkLinksWithRestart(c *check.C) {
239
+	testRequires(c, DaemonIsLinux, NotUserNamespace)
240
+	dockerCmd(c, "network", "create", "-d", "bridge", "udlinkNet")
241
+
242
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=first", "busybox", "top")
243
+	c.Assert(waitRun("first"), check.IsNil)
244
+
245
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=second", "--link=first:foo",
246
+		"busybox", "top")
247
+	c.Assert(waitRun("second"), check.IsNil)
248
+
249
+	// ping to first and its alias foo must succeed
250
+	_, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
251
+	c.Assert(err, check.IsNil)
252
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo")
253
+	c.Assert(err, check.IsNil)
254
+
255
+	// Restart first container
256
+	dockerCmd(c, "restart", "first")
257
+	c.Assert(waitRun("first"), check.IsNil)
258
+
259
+	// ping to first and its alias foo must still succeed
260
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
261
+	c.Assert(err, check.IsNil)
262
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo")
263
+	c.Assert(err, check.IsNil)
264
+
265
+	// Restart second container
266
+	dockerCmd(c, "restart", "second")
267
+	c.Assert(waitRun("second"), check.IsNil)
268
+
269
+	// ping to first and its alias foo must still succeed
270
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
271
+	c.Assert(err, check.IsNil)
272
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo")
273
+	c.Assert(err, check.IsNil)
274
+}
275
+
202 276
 // Issue 9677.
203 277
 func (s *DockerSuite) TestRunWithDaemonFlags(c *check.C) {
204 278
 	out, _, err := dockerCmdWithError("--exec-opt", "foo=bar", "run", "-i", "busybox", "true")
... ...
@@ -48,10 +48,6 @@ func ValidateNetMode(c *container.Config, hc *container.HostConfig) error {
48 48
 		return ErrConflictContainerNetworkAndLinks
49 49
 	}
50 50
 
51
-	if hc.NetworkMode.IsUserDefined() && len(hc.Links) > 0 {
52
-		return ErrConflictUserDefinedNetworkAndLinks
53
-	}
54
-
55 51
 	if (hc.NetworkMode.IsHost() || hc.NetworkMode.IsContainer()) && len(hc.DNS) > 0 {
56 52
 		return ErrConflictNetworkAndDNS
57 53
 	}
... ...
@@ -409,11 +409,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
409 409
 		config.StdinOnce = true
410 410
 	}
411 411
 
412
-	var networkingConfig *networktypes.NetworkingConfig
412
+	networkingConfig := &networktypes.NetworkingConfig{
413
+		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
414
+	}
415
+
413 416
 	if *flIPv4Address != "" || *flIPv6Address != "" {
414
-		networkingConfig = &networktypes.NetworkingConfig{
415
-			EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
416
-		}
417 417
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
418 418
 			IPAMConfig: &networktypes.EndpointIPAMConfig{
419 419
 				IPv4Address: *flIPv4Address,
... ...
@@ -422,6 +422,16 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
422 422
 		}
423 423
 	}
424 424
 
425
+	if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
426
+		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
427
+		if epConfig == nil {
428
+			epConfig = &networktypes.EndpointSettings{}
429
+		}
430
+		epConfig.Links = make([]string, len(hostConfig.Links))
431
+		copy(epConfig.Links, hostConfig.Links)
432
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
433
+	}
434
+
425 435
 	return config, hostConfig, networkingConfig, cmd, nil
426 436
 }
427 437