Allow user to choose the IP address for the container

Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
Alessandro Boch 2016-01-07 16:18:34 -08:00
parent 19b063e740
commit 2bb3fc1bc5
26 changed files with 412 additions and 157 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
)
func (cli *DockerCli) pullImage(image string) error {
@ -79,7 +80,7 @@ func newCIDFile(path string) (*cidFile, error) {
return &cidFile{path: path, file: f}, nil
}
func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
var containerIDFile *cidFile
if cidfile != "" {
var err error
@ -107,7 +108,8 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
}
//create the container
response, err := cli.client.ContainerCreate(config, hostConfig, nil, name)
response, err := cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
//if image not found try to pull it
if err != nil {
if client.IsErrImageNotFound(err) {
@ -124,7 +126,7 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
}
// Retry
var retryErr error
response, retryErr = cli.client.ContainerCreate(config, hostConfig, nil, name)
response, retryErr = cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
if retryErr != nil {
return nil, retryErr
}
@ -156,7 +158,8 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
)
config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
if err != nil {
cmd.ReportError(err.Error(), true)
os.Exit(1)
@ -165,7 +168,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
cmd.Usage()
return nil
}
response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
response, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
if err != nil {
return err
}

View File

@ -107,15 +107,22 @@ func (cli *DockerCli) CmdNetworkRm(args ...string) error {
// CmdNetworkConnect connects a container to a network
//
// Usage: docker network connect <NETWORK> <CONTAINER>
// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
cmd.Require(flag.Exact, 2)
flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
cmd.Require(flag.Min, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), nil)
epConfig := &network.EndpointSettings{
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: *flIPAddress,
IPv6Address: *flIPv6Address,
},
}
return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), epConfig)
}
// CmdNetworkDisconnect disconnects a container from a network

View File

@ -82,7 +82,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
)
config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
// just in case the Parse does not exit
if err != nil {
cmd.ReportError(err.Error(), true)
@ -145,7 +146,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
}
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
createResponse, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
if err != nil {
cmd.ReportError(err.Error(), true)
return runStartContainerErr(err)

View File

@ -332,7 +332,7 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
return err
}
_, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
_, hostConfig, _, err := runconfig.DecodeContainerConfig(r.Body)
if err != nil {
return err
}
@ -358,7 +358,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
name := r.Form.Get("name")
config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
config, hostConfig, networkingConfig, err := runconfig.DecodeContainerConfig(r.Body)
if err != nil {
return err
}
@ -366,10 +366,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
adjustCPUShares := version.LessThan("1.19")
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
Name: name,
Config: config,
HostConfig: hostConfig,
AdjustCPUShares: adjustCPUShares,
Name: name,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
AdjustCPUShares: adjustCPUShares,
})
if err != nil {
return err

View File

@ -39,7 +39,7 @@ func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.
pause = true
}
c, _, err := runconfig.DecodeContainerConfig(r.Body)
c, _, _, err := runconfig.DecodeContainerConfig(r.Body)
if err != nil && err != io.EOF { //Do not fail if body is empty.
return err
}

View File

@ -2,7 +2,6 @@ package network
import (
"github.com/docker/engine-api/types/network"
"github.com/docker/libnetwork"
)
@ -15,7 +14,7 @@ type Backend interface {
GetAllNetworks() []libnetwork.Network
CreateNetwork(name, driver string, ipam network.IPAM,
options map[string]string) (libnetwork.Network, error)
ConnectContainerToNetwork(containerName, networkName string) error
ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
DisconnectContainerFromNetwork(containerName string,
network libnetwork.Network) error
NetworkControllerEnabled() bool

View File

@ -122,7 +122,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
return err
}
return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name())
return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
}
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

View File

@ -261,6 +261,14 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]
createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
}
if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
ipam := epConfig.IPAMConfig
if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") {
createOptions = append(createOptions,
libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil))
}
}
// Other configs are applicable only for the endpoint in the network
// to which container was connected to on docker run.
if n.Name() != container.HostConfig.NetworkMode.NetworkName() &&

View File

