Skip to content

Commit 2bb3fc1

Browse files
committed
Allow user to choose the IP address for the container
Signed-off-by: Alessandro Boch <[email protected]>
1 parent 19b063e commit 2bb3fc1

26 files changed

Lines changed: 412 additions & 157 deletions

api/client/create.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/docker/engine-api/client"
1414
"github.com/docker/engine-api/types"
1515
"github.com/docker/engine-api/types/container"
16+
networktypes "github.com/docker/engine-api/types/network"
1617
)
1718

1819
func (cli *DockerCli) pullImage(image string) error {
@@ -79,7 +80,7 @@ func newCIDFile(path string) (*cidFile, error) {
7980
return &cidFile{path: path, file: f}, nil
8081
}
8182

82-
func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
83+
func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
8384
var containerIDFile *cidFile
8485
if cidfile != "" {
8586
var err error
@@ -107,7 +108,8 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
107108
}
108109

109110
//create the container
110-
response, err := cli.client.ContainerCreate(config, hostConfig, nil, name)
111+
response, err := cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
112+
111113
//if image not found try to pull it
112114
if err != nil {
113115
if client.IsErrImageNotFound(err) {
@@ -124,7 +126,7 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
124126
}
125127
// Retry
126128
var retryErr error
127-
response, retryErr = cli.client.ContainerCreate(config, hostConfig, nil, name)
129+
response, retryErr = cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
128130
if retryErr != nil {
129131
return nil, retryErr
130132
}
@@ -156,7 +158,8 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
156158
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
157159
)
158160

159-
config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
161+
config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
162+
160163
if err != nil {
161164
cmd.ReportError(err.Error(), true)
162165
os.Exit(1)
@@ -165,7 +168,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
165168
cmd.Usage()
166169
return nil
167170
}
168-
response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
171+
response, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
169172
if err != nil {
170173
return err
171174
}

api/client/network.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,22 @@ func (cli *DockerCli) CmdNetworkRm(args ...string) error {
107107

108108
// CmdNetworkConnect connects a container to a network
109109
//
110-
// Usage: docker network connect <NETWORK> <CONTAINER>
110+
// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
111111
func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
112112
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
113-
cmd.Require(flag.Exact, 2)
113+
flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
114+
flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
115+
cmd.Require(flag.Min, 2)
114116
if err := cmd.ParseFlags(args, true); err != nil {
115117
return err
116118
}
117-
118-
return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), nil)
119+
epConfig := &network.EndpointSettings{
120+
IPAMConfig: &network.EndpointIPAMConfig{
121+
IPv4Address: *flIPAddress,
122+
IPv6Address: *flIPv6Address,
123+
},
124+
}
125+
return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), epConfig)
119126
}
120127

121128
// CmdNetworkDisconnect disconnects a container from a network

api/client/run.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
8282
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
8383
)
8484

85-
config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
85+
config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
86+
8687
// just in case the Parse does not exit
8788
if err != nil {
8889
cmd.ReportError(err.Error(), true)
@@ -145,7 +146,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
145146
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
146147
}
147148

148-
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
149+
createResponse, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
149150
if err != nil {
150151
cmd.ReportError(err.Error(), true)
151152
return runStartContainerErr(err)

api/server/router/container/container_routes.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
332332
return err
333333
}
334334

335-
_, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
335+
_, hostConfig, _, err := runconfig.DecodeContainerConfig(r.Body)
336336
if err != nil {
337337
return err
338338
}
@@ -358,18 +358,19 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
358358

359359
name := r.Form.Get("name")
360360

361-
config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
361+
config, hostConfig, networkingConfig, err := runconfig.DecodeContainerConfig(r.Body)
362362
if err != nil {
363363
return err
364364
}
365365
version := httputils.VersionFromContext(ctx)
366366
adjustCPUShares := version.LessThan("1.19")
367367

368368
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
369-
Name: name,
370-
Config: config,
371-
HostConfig: hostConfig,
372-
AdjustCPUShares: adjustCPUShares,
369+
Name: name,
370+
Config: config,
371+
HostConfig: hostConfig,
372+
NetworkingConfig: networkingConfig,
373+
AdjustCPUShares: adjustCPUShares,
373374
})
374375
if err != nil {
375376
return err

api/server/router/local/image.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.
3939
pause = true
4040
}
4141

