diff --git a/api/client/network.go b/api/client/network.go index 6be1a5ebe5..4c49fad7d8 100644 --- a/api/client/network.go +++ b/api/client/network.go @@ -10,6 +10,7 @@ import ( "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/stringid" + runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/filters" "github.com/docker/engine-api/types/network" @@ -112,6 +113,8 @@ func (cli *DockerCli) CmdNetworkConnect(args ...string) error { cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false) flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address") flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address") + flLinks := opts.NewListOpts(runconfigopts.ValidateLink) + cmd.Var(&flLinks, []string{"-link"}, "Add link to another container") cmd.Require(flag.Min, 2) if err := cmd.ParseFlags(args, true); err != nil { return err @@ -121,6 +124,7 @@ func (cli *DockerCli) CmdNetworkConnect(args ...string) error { IPv4Address: *flIPAddress, IPv6Address: *flIPv6Address, }, + Links: flLinks.GetAll(), } return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), epConfig) } diff --git a/container/container_unix.go b/container/container_unix.go index 4b8296bba6..d59a77776b 100644 --- a/container/container_unix.go +++ b/container/container_unix.go @@ -18,6 +18,7 @@ import ( "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" + runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/docker/utils" "github.com/docker/docker/volume" "github.com/docker/engine-api/types/container" @@ -247,6 +248,21 @@ func (container *Container) UpdateSandboxNetworkSettings(sb libnetwork.Sandbox) return nil } +// BuildJoinOptions builds endpoint Join options from a given network. +func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) { + var joinOptions []libnetwork.EndpointOption + if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok { + for _, str := range epConfig.Links { + name, alias, err := runconfigopts.ParseLink(str) + if err != nil { + return nil, err + } + joinOptions = append(joinOptions, libnetwork.CreateOptionAlias(name, alias)) + } + } + return joinOptions, nil +} + // BuildCreateEndpointOptions builds endpoint options from a given network. func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) { var ( diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index 9a34fb21a3..7ac05d771c 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -586,7 +586,7 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai if container.NetworkSettings == nil { container.NetworkSettings = &network.Settings{} } - if endpointsConfig != nil { + if len(endpointsConfig) > 0 { container.NetworkSettings.Networks = endpointsConfig } if container.NetworkSettings.Networks == nil { @@ -816,7 +816,12 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName container.UpdateSandboxNetworkSettings(sb) } - if err := ep.Join(sb); err != nil { + joinOptions, err := container.BuildJoinOptions(n) + if err != nil { + return err + } + + if err := ep.Join(sb, joinOptions...); err != nil { return err } diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 8928fd64f6..9ae0e1c5e0 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -868,7 +868,7 @@ func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error // registerLinks writes the links to a file. func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error { - if hostConfig == nil { + if hostConfig == nil || hostConfig.NetworkMode.IsUserDefined() { return nil } diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index 74af58b579..1732216d28 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/integration-cli/docker_cli_network_unix_test.go @@ -1040,3 +1040,44 @@ func verifyIPAddresses(c *check.C, cName, nwname, ipv4, ipv6 string) { out, _ = dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.GlobalIPv6Address }}'", nwname), cName) c.Assert(strings.TrimSpace(out), check.Equals, ipv6) } + +func (s *DockerSuite) TestUserDefinedNetworkConnectDisconnectLink(c *check.C) { + testRequires(c, DaemonIsLinux, NotUserNamespace) + dockerCmd(c, "network", "create", "-d", "bridge", "foo1") + dockerCmd(c, "network", "create", "-d", "bridge", "foo2") + + dockerCmd(c, "run", "-d", "--net=foo1", "--name=first", "busybox", "top") + c.Assert(waitRun("first"), check.IsNil) + + // run a container in user-defined network udlinkNet with a link for an existing container + // and a link for a container that doesnt exist + dockerCmd(c, "run", "-d", "--net=foo1", "--name=second", "--link=first:FirstInFoo1", + "--link=third:bar", "busybox", "top") + c.Assert(waitRun("second"), check.IsNil) + + // ping to first and its alias FirstInFoo1 must succeed + _, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") + c.Assert(err, check.IsNil) + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo1") + c.Assert(err, check.IsNil) + + // connect first container to foo2 network + dockerCmd(c, "network", "connect", "foo2", "first") + // connect second container to foo2 network with a different alias for first container + dockerCmd(c, "network", "connect", "--link=first:FirstInFoo2", "foo2", "second") + + // ping the new alias in network foo2 + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo2") + c.Assert(err, check.IsNil) + + // disconnect first container from foo1 network + dockerCmd(c, "network", "disconnect", "foo1", "first") + + // link in foo1 network must fail + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo1") + c.Assert(err, check.NotNil) + + // link in foo2 network must succeed + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo2") + c.Assert(err, check.IsNil) +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index bf056b9908..aaef5315a9 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -199,6 +199,80 @@ func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) { } } +func (s *DockerSuite) TestUserDefinedNetworkLinks(c *check.C) { + testRequires(c, DaemonIsLinux, NotUserNamespace) + dockerCmd(c, "network", "create", "-d", "bridge", "udlinkNet") + + dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=first", "busybox", "top") + c.Assert(waitRun("first"), check.IsNil) + + // run a container in user-defined network udlinkNet with a link for an existing container + // and a link for a container that doesnt exist + dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=second", "--link=first:foo", + "--link=third:bar", "busybox", "top") + c.Assert(waitRun("second"), check.IsNil) + + // ping to first and its alias foo must succeed + _, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") + c.Assert(err, check.IsNil) + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo") + c.Assert(err, check.IsNil) + + // ping to third and its alias must fail + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "third") + c.Assert(err, check.NotNil) + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "bar") + c.Assert(err, check.NotNil) + + // start third container now + dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=third", "busybox", "top") + c.Assert(waitRun("third"), check.IsNil) + + // ping to third and its alias must succeed now + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "third") + c.Assert(err, check.IsNil) + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "bar") + c.Assert(err, check.IsNil) +} + +func (s *DockerSuite) TestUserDefinedNetworkLinksWithRestart(c *check.C) { + testRequires(c, DaemonIsLinux, NotUserNamespace) + dockerCmd(c, "network", "create", "-d", "bridge", "udlinkNet") + + dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=first", "busybox", "top") + c.Assert(waitRun("first"), check.IsNil) + + dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=second", "--link=first:foo", + "busybox", "top") + c.Assert(waitRun("second"), check.IsNil) + + // ping to first and its alias foo must succeed + _, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") + c.Assert(err, check.IsNil) + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo") + c.Assert(err, check.IsNil) + + // Restart first container + dockerCmd(c, "restart", "first") + c.Assert(waitRun("first"), check.IsNil) + + // ping to first and its alias foo must still succeed + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") + c.Assert(err, check.IsNil) + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo") + c.Assert(err, check.IsNil) + + // Restart second container + dockerCmd(c, "restart", "second") + c.Assert(waitRun("second"), check.IsNil) + + // ping to first and its alias foo must still succeed + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") + c.Assert(err, check.IsNil) + _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo") + c.Assert(err, check.IsNil) +} + // Issue 9677. func (s *DockerSuite) TestRunWithDaemonFlags(c *check.C) { out, _, err := dockerCmdWithError("--exec-opt", "foo=bar", "run", "-i", "busybox", "true") diff --git a/runconfig/hostconfig_unix.go b/runconfig/hostconfig_unix.go index 7875d82e25..e536313185 100644 --- a/runconfig/hostconfig_unix.go +++ b/runconfig/hostconfig_unix.go @@ -48,10 +48,6 @@ func ValidateNetMode(c *container.Config, hc *container.HostConfig) error { return ErrConflictContainerNetworkAndLinks } - if hc.NetworkMode.IsUserDefined() && len(hc.Links) > 0 { - return ErrConflictUserDefinedNetworkAndLinks - } - if (hc.NetworkMode.IsHost() || hc.NetworkMode.IsContainer()) && len(hc.DNS) > 0 { return ErrConflictNetworkAndDNS } diff --git a/runconfig/opts/parse.go b/runconfig/opts/parse.go index d78e8f0810..8746f8a310 100644 --- a/runconfig/opts/parse.go +++ b/runconfig/opts/parse.go @@ -409,11 +409,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host config.StdinOnce = true } - var networkingConfig *networktypes.NetworkingConfig + networkingConfig := &networktypes.NetworkingConfig{ + EndpointsConfig: make(map[string]*networktypes.EndpointSettings), + } + if *flIPv4Address != "" || *flIPv6Address != "" { - networkingConfig = &networktypes.NetworkingConfig{ - EndpointsConfig: make(map[string]*networktypes.EndpointSettings), - } networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{ IPAMConfig: &networktypes.EndpointIPAMConfig{ IPv4Address: *flIPv4Address, @@ -422,6 +422,16 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host } } + if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 { + epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] + if epConfig == nil { + epConfig = &networktypes.EndpointSettings{} + } + epConfig.Links = make([]string, len(hostConfig.Links)) + copy(epConfig.Links, hostConfig.Links) + networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig + } + return config, hostConfig, networkingConfig, cmd, nil }