@ -503,7 +503,10 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
return runconfig.ErrConflictNoNetwork
}
}
container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
}
return nil
}
@ -562,7 +565,12 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
}
// updateContainerNetworkSettings update the network settings
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error {
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
var (
n libnetwork.Network
err error
)
mode := container.HostConfig.NetworkMode
if container.Config.NetworkDisabled || mode.IsContainer() {
return nil
@ -573,14 +581,35 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
networkName = daemon.netController.Config().Daemon.DefaultNetwork
}
if mode.IsUserDefined() {
n, err := daemon.FindNetwork(networkName)
n, err = daemon.FindNetwork(networkName)
if err != nil {
return err
}
networkName = n.Name()
}
container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
if container.NetworkSettings == nil {
container.NetworkSettings = &network.Settings{}
}
if endpointsConfig != nil {
container.NetworkSettings.Networks = endpointsConfig
}
if container.NetworkSettings.Networks == nil {
container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
}
if !mode.IsUserDefined() {
return nil
}
// Make sure to internally store the per network endpoint config by network name
if _, ok := container.NetworkSettings.Networks[networkName]; ok {
return nil
}
if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
container.NetworkSettings.Networks[networkName] = nwConfig
delete(container.NetworkSettings.Networks, n.ID())
return nil
}
return nil
}
@ -598,15 +627,15 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
return nil
}
err := daemon.updateContainerNetworkSettings(container)
err := daemon.updateContainerNetworkSettings(container, nil)
if err != nil {
return err
}
updateSettings = true
}
for n := range container.NetworkSettings.Networks {
if err := daemon.connectToNetwork(container, n, updateSettings); err != nil {
for n, nConf := range container.NetworkSettings.Networks {
if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
return err
}
}
@ -626,12 +655,65 @@ func (daemon *Daemon) getNetworkSandbox(container *container.Container) libnetwo
return sb
}
// hasUserDefinedIPAddress returns whether the passed endpoint configuration contains IP address configuration
func hasUserDefinedIPAddress(epConfig *networktypes.EndpointSettings) bool {
return epConfig != nil && epConfig.IPAMConfig != nil && (len(epConfig.IPAMConfig.IPv4Address) > 0 || len(epConfig.IPAMConfig.IPv6Address) > 0)
}
// User specified ip address is acceptable only for networks with user specified subnets.
func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.EndpointSettings) error {
if !hasUserDefinedIPAddress(epConfig) {
return nil
}
_, nwIPv4Configs, nwIPv6Configs := n.Info().IpamConfig()
for _, s := range []struct {
ipConfigured bool
subnetConfigs []*libnetwork.IpamConf
}{
{
ipConfigured: len(epConfig.IPAMConfig.IPv4Address) > 0,
subnetConfigs: nwIPv4Configs,
},
{
ipConfigured: len(epConfig.IPAMConfig.IPv6Address) > 0,
subnetConfigs: nwIPv6Configs,
},
} {
if s.ipConfigured {
foundSubnet := false
for _, cfg := range s.subnetConfigs {
if len(cfg.PreferredPool) > 0 {
foundSubnet = true
break
}
}
if !foundSubnet {
return runconfig.ErrUnsupportedNetworkNoSubnetAndIP
}
}
}
return nil
}
// cleanOperationalData resets the operational data from the passed endpoint settings
func cleanOperationalData(es *networktypes.EndpointSettings) {
es.EndpointID = ""
es.Gateway = ""
es.IPAddress = ""
es.IPPrefixLen = 0
es.IPv6Gateway = ""
es.GlobalIPv6Address = ""
es.GlobalIPv6PrefixLen = 0
es.MacAddress = ""
}
// ConnectToNetwork connects a container to a network
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error {
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
if !container.Running {
return derr.ErrorCodeNotRunning.WithArgs(container.ID)
}
if err := daemon.connectToNetwork(container, idOrName, true); err != nil {
if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
return err
}
if err := container.ToDiskLocking(); err != nil {
@ -640,11 +722,15 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
return nil
}
func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, updateSettings bool) (err error) {
func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
if container.HostConfig.NetworkMode.IsContainer() {
return runconfig.ErrConflictSharedNetwork
}
if !containertypes.NetworkMode(idOrName).IsUserDefined() && hasUserDefinedIPAddress(endpointConfig) {
return runconfig.ErrUnsupportedNetworkAndIP
}
if containertypes.NetworkMode(idOrName).IsBridge() &&
daemon.configStore.DisableBridge {
container.Config.NetworkDisabled = true
@ -658,12 +744,20 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
return err
}
if err := validateNetworkingConfig(n, endpointConfig); err != nil {
return err
}
if updateSettings {
if err := daemon.updateNetworkSettings(container, n); err != nil {
return err
}
}
if endpointConfig != nil {
container.NetworkSettings.Networks[n.Name()] = endpointConfig
}
ep, err := container.GetEndpointInNetwork(n)
if err == nil {
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) {
sid := container.NetworkSettings.SandboxID
settings := container.NetworkSettings.Networks
if sid == "" || len(settings) == 0 {
return
}
var networks []libnetwork.Network
for n := range settings {
for n, epSettings := range settings {
if nw, err := daemon.FindNetwork(n); err == nil {
networks = append(networks, nw)
}
settings[n] = &networktypes.EndpointSettings{}
}
container.NetworkSettings = &network.Settings{Networks: settings}
if sid == "" || len(settings) == 0 {
return
cleanOperationalData(epSettings)
}
sb, err := daemon.netController.SandboxByID(sid)

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/docker/daemon/execdriver/windows"
derr "github.com/docker/docker/errors"
"github.com/docker/docker/layer"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/libnetwork"
)
@ -18,7 +19,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
}
// updateContainerNetworkSettings update the network settings
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error {
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
return nil
}
@ -27,7 +28,7 @@ func (daemon *Daemon) initializeNetworking(container *container.Container) error
}
// ConnectToNetwork connects a container to the network
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error {
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
return nil
}