42-
c, _, err := runconfig.DecodeContainerConfig(r.Body)
42+
c, _, _, err := runconfig.DecodeContainerConfig(r.Body)
4343
if err != nil && err != io.EOF { //Do not fail if body is empty.
4444
return err
4545
}

api/server/router/network/backend.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package network
22

33
import (
44
"github.com/docker/engine-api/types/network"
5-
65
"github.com/docker/libnetwork"
76
)
87

@@ -15,7 +14,7 @@ type Backend interface {
1514
GetAllNetworks() []libnetwork.Network
1615
CreateNetwork(name, driver string, ipam network.IPAM,
1716
options map[string]string) (libnetwork.Network, error)
18-
ConnectContainerToNetwork(containerName, networkName string) error
17+
ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
1918
DisconnectContainerFromNetwork(containerName string,
2019
network libnetwork.Network) error
2120
NetworkControllerEnabled() bool

api/server/router/network/network_routes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
122122
return err
123123
}
124124

125-
return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name())
125+
return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
126126
}
127127

128128
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

container/container_unix.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,14 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]
261261
createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
262262
}
263263

264+
if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
265+
ipam := epConfig.IPAMConfig
266+
if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") {
267+
createOptions = append(createOptions,
268+
libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil))
269+
}
270+
}
271+
264272
// Other configs are applicable only for the endpoint in the network
265273
// to which container was connected to on docker run.
266274
if n.Name() != container.HostConfig.NetworkMode.NetworkName() &&

daemon/container_operations_unix.go

Lines changed: 111 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,10 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
503503
return runconfig.ErrConflictNoNetwork
504504
}
505505
}
506-
container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
506+
507+
if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
508+
container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
509+
}
507510

508511
return nil
509512
}
@@ -562,7 +565,12 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
562565
}
563566

564567
// updateContainerNetworkSettings update the network settings
565-
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error {
568+
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
569+
var (
570+
n libnetwork.Network
571+
err error
572+
)
573+
566574
mode := container.HostConfig.NetworkMode
567575
if container.Config.NetworkDisabled || mode.IsContainer() {
568576
return nil
@@ -573,14 +581,35 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
573581
networkName = daemon.netController.Config().Daemon.DefaultNetwork
574582
}
575583
if mode.IsUserDefined() {
576-
n, err := daemon.FindNetwork(networkName)
584+
n, err = daemon.FindNetwork(networkName)
577585
if err != nil {
578586
return err
579587
}
580588
networkName = n.Name()
581589
}
582-
container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
583-
container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
590+
if container.NetworkSettings == nil {
591+
container.NetworkSettings = &network.Settings{}
592+
}
593+
if endpointsConfig != nil {
594+
container.NetworkSettings.Networks = endpointsConfig
595+
}
596+
if container.NetworkSettings.Networks == nil {
597+
container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
598+
container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
599+
}
600+
if !mode.IsUserDefined() {
601+
return nil
602+
}
603+
// Make sure to internally store the per network endpoint config by network name
604+
if _, ok := container.NetworkSettings.Networks[networkName]; ok {
605+
return nil
606+
}
607+
if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
608+
container.NetworkSettings.Networks[networkName] = nwConfig
609+
delete(container.NetworkSettings.Networks, n.ID())
610+
return nil
611+
}
612+
584613
return nil
585614
}
586615

@@ -598,15 +627,15 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
598627
return nil
599628
}
600629

601-
err := daemon.updateContainerNetworkSettings(container)
630+
err := daemon.updateContainerNetworkSettings(container, nil)
602631
if err != nil {
603632
return err
604633
}
605634
updateSettings = true
606635
}
607636

608-
for n := range container.NetworkSettings.Networks {
609-
if err := daemon.connectToNetwork(container, n, updateSettings); err != nil {
637+
for n, nConf := range container.NetworkSettings.Networks {
638+
if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
610639
return err
611640
}
612641
}
@@ -626,12 +655,65 @@ func (daemon *Daemon) getNetworkSandbox(container *container.Container) libnetwo
626655
return sb
627656
}
628657

658+
// hasUserDefinedIPAddress returns whether the passed endpoint configuration contains IP address configuration
659+
func hasUserDefinedIPAddress(epConfig *networktypes.EndpointSettings) bool {
660+
return epConfig != nil && epConfig.IPAMConfig != nil && (len(epConfig.IPAMConfig.IPv4Address) > 0 || len(epConfig.IPAMConfig.IPv6Address) > 0)
661+
}
662+
663+
// User specified ip address is acceptable only for networks with user specified subnets.
664+
func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.EndpointSettings) error {
665+
if !hasUserDefinedIPAddress(epConfig) {
666+
return nil
667+
}
668+
_, nwIPv4Configs, nwIPv6Configs := n.Info().IpamConfig()
669+
for _, s := range []struct {
670+
ipConfigured bool
671+
subnetConfigs []*libnetwork.IpamConf
672+
}{
673+
{
674+
ipConfigured: len(epConfig.IPAMConfig.IPv4Address) > 0,
675+
subnetConfigs: nwIPv4Configs,
676+
},
677+
{
678+
ipConfigured: len(epConfig.IPAMConfig.IPv6Address) > 0,
679+
subnetConfigs: nwIPv6Configs,
680+
},
681+
} {
682+
if s.ipConfigured {
683+
foundSubnet := false
684+
for _, cfg := range s.subnetConfigs {
685+
if len(cfg.PreferredPool) > 0 {
686+
foundSubnet = true
687+
break
688+
}
689+
}
690+
if !foundSubnet {
691+
return runconfig.ErrUnsupportedNetworkNoSubnetAndIP
692+
}
693+
}
694+
}
695+
696+
return nil
697+
}
698+
699+
// cleanOperationalData resets the operational data from the passed endpoint settings
700+
func cleanOperationalData(es *networktypes.EndpointSettings) {
701+
es.EndpointID = ""
702+
es.Gateway = ""
703+
es.IPAddress = ""
704+
es.IPPrefixLen = 0
705+
es.IPv6Gateway = ""
706+
es.GlobalIPv6Address = ""
707+
es.GlobalIPv6PrefixLen = 0
708+
es.MacAddress = ""
709+
}
710+
629711
// ConnectToNetwork connects a container to a network
630-
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error {
712+
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
631713
if !container.Running {
632714
return derr.ErrorCodeNotRunning.WithArgs(container.ID)
633715
}
634-
if err := daemon.connectToNetwork(container, idOrName, true); err != nil {
716+
if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
635717
return err
636718
}
637719
if err := container.ToDiskLocking(); err != nil {
@@ -640,11 +722,15 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
640722
return nil
641723
}
642724

643-
func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, updateSettings bool) (err error) {
725+
func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
644726
if container.HostConfig.NetworkMode.IsContainer() {
645727
return runconfig.ErrConflictSharedNetwork
646728
}
647729

730+
if !containertypes.NetworkMode(idOrName).IsUserDefined() && hasUserDefinedIPAddress(endpointConfig) {
731+
return runconfig.ErrUnsupportedNetworkAndIP
732+
}
733+
648734
if containertypes.NetworkMode(idOrName).IsBridge() &&
649735
daemon.configStore.DisableBridge {
650736
container.Config.NetworkDisabled = true
@@ -658,12 +744,20 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
658744
return err
659745
}
660746

747+
if err := validateNetworkingConfig(n, endpointConfig); err != nil {
748+
return err
749+
}
750+
661751
if updateSettings {
662752
if err := daemon.updateNetworkSettings(container, n); err != nil {
663753
return err
664754
}
665755
}
666756

757+
if endpointConfig != nil {
758+
container.NetworkSettings.Networks[n.Name()] = endpointConfig
759+
}
760+
667761
ep, err := container.GetEndpointInNetwork(n)
668762
if err == nil {
669763
return fmt.Errorf("Conflict. A container with name %q is already connected to network %s.", strings.TrimPrefix(container.Name, "/"), idOrName)
@@ -869,18 +963,16 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
869963

870964
sid := container.NetworkSettings.SandboxID
871965
settings := container.NetworkSettings.Networks
966+
if sid == "" || len(settings) == 0 {
967+
return
968+
}
969+
872970
var networks []libnetwork.Network
873-
for n := range settings {
971+
for n, epSettings := range settings {
874972
if nw, err := daemon.FindNetwork(n); err == nil {
875973
networks = append(networks, nw)
876974
}
877-
settings[n] = &networktypes.EndpointSettings{}
878-
}
879-
880-
container.NetworkSettings = &network.Settings{Networks: settings}
881-
882-
if sid == "" || len(settings) == 0 {
883-
return
975+
cleanOperationalData(epSettings)
884976
}
885977

886978
sb, err := daemon.netController.SandboxByID(sid)

0 commit comments

Comments
 (0)