View File

@ -11,6 +11,7 @@ import (
volumestore "github.com/docker/docker/volume/store"
"github.com/docker/engine-api/types"
containertypes "github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
"github.com/opencontainers/runc/libcontainer/label"
)
@ -108,7 +109,12 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *containe
return nil, err
}
if err := daemon.updateContainerNetworkSettings(container); err != nil {
var endpointsConfigs map[string]*networktypes.EndpointSettings
if params.NetworkingConfig != nil {
endpointsConfigs = params.NetworkingConfig.EndpointsConfig
}
if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil {
return nil, err
}

View File

@ -150,12 +150,12 @@ func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnet
// ConnectContainerToNetwork connects the given container to the given
// network. If either cannot be found, an err is returned. If the
// network cannot be set up, an err is returned.
func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string) error {
func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error {
container, err := daemon.GetContainer(containerName)
if err != nil {
return err
}
return daemon.ConnectToNetwork(container, networkName)
return daemon.ConnectToNetwork(container, networkName, endpointConfig)
}
// DisconnectContainerFromNetwork disconnects the given container from

View File

@ -110,6 +110,8 @@ This section lists each version from latest to oldest. Each listing includes a
* `POST /containers/create` now allows you to set a read/write rate limit for a
device (in bytes per second or IO per second).
* `GET /networks` now supports filtering by `name`, `id` and `type`.
* `POST /containers/create` now allows you to set the static IPv4 and/or IPv6 address for the container.
* `POST /networks/(id)/connect` now allows you to set the static IPv4 and/or IPv6 address for the container.
### v1.21 API changes

View File

@ -3031,7 +3031,13 @@ POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/
Content-Type: application/json
{
"Container":"3613f73ba0e4"
"Container":"3613f73ba0e4",
"endpoint_config": {
"test_nw": {
"IPv4Address":"172.24.56.89",
"IPv6Address":"2001:db8::5689"
}
}
}
```

View File

@ -30,11 +30,18 @@ You can also use the `docker run --net=<network-name>` option to start a contain
$ docker run -itd --net=multi-host-network busybox
```
You can specify the IP address you want to be assigned to the container's interface.
```bash
$ docker network connect multi-host-network --ip 10.10.36.122 container2
```
You can pause, restart, and stop containers that are connected to a network.
Paused containers remain connected and a revealed by a `network inspect`. When
the container is stopped, it does not appear on the network until you restart
it. The container's IP address is not guaranteed to remain the same when a
stopped container rejoins the network.
stopped container rejoins the network, unless you specified one when you run
`docker network connect` command.
To verify the container is connected, use the `docker network inspect` command. Use `docker network disconnect` to remove a container from the network.

View File

@ -56,6 +56,8 @@ parent = "smn_cli"
--log-opt=[] Log driver specific options
-m, --memory="" Memory limit
--mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33)
--ip="" Container IPv4 address (e.g. 172.30.100.104)
--ip6="" Container IPv6 address (e.g. 2001:db8::33)
--memory-reservation="" Memory soft limit
--memory-swap="" A positive integer equal to memory plus swap. Specify -1 to enable unlimited swap.
--memory-swappiness="" Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.

View File

@ -275,6 +275,8 @@ of the containers.
'<network-name>|<network-id>': connect to a user-defined network
--add-host="" : Add a line to /etc/hosts (host:IP)
--mac-address="" : Sets the container's Ethernet device's MAC address
--ip="" : Sets the container's Ethernet device's IPv4 address
--ip6="" : Sets the container's Ethernet device's IPv6 address
By default, all containers have networking enabled and they can make any
outgoing connections. The operator can completely disable networking

View File

@ -115,8 +115,8 @@ $ docker run -itd --name=container2 busybox
Then create an isolated, `bridge` network to test with.
```bash
$ docker network create -d bridge isolated_nw
f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a
$ docker network create -d bridge --subnet 172.25.0.0/16 isolated_nw
06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8
```
Connect `container2` to the network and then `inspect` the network to verify the connection:
@ -124,23 +124,26 @@ Connect `container2` to the network and then `inspect` the network to verify the
```
$ docker network connect isolated_nw container2
$ docker network inspect isolated_nw
[[
[
{
"Name": "isolated_nw",
"Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
"Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
"Scope": "local",
"Driver": "bridge",
"IPAM": {
"Driver": "default",
"Config": [
{}
{
"Subnet": "172.25.0.0/16"
}
]
},
"Containers": {
"498eaaaf328e1018042c04b2de04036fc04719a6e39a097a4f4866043a2c2152": {
"EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
"MacAddress": "02:42:ac:15:00:02",
"IPv4Address": "172.21.0.2/16",
"90e1f3ec71caf82ae776a827e0712a68a110a3f175954e5bd4222fd142ac9428": {
"Name": "container2",
"EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
"MacAddress": "02:42:ac:19:00:02",
"IPv4Address": "172.25.0.2/16",
"IPv6Address": ""
}
},
@ -150,20 +153,28 @@ $ docker network inspect isolated_nw
```
You can see that the Engine automatically assigns an IP address to `container2`.
If you had specified a `--subnetwork` when creating your network, the network
would have used that addressing. Now, start a third container and connect it to
Given we specified a `--subnet` when creating the network, Engine picked
an address from that same subnet. Now, start a third container and connect it to
the network on launch using the `docker run` command's `--net` option:
```bash
$ docker run --net=isolated_nw -itd --name=container3 busybox
c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c
$ docker run --net=isolated_nw --ip=172.25.3.3 -itd --name=container3 busybox
467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551
```
As you can see you were able to specify the ip address for your container.
As long as the network to which the container is connecting was created with
a user specified subnet, you will be able to select the IPv4 and/or IPv6 address(es)
for your container when executing `docker run` and `docker network connect` commands.
The selected IP address is part of the container networking configuration and will be
preserved across container reload. The feature is only available on user defined networks,
because they guarantee their subnets configuration does not change across daemon reload.
Now, inspect the network resources used by `container3`.
```bash
$ docker inspect --format='{{json .NetworkSettings.Networks}}' container3
{"isolated_nw":{"EndpointID":"e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847","Gateway":"172.21.0.1","IPAddress":"172.21.0.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:15:00:03"}}
{"isolated_nw":{"IPAMConfig":{"IPv4Address":"172.25.3.3"},"EndpointID":"dffc7ec2915af58cc827d995e6ebdc897342be0420123277103c40ae35579103","Gateway":"172.25.0.1","IPAddress":"172.25.3.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:19:03:03"}}
```
Repeat this command for `container2`. If you have Python installed, you can pretty print the output.
@ -171,24 +182,26 @@ Repeat this command for `container2`. If you have Python installed, you can pret
$ docker inspect --format='{{json .NetworkSettings.Networks}}' container2 | python -m json.tool
{
"bridge": {
"EndpointID": "281b5ead415cf48a6a84fd1a6504342c76e9091fe09b4fdbcc4a01c30b0d3c5b",
"EndpointID": "0099f9efb5a3727f6a554f176b1e96fca34cae773da68b3b6a26d046c12cb365",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAMConfig": null,
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:03"
},
"isolated_nw": {
"EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
"Gateway": "172.21.0.1",
"EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
"Gateway": "172.25.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.21.0.2",
"IPAMConfig": null,
"IPAddress": "172.25.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:15:00:02"
"MacAddress": "02:42:ac:19:00:02"
}
}
```
@ -223,8 +236,8 @@ eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03
RX bytes:648 (648.0 B) TX bytes:648 (648.0 B)
eth1 Link encap:Ethernet HWaddr 02:42:AC:15:00:02
inet addr:172.21.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:acff:fe15:2/64 Scope:Link
inet addr:172.25.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:acff:fe19:2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
@ -252,19 +265,19 @@ fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.21.0.3 container3
172.21.0.3 container3.isolated_nw
172.21.3.3 container3
172.21.3.3 container3.isolated_nw
```
On the `isolated_nw` which was user defined, the Docker network feature updated the `/etc/hosts` with the proper name resolution. Inside of `container2` it is possible to ping `container3` by name.
```bash
/ # ping -w 4 container3
PING container3 (172.21.0.3): 56 data bytes
64 bytes from 172.21.0.3: seq=0 ttl=64 time=0.070 ms
64 bytes from 172.21.0.3: seq=1 ttl=64 time=0.080 ms
64 bytes from 172.21.0.3: seq=2 ttl=64 time=0.080 ms
64 bytes from 172.21.0.3: seq=3 ttl=64 time=0.097 ms
PING container3 (172.25.3.3): 56 data bytes
64 bytes from 172.25.3.3: seq=0 ttl=64 time=0.070 ms
64 bytes from 172.25.3.3: seq=1 ttl=64 time=0.080 ms
64 bytes from 172.25.3.3: seq=2 ttl=64 time=0.080 ms
64 bytes from 172.25.3.3: seq=3 ttl=64 time=0.097 ms
--- container3 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
@ -342,23 +355,26 @@ docker inspect --format='{{json .NetworkSettings.Networks}}' container2 | pytho
$ docker network inspect isolated_nw
[[
[
{
"Name": "isolated_nw",
"Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
"Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
"Scope": "local",
"Driver": "bridge",
"IPAM": {
"Driver": "default",
"Config": [
{}
{
"Subnet": "172.25.0.0/16"
}
]
},
"Containers": {
"c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c": {
"EndpointID": "e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847",
"MacAddress": "02:42:ac:15:00:03",
"IPv4Address": "172.21.0.3/16",
"467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551": {
"Name": "container3",
"EndpointID": "dffc7ec2915af58cc827d995e6ebdc897342be0420123277103c40ae35579103",
"MacAddress": "02:42:ac:19:03:03",
"IPv4Address": "172.25.3.3/16",
"IPv6Address": ""
}
},
@ -393,7 +409,7 @@ lo Link encap:Local Loopback
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
/ # ping container3
PING container3 (172.20.0.1): 56 data bytes
PING container3 (172.25.3.3): 56 data bytes
^C
--- container3 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss
@ -426,13 +442,15 @@ docker network inspect isolated_nw
[
{
"Name": "isolated_nw",
"Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
"Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
"Scope": "local",
"Driver": "bridge",
"IPAM": {
"Driver": "default",
"Config": [
{}
{
"Subnet": "172.25.0.0/16"
}
]
},
"Containers": {},

View File

@ -962,3 +962,71 @@ func (s *DockerNetworkSuite) TestDockerNetworkRestartWithMulipleNetworks(c *chec
c.Assert(networks, checker.Contains, "bridge", check.Commentf("Should contain 'bridge' network"))
c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' netwokr"))
}
func (s *DockerNetworkSuite) TestDockerNetworkConnectPreferredIP(c *check.C) {
// create two networks
dockerCmd(c, "network", "create", "--subnet=172.28.0.0/16", "--subnet=2001:db8:1234::/64", "n0")
assertNwIsAvailable(c, "n0")
dockerCmd(c, "network", "create", "--subnet=172.30.0.0/16", "--ip-range=172.30.5.0/24", "--subnet=2001:db8:abcd::/64", "--ip-range=2001:db8:abcd::/80", "n1")
assertNwIsAvailable(c, "n1")
// run a container on first network specifying the ip addresses
dockerCmd(c, "run", "-d", "--name", "c0", "--net=n0", "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
c.Assert(waitRun("c0"), check.IsNil)
verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
// connect the container to the second network specifying the preferred ip addresses
dockerCmd(c, "network", "connect", "--ip", "172.30.55.44", "--ip6", "2001:db8:abcd::5544", "n1", "c0")
verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
// Stop and restart the container
dockerCmd(c, "stop", "c0")
dockerCmd(c, "start", "c0")
// verify preferred addresses are applied
verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
// Still it should fail to connect to the default network with a specified IP (whatever ip)
out, _, err := dockerCmdWithError("network", "connect", "--ip", "172.21.55.44", "bridge", "c0")
c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
}
func (s *DockerNetworkSuite) TestDockerNetworkUnsupportedPreferredIP(c *check.C) {
// preferred IP is not supported on predefined networks
for _, mode := range []string{"none", "host", "bridge"} {
checkUnsupportedNetworkAndIP(c, mode)
}
// preferred IP is not supported on networks with no user defined subnets
dockerCmd(c, "network", "create", "n0")
assertNwIsAvailable(c, "n0")
out, _, err := dockerCmdWithError("run", "-d", "--ip", "172.28.99.88", "--net", "n0", "busybox", "top")
c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
out, _, err = dockerCmdWithError("run", "-d", "--ip6", "2001:db8:1234::9988", "--net", "n0", "busybox", "top")
c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
dockerCmd(c, "network", "rm", "n0")
assertNwNotAvailable(c, "n0")
}
func checkUnsupportedNetworkAndIP(c *check.C, nwMode string) {
out, _, err := dockerCmdWithError("run", "-d", "--net", nwMode, "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
}
func verifyIPAddresses(c *check.C, cName, nwname, ipv4, ipv6 string) {
out, _ := dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", nwname), cName)
c.Assert(strings.TrimSpace(out), check.Equals, ipv4)
out, _ = dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.GlobalIPv6Address }}'", nwname), cName)
c.Assert(strings.TrimSpace(out), check.Equals, ipv6)
}

View File

@ -7,18 +7,19 @@ import (
"github.com/docker/docker/volume"
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
)
// DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
// struct and returns both a Config and an HostConfig struct
// Be aware this function is not checking whether the resulted structs are nil,
// it's your business to do so
func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, error) {
func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
var w ContainerConfigWrapper
decoder := json.NewDecoder(src)
if err := decoder.Decode(&w); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
hc := w.getHostConfig()
@ -33,21 +34,21 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
// Now validate all the volumes and binds
if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
}
// Certain parameters need daemon-side validation that cannot be done
// on the client, as only the daemon knows what is valid for the platform.
if err := ValidateNetMode(w.Config, hc); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
// Validate the isolation level
if err := ValidateIsolationLevel(hc); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
return w.Config, hc, nil
return w.Config, hc, w.NetworkingConfig, nil
}
// validateVolumesAndBindSettings validates each of the volumes and bind settings

View File

@ -10,6 +10,7 @@ import (
"testing"
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/strslice"
)
@ -45,7 +46,7 @@ func TestDecodeContainerConfig(t *testing.T) {
t.Fatal(err)
}
c, h, err := DecodeContainerConfig(bytes.NewReader(b))
c, h, _, err := DecodeContainerConfig(bytes.NewReader(b))
if err != nil {
t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
}
@ -70,29 +71,29 @@ func TestDecodeContainerConfig(t *testing.T) {
func TestDecodeContainerConfigIsolation(t *testing.T) {
// An invalid isolation level
if _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
if _, _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) {
t.Fatal(err)
}
}
// Blank isolation level (== default)
if _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
if _, _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
t.Fatal("Blank isolation should have succeeded")
}
// Default isolation level
if _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
if _, _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
t.Fatal("default isolation should have succeeded")
}
// Hyper-V Containers isolation level (Valid on Windows only)
if runtime.GOOS == "windows" {
if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
t.Fatal("hyperv isolation should have succeeded")
}
} else {
if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) {
t.Fatal(err)
}
@ -102,7 +103,7 @@ func TestDecodeContainerConfigIsolation(t *testing.T) {
// callDecodeContainerConfigIsolation is a utility function to call
// DecodeContainerConfig for validating isolation levels
func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, error) {
func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
var (
b []byte
err error
@ -114,7 +115,7 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c
Isolation: container.IsolationLevel(isolation)},
}
if b, err = json.Marshal(w); err != nil {
return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
return nil, nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
}
return DecodeContainerConfig(bytes.NewReader(b))
}

View File

@ -2,15 +2,19 @@
package runconfig
import "github.com/docker/engine-api/types/container"
import (
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
)
// ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
// and the corresponding HostConfig (non-portable).
type ContainerConfigWrapper struct {
*container.Config
InnerHostConfig *container.HostConfig `json:"HostConfig,omitempty"`
Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
*container.HostConfig // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
InnerHostConfig *container.HostConfig `json:"HostConfig,omitempty"`
Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
*container.HostConfig // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
}
// getHostConfig gets the HostConfig of the Config.

View File

@ -1,12 +1,16 @@
package runconfig
import "github.com/docker/engine-api/types/container"
import (
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
)
// ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
// and the corresponding HostConfig (non-portable).
type ContainerConfigWrapper struct {
*container.Config
HostConfig *container.HostConfig `json:"HostConfig,omitempty"`
HostConfig *container.HostConfig `json:"HostConfig,omitempty"`
NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
}
// getHostConfig gets the HostConfig of the Config.

View File

@ -29,4 +29,8 @@ var (
ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
// ErrConflictNetworkExposePorts conflict between the expose option and the network mode
ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
// ErrUnsupportedNetworkAndIP conflict between network mode and preferred ip address
ErrUnsupportedNetworkAndIP = fmt.Errorf("User specified IP address is supported on user defined networks only")
// ErrUnsupportedNetworkNoSubnetAndIP conflict between network with no configured subnet and preferred ip address
ErrUnsupportedNetworkNoSubnetAndIP = fmt.Errorf("User specified IP address is supported only when connecting to networks with user configured subnets")
)

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/signal"
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/strslice"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
@ -19,7 +20,7 @@ import (
// Parse parses the specified args for the specified command and generates a Config,
// a HostConfig and returns them with the specified command.
// If the specified args are not valid, it will return an error.
func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
var (
// FIXME: use utils.ListOpts for attach and volumes?
flAttach = opts.NewListOpts(ValidateAttach)
@ -77,6 +78,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
flSwappiness = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
flNetMode = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
flIPv4Address = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)")
flIPv6Address = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)")
flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
@ -119,7 +122,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
var (
@ -131,7 +134,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
// Validate the input mac address
if *flMacAddress != "" {
if _, err := ValidateMACAddress(*flMacAddress); err != nil {
return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
return nil, nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
}
}
if *flStdin {
@ -149,7 +152,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
if *flMemoryString != "" {
flMemory, err = units.RAMInBytes(*flMemoryString)
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
}
@ -157,7 +160,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
if *flMemoryReservation != "" {
MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
}
@ -168,7 +171,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
} else {
memorySwap, err = units.RAMInBytes(*flMemorySwap)
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
}
}
@ -177,20 +180,20 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
if *flKernelMemory != "" {
KernelMemory, err = units.RAMInBytes(*flKernelMemory)
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
}
swappiness := *flSwappiness
if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
return nil, nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
}
var shmSize int64
if *flShmSize != "" {
shmSize, err = units.RAMInBytes(*flShmSize)
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
}
@ -210,7 +213,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
for _, t := range flTmpfs.GetAll() {
if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
tmpfs[arr[0]] = arr[1]
} else {
@ -243,13 +246,13 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose.GetAll() {
if strings.Contains(e, ":") {
return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
return nil, nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
}
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(e)
@ -257,12 +260,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
//if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port)
if err != nil {
return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
return nil, nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
}
for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
@ -275,7 +278,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
for _, device := range flDevices.GetAll() {
deviceMapping, err := ParseDevice(device)
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
deviceMappings = append(deviceMappings, deviceMapping)
}
@ -283,38 +286,38 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
// collect all the environment variables for the container
envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
// collect all the labels for the container
labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
ipcMode := container.IpcMode(*flIpcMode)
if !ipcMode.Valid() {
return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
return nil, nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
}
pidMode := container.PidMode(*flPidMode)
if !pidMode.Valid() {
return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
return nil, nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
}
utsMode := container.UTSMode(*flUTSMode)
if !utsMode.Valid() {
return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
return nil, nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
}
restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
if err != nil {
return nil, nil, cmd, err
return nil, nil, nil, cmd, err
}
resources := container.Resources{
@ -405,7 +408,21 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
return config, hostConfig, cmd, nil
var networkingConfig *networktypes.NetworkingConfig
if *flIPv4Address != "" || *flIPv6Address != "" {
networkingConfig = &networktypes.NetworkingConfig{
EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
}
networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: *flIPv4Address,
IPv6Address: *flIPv6Address,
},
}
}
return config, hostConfig, networkingConfig, cmd, nil
}
// reads a file of line terminated key=value pairs and override that with override parameter

View File

@ -13,10 +13,11 @@ import (
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/runconfig"
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/go-connections/nat"
)
func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
cmd := flag.NewFlagSet("run", flag.ContinueOnError)
cmd.SetOutput(ioutil.Discard)
cmd.Usage = nil
@ -24,7 +25,7 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.Fl
}
func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
config, hostConfig, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
return config, hostConfig, err
}
@ -304,7 +305,7 @@ func callDecodeContainerConfig(volumes []string, binds []string) (*container.Con
if b, err = json.Marshal(w); err != nil {
return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
}
c, h, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
if err != nil {
return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
}
@ -349,7 +350,7 @@ func setupPlatformVolume(u []string, w []string) ([]string, string) {
func TestParseWithMacAddress(t *testing.T) {
invalidMacAddress := "--mac-address=invalidMacAddress"
validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
if _, _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
}
if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
@ -360,7 +361,7 @@ func TestParseWithMacAddress(t *testing.T) {
func TestParseWithMemory(t *testing.T) {
invalidMemory := "--memory=invalid"
validMemory := "--memory=1G"
if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
}
if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
@ -372,7 +373,7 @@ func TestParseWithMemorySwap(t *testing.T) {
invalidMemory := "--memory-swap=invalid"
validMemory := "--memory-swap=1G"
anotherValidMemory := "--memory-swap=-1"
if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
}
if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
@ -417,12 +418,12 @@ func TestParseWithExpose(t *testing.T) {
"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
}
for expose, expectedError := range invalids {
if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
}
}
for expose, exposedPorts := range valids {
config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
config, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -436,7 +437,7 @@ func TestParseWithExpose(t *testing.T) {
}
}
// Merge with actual published port
config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
config, _, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -475,7 +476,7 @@ func TestParseDevice(t *testing.T) {
},
}
for device, deviceMapping := range valids {
_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -491,11 +492,11 @@ func TestParseDevice(t *testing.T) {
func TestParseModes(t *testing.T) {
// ipc ko
if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
if _, _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
}
// ipc ok
_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
_, hostconfig, _, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -503,11 +504,11 @@ func TestParseModes(t *testing.T) {
t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
}
// pid ko
if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
if _, _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
}
// pid ok
_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
_, hostconfig, _, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -515,11 +516,11 @@ func TestParseModes(t *testing.T) {
t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
}
// uts ko
if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
if _, _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
}
// uts ok
_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
_, hostconfig, _, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -527,11 +528,11 @@ func TestParseModes(t *testing.T) {
t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
}
// shm-size ko
if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
if _, _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
}
// shm-size ok
_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
_, hostconfig, _, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -560,12 +561,12 @@ func TestParseRestartPolicy(t *testing.T) {
},
}
for restart, expectedError := range invalids {
if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
}
}
for restart, expected := range valids {
_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -577,11 +578,11 @@ func TestParseRestartPolicy(t *testing.T) {
func TestParseLoggingOpts(t *testing.T) {
// logging opts ko
if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
if _, _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
}
// logging opts ok
_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
_, hostconfig, _, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -596,18 +597,18 @@ func TestParseEnvfileVariables(t *testing.T) {
e = "open nonexistent: The system cannot find the file specified."
}
// env ko
if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
if _, _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
t.Fatalf("Expected an error with message '%s', got %v", e, err)
}
// env ok
config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
config, _, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
}
config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
config, _, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -622,18 +623,18 @@ func TestParseLabelfileVariables(t *testing.T) {
e = "open nonexistent: The system cannot find the file specified."
}
// label ko
if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
if _, _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
t.Fatalf("Expected an error with message '%s', got %v", e, err)
}
// label ok
config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
config, _, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
}
config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
config, _, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
@ -643,7 +644,7 @@ func TestParseLabelfileVariables(t *testing.T) {
}
func TestParseEntryPoint(t *testing.T) {
config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
config, _, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
if err != nil {
t.Fatal(err)
}