Add support for docker run in swarm mode overlay

This PR adds support for running regular containers to be connected to
swarm mode multi-host network so that:
    - containers connected to the same network across the cluster can
      discover and connect to each other.
    - Get access to services(and their associated loadbalancers)
      connected to the same network

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
This commit is contained in:
Jana Radhakrishnan 2016-08-23 16:50:15 -07:00
parent 99c3968098
commit 99a98ccc14
23 changed files with 606 additions and 104 deletions

View File

@ -23,6 +23,7 @@ type createOptions struct {
labels []string
internal bool
ipv6 bool
attachable bool
ipamDriver string
ipamSubnet []string
@ -55,6 +56,7 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata on a network")
flags.BoolVar(&opts.internal, "internal", false, "Restrict external access to the network")
flags.BoolVar(&opts.ipv6, "ipv6", false, "Enable IPv6 networking")
flags.BoolVar(&opts.attachable, "attachable", false, "Enable manual container attachment")
flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment")
@ -87,6 +89,7 @@ func runCreate(dockerCli *client.DockerCli, opts createOptions) error {
CheckDuplicate: true,
Internal: opts.internal,
EnableIPv6: opts.ipv6,
Attachable: opts.attachable,
Labels: runconfigopts.ConvertKVStringsToMap(opts.labels),
}

View File

@ -451,6 +451,7 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
Mounts: opts.mounts.Value(),
StopGracePeriod: opts.stopGrace.Value(),
},
Networks: convertNetworks(opts.networks),
Resources: opts.resources.ToResourceRequirements(),
RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
Placement: &swarm.Placement{
@ -458,13 +459,13 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
},
LogDriver: opts.logDriver.toLogDriver(),
},
Mode: swarm.ServiceMode{},
Networks: convertNetworks(opts.networks),
Mode: swarm.ServiceMode{},
UpdateConfig: &swarm.UpdateConfig{
Parallelism: opts.update.parallelism,
Delay: opts.update.delay,
FailureAction: opts.update.onFailure,
},
Networks: convertNetworks(opts.networks),
EndpointSpec: opts.endpoint.ToEndpointSpec(),
}

View File

@ -2,7 +2,6 @@ package network
import (
"encoding/json"
"fmt"
"net/http"
"golang.org/x/net/context"
@ -11,7 +10,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/errors"
"github.com/docker/libnetwork"
)
@ -116,17 +114,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
return err
}
nw, err := n.backend.FindNetwork(vars["id"])
if err != nil {
return err
}
if nw.Info().Dynamic() {
err := fmt.Errorf("operation not supported for swarm scoped networks")
return errors.NewRequestForbiddenError(err)
}
return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
return n.backend.ConnectContainerToNetwork(connect.Container, vars["id"], connect.EndpointConfig)
}
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@ -143,13 +131,6 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
return err
}
nw, _ := n.backend.FindNetwork(vars["id"])
if nw != nil && nw.Info().Dynamic() {
err := fmt.Errorf("operation not supported for swarm scoped networks")
return errors.NewRequestForbiddenError(err)
}
return n.backend.DisconnectContainerFromNetwork(disconnect.Container, vars["id"], disconnect.Force)
}

View File

@ -700,7 +700,9 @@ func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwor
}
if _, ok := networkSettings.Networks[n.Name()]; !ok {
networkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
networkSettings.Networks[n.Name()] = &network.EndpointSettings{
EndpointSettings: &networktypes.EndpointSettings{},
}
}
networkSettings.Networks[n.Name()].NetworkID = n.ID()
networkSettings.Networks[n.Name()].EndpointID = ep.ID()

View File

@ -16,6 +16,7 @@ import (
"github.com/Sirupsen/logrus"
apitypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
types "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/daemon/cluster/convert"
executorpkg "github.com/docker/docker/daemon/cluster/executor"
@ -126,6 +127,18 @@ type Cluster struct {
stop bool
err error
cancelDelay func()
attachers map[string]*attacher
}
// attacher manages the in-memory attachment state of a container
// attachment to a global scope network managed by swarm manager. It
// helps in identifying the attachment ID via the taskID and the
// corresponding attachment configuration obtained from the manager.
type attacher struct {
taskID string
config *network.NetworkingConfig
attachWaitCh chan *network.NetworkingConfig
detachWaitCh chan struct{}
}
type node struct {
@ -154,6 +167,7 @@ func New(config Config) (*Cluster, error) {
config: config,
configEvent: make(chan struct{}, 10),
runtimeRoot: config.RuntimeRoot,
attachers: make(map[string]*attacher),
}
st, err := c.loadState()
@ -1212,6 +1226,120 @@ func (c *Cluster) GetNetworks() ([]apitypes.NetworkResource, error) {
return networks, nil
}
func attacherKey(target, containerID string) string {
return containerID + ":" + target
}
// UpdateAttachment signals the attachment config to the attachment
// waiter who is trying to start or attach the container to the
// network.
func (c *Cluster) UpdateAttachment(target, containerID string, config *network.NetworkingConfig) error {
c.RLock()
attacher, ok := c.attachers[attacherKey(target, containerID)]
c.RUnlock()
if !ok || attacher == nil {
return fmt.Errorf("could not find attacher for container %s to network %s", containerID, target)
}
attacher.attachWaitCh <- config
close(attacher.attachWaitCh)
return nil
}
// WaitForDetachment waits for the container to stop or detach from
// the network.
func (c *Cluster) WaitForDetachment(ctx context.Context, networkName, networkID, taskID, containerID string) error {
c.RLock()
attacher, ok := c.attachers[attacherKey(networkName, containerID)]
if !ok {
attacher, ok = c.attachers[attacherKey(networkID, containerID)]
}
if c.node == nil || c.node.Agent() == nil {
c.RUnlock()
return fmt.Errorf("invalid cluster node while waiting for detachment")
}
agent := c.node.Agent()
c.RUnlock()
if ok && attacher != nil && attacher.detachWaitCh != nil {
select {
case <-attacher.detachWaitCh:
case <-ctx.Done():
return ctx.Err()
}
}
return agent.ResourceAllocator().DetachNetwork(ctx, taskID)
}
// AttachNetwork generates an attachment request towards the manager.
func (c *Cluster) AttachNetwork(target string, containerID string, addresses []string) (*network.NetworkingConfig, error) {
aKey := attacherKey(target, containerID)
c.Lock()
if c.node == nil || c.node.Agent() == nil {
c.Unlock()
return nil, fmt.Errorf("invalid cluster node while attaching to network")
}
if attacher, ok := c.attachers[aKey]; ok {
c.Unlock()
return attacher.config, nil
}
agent := c.node.Agent()
attachWaitCh := make(chan *network.NetworkingConfig)
detachWaitCh := make(chan struct{})
c.attachers[aKey] = &attacher{
attachWaitCh: attachWaitCh,
detachWaitCh: detachWaitCh,
}
c.Unlock()
ctx, cancel := c.getRequestContext()
defer cancel()
taskID, err := agent.ResourceAllocator().AttachNetwork(ctx, containerID, target, addresses)
if err != nil {
c.Lock()
delete(c.attachers, aKey)
c.Unlock()
return nil, fmt.Errorf("Could not attach to network %s: %v", target, err)
}
logrus.Debugf("Successfully attached to network %s with tid %s", target, taskID)
var config *network.NetworkingConfig
select {
case config = <-attachWaitCh:
case <-ctx.Done():
return nil, fmt.Errorf("attaching to network failed, make sure your network options are correct and check manager logs: %v", ctx.Err())
}
c.Lock()
c.attachers[aKey].taskID = taskID
c.attachers[aKey].config = config
c.Unlock()
return config, nil
}
// DetachNetwork unblocks the waiters waiting on WaitForDetachment so
// that a request to detach can be generated towards the manager.
func (c *Cluster) DetachNetwork(target string, containerID string) error {
aKey := attacherKey(target, containerID)
c.Lock()
attacher, ok := c.attachers[aKey]
delete(c.attachers, aKey)
c.Unlock()
if !ok {
return fmt.Errorf("could not find network attachment for container %s to network %s", containerID, target)
}
close(attacher.detachWaitCh)
return nil
}
// CreateNetwork creates a new cluster managed network.
func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error) {
c.RLock()
@ -1262,7 +1390,14 @@ func (c *Cluster) RemoveNetwork(input string) error {
}
func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.ControlClient, s *types.ServiceSpec) error {
for i, n := range s.Networks {
// Always prefer NetworkAttachmentConfigs from TaskTemplate
// but fallback to service spec for backward compatibility
networks := s.TaskTemplate.Networks
if len(networks) == 0 {
networks = s.Networks
}
for i, n := range networks {
apiNetwork, err := getNetwork(ctx, client, n.Target)
if err != nil {
if ln, _ := c.config.Backend.FindNetwork(n.Target); ln != nil && !ln.Info().Dynamic() {
@ -1271,7 +1406,7 @@ func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.Control
}
return err
}
s.Networks[i].Target = apiNetwork.ID
networks[i].Target = apiNetwork.ID
}
return nil
}

View File

@ -27,6 +27,7 @@ func networkFromGRPC(n *swarmapi.Network) types.Network {
Spec: types.NetworkSpec{
IPv6Enabled: n.Spec.Ipv6Enabled,
Internal: n.Spec.Internal,
Attachable: n.Spec.Attachable,
IPAMOptions: ipamFromGRPC(n.Spec.IPAM),
},
IPAMOptions: ipamFromGRPC(n.IPAM),
@ -155,6 +156,7 @@ func BasicNetworkFromGRPC(n swarmapi.Network) basictypes.NetworkResource {
EnableIPv6: spec.Ipv6Enabled,
IPAM: ipam,
Internal: spec.Internal,
Attachable: spec.Attachable,
Labels: n.Spec.Annotations.Labels,
}
@ -179,6 +181,7 @@ func BasicNetworkCreateToGRPC(create basictypes.NetworkCreateRequest) swarmapi.N
},
Ipv6Enabled: create.EnableIPv6,
Internal: create.Internal,
Attachable: create.Attachable,
}
if create.IPAM != nil {
ns.IPAM = &swarmapi.IPAMOptions{

View File

@ -15,10 +15,16 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
spec := s.Spec
containerConfig := spec.Task.Runtime.(*swarmapi.TaskSpec_Container).Container
networks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
serviceNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
for _, n := range spec.Networks {
networks = append(networks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
serviceNetworks = append(serviceNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
}
taskNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Task.Networks))
for _, n := range spec.Task.Networks {
taskNetworks = append(taskNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
}
service := types.Service{
ID: s.ID,
@ -29,9 +35,10 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
RestartPolicy: restartPolicyFromGRPC(s.Spec.Task.Restart),
Placement: placementFromGRPC(s.Spec.Task.Placement),
LogDriver: driverFromGRPC(s.Spec.Task.LogDriver),
Networks: taskNetworks,
},
Networks: networks,
Networks: serviceNetworks,
EndpointSpec: endpointSpecFromGRPC(s.Spec.Endpoint),
},
Endpoint: endpointFromGRPC(s.Endpoint),
@ -99,9 +106,14 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
name = namesgenerator.GetRandomName(0)
}
networks := make([]*swarmapi.ServiceSpec_NetworkAttachmentConfig, 0, len(s.Networks))
serviceNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.Networks))
for _, n := range s.Networks {
networks = append(networks, &swarmapi.ServiceSpec_NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
serviceNetworks = append(serviceNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
}
taskNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.TaskTemplate.Networks))
for _, n := range s.TaskTemplate.Networks {
taskNetworks = append(taskNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
}
spec := swarmapi.ServiceSpec{
@ -112,8 +124,9 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
Task: swarmapi.TaskSpec{
Resources: resourcesToGRPC(s.TaskTemplate.Resources),
LogDriver: driverToGRPC(s.TaskTemplate.LogDriver),
Networks: taskNetworks,
},
Networks: networks,
Networks: serviceNetworks,
}
containerSpec, err := containerToGRPC(s.TaskTemplate.ContainerSpec)

View File

@ -12,6 +12,11 @@ import (
func TaskFromGRPC(t swarmapi.Task) types.Task {
containerConfig := t.Spec.Runtime.(*swarmapi.TaskSpec_Container).Container
containerStatus := t.Status.GetContainer()
networks := make([]types.NetworkAttachmentConfig, 0, len(t.Spec.Networks))
for _, n := range t.Spec.Networks {
networks = append(networks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
}
task := types.Task{
ID: t.ID,
ServiceID: t.ServiceID,
@ -23,6 +28,7 @@ func TaskFromGRPC(t swarmapi.Task) types.Task {
RestartPolicy: restartPolicyFromGRPC(t.Spec.Restart),
Placement: placementFromGRPC(t.Spec.Placement),
LogDriver: driverFromGRPC(t.Spec.LogDriver),
Networks: networks,
},
Status: types.TaskStatus{
State: types.TaskState(strings.ToLower(t.Status.State.String())),

View File

@ -40,4 +40,6 @@ type Backend interface {
IsSwarmCompatible() error
SubscribeToEvents(since, until time.Time, filter filters.Args) ([]events.Message, chan interface{})
UnsubscribeFromEvents(listener chan interface{})
UpdateAttachment(string, string, string, *network.NetworkingConfig) error
WaitForDetachment(context.Context, string, string, string, string) error
}

View File

@ -144,6 +144,44 @@ func (c *containerAdapter) removeNetworks(ctx context.Context) error {
return nil
}
func (c *containerAdapter) networkAttach(ctx context.Context) error {
config := c.container.createNetworkingConfig()
var (
networkName string
networkID string
)
if config != nil {
for n, epConfig := range config.EndpointsConfig {
networkName = n
networkID = epConfig.NetworkID
break
}
}
return c.backend.UpdateAttachment(networkName, networkID, c.container.id(), config)
}
func (c *containerAdapter) waitForDetach(ctx context.Context) error {
config := c.container.createNetworkingConfig()
var (
networkName string
networkID string
)
if config != nil {
for n, epConfig := range config.EndpointsConfig {
networkName = n
networkID = epConfig.NetworkID
break
}
}
return c.backend.WaitForDetachment(ctx, networkName, networkID, c.container.taskID(), c.container.id())
}
func (c *containerAdapter) create(ctx context.Context) error {
var cr types.ContainerCreateResponse
var err error
@ -233,7 +271,7 @@ func (c *containerAdapter) events(ctx context.Context) <-chan events.Message {
}
func (c *containerAdapter) wait(ctx context.Context) error {
return c.backend.ContainerWaitWithContext(ctx, c.container.name())
return c.backend.ContainerWaitWithContext(ctx, c.container.nameOrID())
}
func (c *containerAdapter) shutdown(ctx context.Context) error {

View File

@ -0,0 +1,80 @@
package container
import (
executorpkg "github.com/docker/docker/daemon/cluster/executor"
"github.com/docker/swarmkit/api"
"golang.org/x/net/context"
)
// networkAttacherController implements agent.Controller against docker's API.
//
// networkAttacherController manages the lifecycle of network
// attachment of a docker unmanaged container managed as a task from
// agent point of view. It provides network attachment information to
// the unmanaged container for it to attach to the network and run.
type networkAttacherController struct {
backend executorpkg.Backend
task *api.Task
adapter *containerAdapter
closed chan struct{}
}
func newNetworkAttacherController(b executorpkg.Backend, task *api.Task) (*networkAttacherController, error) {
adapter, err := newContainerAdapter(b, task)
if err != nil {
return nil, err
}
return &networkAttacherController{
backend: b,
task: task,
adapter: adapter,
closed: make(chan struct{}),
}, nil
}
func (nc *networkAttacherController) Update(ctx context.Context, t *api.Task) error {
return nil
}
func (nc *networkAttacherController) Prepare(ctx context.Context) error {
// Make sure all the networks that the task needs are created.
if err := nc.adapter.createNetworks(ctx); err != nil {
return err
}
return nil
}
func (nc *networkAttacherController) Start(ctx context.Context) error {
return nc.adapter.networkAttach(ctx)
}
func (nc *networkAttacherController) Wait(pctx context.Context) error {
ctx, cancel := context.WithCancel(pctx)
defer cancel()
return nc.adapter.waitForDetach(ctx)
}
func (nc *networkAttacherController) Shutdown(ctx context.Context) error {
return nil
}
func (nc *networkAttacherController) Terminate(ctx context.Context) error {
return nil
}
func (nc *networkAttacherController) Remove(ctx context.Context) error {
// Try removing the network referenced in this task in case this
// task is the last one referencing it
if err := nc.adapter.removeNetworks(ctx); err != nil {
return err
}
return nil
}
func (nc *networkAttacherController) Close() error {
return nil
}

View File

@ -44,17 +44,19 @@ func newContainerConfig(t *api.Task) (*containerConfig, error) {
}
func (c *containerConfig) setTask(t *api.Task) error {
container := t.Spec.GetContainer()
if container == nil {
if t.Spec.GetContainer() == nil && t.Spec.GetAttachment() == nil {
return exec.ErrRuntimeUnsupported
}
if container.Image == "" {
return ErrImageRequired
}
container := t.Spec.GetContainer()
if container != nil {
if container.Image == "" {
return ErrImageRequired
}
if err := validateMounts(container.Mounts); err != nil {
return err
if err := validateMounts(container.Mounts); err != nil {
return err
}
}
// index the networks by name
@ -67,6 +69,19 @@ func (c *containerConfig) setTask(t *api.Task) error {
return nil
}
func (c *containerConfig) id() string {
attachment := c.task.Spec.GetAttachment()
if attachment == nil {
return ""
}
return attachment.ContainerID
}
func (c *containerConfig) taskID() string {
return c.task.ID
}
func (c *containerConfig) endpoint() *api.Endpoint {
return c.task.Endpoint
}
@ -75,6 +90,14 @@ func (c *containerConfig) spec() *api.ContainerSpec {
return c.task.Spec.GetContainer()
}
func (c *containerConfig) nameOrID() string {
if c.task.Spec.GetContainer() != nil {
return c.name()
}
return c.id()
}
func (c *containerConfig) name() string {
if c.task.Annotations.Name != "" {
// if set, use the container Annotations.Name field, set in the orchestrator.
@ -342,7 +365,7 @@ func (c *containerConfig) resources() enginecontainer.Resources {
// Docker daemon supports just 1 network during container create.
func (c *containerConfig) createNetworkingConfig() *network.NetworkingConfig {
var networks []*api.NetworkAttachment
if c.task.Spec.GetContainer() != nil {
if c.task.Spec.GetContainer() != nil || c.task.Spec.GetAttachment() != nil {
networks = c.task.Networks
}
@ -392,6 +415,7 @@ func getEndpointConfig(na *api.NetworkAttachment) *network.EndpointSettings {
}
return &network.EndpointSettings{
NetworkID: na.Network.ID,
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: ipv4,
IPv6Address: ipv6,

View File

@ -121,6 +121,10 @@ func (e *executor) Configure(ctx context.Context, node *api.Node) error {
// Controller returns a docker container runner.
func (e *executor) Controller(t *api.Task) (exec.Controller, error) {
if t.Spec.GetAttachment() != nil {
return newNetworkAttacherController(e.backend, t)
}
ctlr, err := newController(e.backend, t)
if err != nil {
return nil, err

View File

@ -178,7 +178,7 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container) ([]lib
// return if this call to build join options is not for default bridge network
// Legacy Link is only supported by docker run --link
bridgeSettings, ok := container.NetworkSettings.Networks[defaultNetName]
if !ok {
if !ok || bridgeSettings.EndpointSettings == nil {
return sboxOptions, nil
}
@ -238,9 +238,9 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container) ([]lib
return sboxOptions, nil
}
func (daemon *Daemon) updateNetworkSettings(container *container.Container, n libnetwork.Network) error {
func (daemon *Daemon) updateNetworkSettings(container *container.Container, n libnetwork.Network, endpointConfig *networktypes.EndpointSettings) error {
if container.NetworkSettings == nil {
container.NetworkSettings = &network.Settings{Networks: make(map[string]*networktypes.EndpointSettings)}
container.NetworkSettings = &network.Settings{Networks: make(map[string]*network.EndpointSettings)}
}
if !container.HostConfig.NetworkMode.IsHost() && containertypes.NetworkMode(n.Type()).IsHost() {
@ -268,7 +268,9 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
}
if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
container.NetworkSettings.Networks[n.Name()] = &network.EndpointSettings{
EndpointSettings: endpointConfig,
}
}
return nil
@ -331,12 +333,63 @@ func errClusterNetworkOnRun(n string) error {
return fmt.Errorf("swarm-scoped network (%s) is not compatible with `docker create` or `docker run`. This network can only be used by a docker service", n)
}
func (daemon *Daemon) findAndAttachNetwork(container *container.Container, idOrName string, epConfig *networktypes.EndpointSettings) (libnetwork.Network, *networktypes.NetworkingConfig, error) {
n, err := daemon.FindNetwork(idOrName)
if err != nil {
// We should always be able to find the network for a
// managed container.
if container.Managed {
return nil, nil, err
}
}
// If we found a network and if it is not dynamically created
// we should never attempt to attach to that network here.
if n != nil {
if container.Managed || !n.Info().Dynamic() {
return n, nil, nil
}
}
var addresses []string
if epConfig != nil && epConfig.IPAMConfig != nil {
if epConfig.IPAMConfig.IPv4Address != "" {
addresses = append(addresses, epConfig.IPAMConfig.IPv4Address)
}
if epConfig.IPAMConfig.IPv6Address != "" {
addresses = append(addresses, epConfig.IPAMConfig.IPv6Address)
}
}
// In all other cases, attempt to attach to the network to
// trigger attachment in the swarm cluster manager.
var config *networktypes.NetworkingConfig
if daemon.clusterProvider != nil {
var err error
config, err = daemon.clusterProvider.AttachNetwork(idOrName, container.ID, addresses)
if err != nil {
return nil, nil, err
}
}
n, err = daemon.FindNetwork(idOrName)
if err != nil {
if daemon.clusterProvider != nil {
if err := daemon.clusterProvider.DetachNetwork(idOrName, container.ID); err != nil {
logrus.Warnf("Could not rollback attachment for container %s to network %s: %v", container.ID, idOrName, err)
}
}
return nil, nil, err
}
return n, config, nil
}
// updateContainerNetworkSettings update the network settings
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
var (
n libnetwork.Network
err error
)
var n libnetwork.Network
mode := container.HostConfig.NetworkMode
if container.Config.NetworkDisabled || mode.IsContainer() {
@ -347,26 +400,48 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
if mode.IsDefault() {
networkName = daemon.netController.Config().Daemon.DefaultNetwork
}
if mode.IsUserDefined() {
var err error
n, err = daemon.FindNetwork(networkName)
if err != nil {
return err
if err == nil {
networkName = n.Name()
}
if !container.Managed && n.Info().Dynamic() {
return errClusterNetworkOnRun(networkName)
}
networkName = n.Name()
}
if container.NetworkSettings == nil {
container.NetworkSettings = &network.Settings{}
}
if len(endpointsConfig) > 0 {
container.NetworkSettings.Networks = endpointsConfig
if container.NetworkSettings.Networks == nil {
container.NetworkSettings.Networks = make(map[string]*network.EndpointSettings)
}
for name, epConfig := range endpointsConfig {
container.NetworkSettings.Networks[name] = &network.EndpointSettings{
EndpointSettings: epConfig,
}
}
}
if container.NetworkSettings.Networks == nil {
container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
container.NetworkSettings.Networks = make(map[string]*network.EndpointSettings)
container.NetworkSettings.Networks[networkName] = &network.EndpointSettings{
EndpointSettings: &networktypes.EndpointSettings{},
}
}
// Convert any settings added by client in default name to
// engine's default network name key
if mode.IsDefault() {
if nConf, ok := container.NetworkSettings.Networks[mode.NetworkName()]; ok {
container.NetworkSettings.Networks[networkName] = nConf
delete(container.NetworkSettings.Networks, mode.NetworkName())
}
}
if !mode.IsUserDefined() {
return nil
}
@ -374,10 +449,13 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
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
if n != 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
@ -414,16 +492,27 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
// on first network connecting.
defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName()
if nConf, ok := container.NetworkSettings.Networks[defaultNetName]; ok {
if err := daemon.connectToNetwork(container, defaultNetName, nConf, updateSettings); err != nil {
if err := daemon.connectToNetwork(container, defaultNetName, nConf.EndpointSettings, updateSettings); err != nil {
return err
}
}
for n, nConf := range container.NetworkSettings.Networks {
var (
networks []string
epConfigs []*network.EndpointSettings
)
for n, epConf := range container.NetworkSettings.Networks {
if n == defaultNetName {
continue
}
if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
networks = append(networks, n)
epConfigs = append(epConfigs, epConf)
}
for i, epConf := range epConfigs {
if err := daemon.connectToNetwork(container, networks[i], epConf.EndpointSettings, updateSettings); err != nil {
return err
}
}
@ -488,7 +577,7 @@ func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.Endpo
}
// cleanOperationalData resets the operational data from the passed endpoint settings
func cleanOperationalData(es *networktypes.EndpointSettings) {
func cleanOperationalData(es *network.EndpointSettings) {
es.EndpointID = ""
es.Gateway = ""
es.IPAddress = ""
@ -497,25 +586,18 @@ func cleanOperationalData(es *networktypes.EndpointSettings) {
es.GlobalIPv6Address = ""
es.GlobalIPv6PrefixLen = 0
es.MacAddress = ""
if es.IPAMOperational {
es.IPAMConfig = nil
}
}
func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (libnetwork.Network, error) {
if container.HostConfig.NetworkMode.IsContainer() {
return nil, runconfig.ErrConflictSharedNetwork
}
if containertypes.NetworkMode(idOrName).IsBridge() &&
daemon.configStore.DisableBridge {
container.Config.NetworkDisabled = true
return nil, nil
}
if !containertypes.NetworkMode(idOrName).IsUserDefined() {
func (daemon *Daemon) updateNetworkConfig(container *container.Container, n libnetwork.Network, endpointConfig *networktypes.EndpointSettings, updateSettings bool) error {
if !containertypes.NetworkMode(n.Name()).IsUserDefined() {
if hasUserDefinedIPAddress(endpointConfig) && !enableIPOnPredefinedNetwork() {
return nil, runconfig.ErrUnsupportedNetworkAndIP
return runconfig.ErrUnsupportedNetworkAndIP
}
if endpointConfig != nil && len(endpointConfig.Aliases) > 0 {
return nil, runconfig.ErrUnsupportedNetworkAndAlias
return runconfig.ErrUnsupportedNetworkAndAlias
}
} else {
addShortID := true
@ -531,28 +613,34 @@ func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrNa
}
}
n, err := daemon.FindNetwork(idOrName)
if err != nil {
return nil, err
}
if err := validateNetworkingConfig(n, endpointConfig); err != nil {
return nil, err
return err
}
if updateSettings {
if err := daemon.updateNetworkSettings(container, n); err != nil {
return nil, err
if err := daemon.updateNetworkSettings(container, n, endpointConfig); err != nil {
return err
}
}
return n, nil
return nil
}
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).IsBridge() &&
daemon.configStore.DisableBridge {
container.Config.NetworkDisabled = true
return nil
}
if endpointConfig == nil {
endpointConfig = &networktypes.EndpointSettings{}
}
n, err := daemon.updateNetworkConfig(container, idOrName, endpointConfig, updateSettings)
n, config, err := daemon.findAndAttachNetwork(container, idOrName, endpointConfig)
if err != nil {
return err
}
@ -560,6 +648,25 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
return nil
}
var operIPAM bool
if config != nil {
if epConfig, ok := config.EndpointsConfig[n.Name()]; ok {
if endpointConfig.IPAMConfig == nil ||
(endpointConfig.IPAMConfig.IPv4Address == "" &&
endpointConfig.IPAMConfig.IPv6Address == "" &&
len(endpointConfig.IPAMConfig.LinkLocalIPs) == 0) {
operIPAM = true
}
endpointConfig = epConfig
}
}
err = daemon.updateNetworkConfig(container, n, endpointConfig, updateSettings)
if err != nil {
return err
}
controller := daemon.netController
sb := daemon.getNetworkSandbox(container)
@ -580,7 +687,13 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
}
}
}()
container.NetworkSettings.Networks[n.Name()] = endpointConfig
container.NetworkSettings.Networks[n.Name()] = &network.EndpointSettings{
EndpointSettings: endpointConfig,
IPAMOperational: operIPAM,
}
if _, ok := container.NetworkSettings.Networks[n.ID()]; ok {
delete(container.NetworkSettings.Networks, n.ID())
}
if err := daemon.updateEndpointNetworkSettings(container, n, ep); err != nil {
return err
@ -632,7 +745,7 @@ func (daemon *Daemon) ForceEndpointDelete(name string, networkName string) error
return ep.Delete(true)
}
func disconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error {
func (daemon *Daemon) disconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error {
var (
ep libnetwork.Endpoint
sbox libnetwork.Sandbox
@ -678,6 +791,13 @@ func disconnectFromNetwork(container *container.Container, n libnetwork.Network,
}
delete(container.NetworkSettings.Networks, n.Name())
if daemon.clusterProvider != nil && n.Info().Dynamic() && !container.Managed {
if err := daemon.clusterProvider.DetachNetwork(n.Name(), container.ID); err != nil {
logrus.Warnf("error detaching from network %s: %v", n, err)
}
}
return nil
}
@ -751,6 +871,11 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
if nw, err := daemon.FindNetwork(n); err == nil {
networks = append(networks, nw)
}
if epSettings.EndpointSettings == nil {
continue
}
cleanOperationalData(epSettings)
}
@ -765,6 +890,12 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
}
for _, nw := range networks {
if daemon.clusterProvider != nil && nw.Info().Dynamic() && !container.Managed {
if err := daemon.clusterProvider.DetachNetwork(nw.Name(), container.ID); err != nil {
logrus.Warnf("error detaching from network %s: %v", nw.Name(), err)
}
}
attributes := map[string]string{
"container": container.ID,
}

View File

@ -16,6 +16,7 @@ import (
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/links"
"github.com/docker/docker/daemon/network"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
@ -35,7 +36,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
children := daemon.children(container)
bridgeSettings := container.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()]
if bridgeSettings == nil {
if bridgeSettings == nil || bridgeSettings.EndpointSettings == nil {
return nil, nil
}
@ -45,7 +46,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
}
childBridgeSettings := child.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()]
if childBridgeSettings == nil {
if childBridgeSettings == nil || childBridgeSettings.EndpointSettings == nil {
return nil, fmt.Errorf("container %s not attached to default bridge network", child.ID)
}
@ -107,10 +108,17 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
if container.RemovalInProgress || container.Dead {
return errRemovalContainer(container.ID)
}
if _, err := daemon.updateNetworkConfig(container, idOrName, endpointConfig, true); err != nil {
return err
n, err := daemon.FindNetwork(idOrName)
if err == nil && n != nil {
if err := daemon.updateNetworkConfig(container, n, endpointConfig, true); err != nil {
return err
}
} else {
container.NetworkSettings.Networks[idOrName] = &network.EndpointSettings{
EndpointSettings: endpointConfig,
}
}
container.NetworkSettings.Networks[idOrName] = endpointConfig
} else {
if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
return err
@ -143,7 +151,7 @@ func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, netw
return runconfig.ErrConflictHostNetwork
}
if err := disconnectFromNetwork(container, n, false); err != nil {
if err := daemon.disconnectFromNetwork(container, n, false); err != nil {
return err
}
} else {

View File

@ -252,7 +252,7 @@ func (daemon *Daemon) verifyNetworkingConfig(nwConfig *networktypes.NetworkingCo
}
if len(nwConfig.EndpointsConfig) == 1 {
for _, v := range nwConfig.EndpointsConfig {
if v.IPAMConfig != nil {
if v != nil && v.IPAMConfig != nil {
if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil {
return errors.NewBadRequestError(fmt.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address))
}

View File

@ -42,6 +42,13 @@ func (daemon *Daemon) ContainerInspectCurrent(name string, size bool) (*types.Co
return nil, err
}
apiNetworks := make(map[string]*networktypes.EndpointSettings)
for name, epConf := range container.NetworkSettings.Networks {
if epConf.EndpointSettings != nil {
apiNetworks[name] = epConf.EndpointSettings
}
}
mountPoints := addMountPoints(container)
networkSettings := &types.NetworkSettings{
NetworkSettingsBase: types.NetworkSettingsBase{
@ -56,7 +63,7 @@ func (daemon *Daemon) ContainerInspectCurrent(name string, size bool) (*types.Co
SecondaryIPv6Addresses: container.NetworkSettings.SecondaryIPv6Addresses,
},
DefaultNetworkSettings: daemon.getDefaultNetworkSettings(container.NetworkSettings.Networks),
Networks: container.NetworkSettings.Networks,
Networks: apiNetworks,
}
return &types.ContainerJSON{
@ -236,10 +243,10 @@ func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Se
// getDefaultNetworkSettings creates the deprecated structure that holds the information
// about the bridge network for a container.
func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*networktypes.EndpointSettings) types.DefaultNetworkSettings {
func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*network.EndpointSettings) types.DefaultNetworkSettings {
var settings types.DefaultNetworkSettings
if defaultNetwork, ok := networks["bridge"]; ok {
if defaultNetwork, ok := networks["bridge"]; ok && defaultNetwork.EndpointSettings != nil {
settings.EndpointID = defaultNetwork.EndpointID
settings.Gateway = defaultNetwork.Gateway
settings.GlobalIPv6Address = defaultNetwork.GlobalIPv6Address

View File

@ -400,6 +400,9 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
return networkExist
}
for _, nw := range container.NetworkSettings.Networks {
if nw.EndpointSettings == nil {
continue
}
if nw.NetworkID == value {
return networkExist
}
@ -460,7 +463,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li
// copy networks to avoid races
networks := make(map[string]*networktypes.EndpointSettings)
for name, network := range container.NetworkSettings.Networks {
if network == nil {
if network == nil || network.EndpointSettings == nil {
continue
}
networks[name] = &networktypes.EndpointSettings{

View File

@ -14,6 +14,7 @@ import (
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork"
networktypes "github.com/docker/libnetwork/types"
"golang.org/x/net/context"
)
// NetworkControllerEnabled checks if the networking stack is enabled.
@ -186,6 +187,29 @@ func (daemon *Daemon) SetNetworkBootstrapKeys(keys []*networktypes.EncryptionKey
return daemon.netController.SetKeys(keys)
}
// UpdateAttachment notifies the attacher about the attachment config.
func (daemon *Daemon) UpdateAttachment(networkName, networkID, containerID string, config *network.NetworkingConfig) error {
if daemon.clusterProvider == nil {
return fmt.Errorf("cluster provider is not initialized")
}
if err := daemon.clusterProvider.UpdateAttachment(networkName, containerID, config); err != nil {
return daemon.clusterProvider.UpdateAttachment(networkID, containerID, config)
}
return nil
}
// WaitForDetachment makes the cluster manager wait for detachment of
// the container from the network.
func (daemon *Daemon) WaitForDetachment(ctx context.Context, networkName, networkID, taskID, containerID string) error {
if daemon.clusterProvider == nil {
return fmt.Errorf("cluster provider is not initialized")
}
return daemon.clusterProvider.WaitForDetachment(ctx, networkName, networkID, taskID, containerID)
}
// CreateManagedNetwork creates an agent network.
func (daemon *Daemon) CreateManagedNetwork(create clustertypes.NetworkCreateRequest) error {
_, err := daemon.createNetwork(create.NetworkCreateRequest, create.ID, true)

View File

@ -14,7 +14,7 @@ type Settings struct {
HairpinMode bool
LinkLocalIPv6Address string
LinkLocalIPv6PrefixLen int
Networks map[string]*networktypes.EndpointSettings
Networks map[string]*EndpointSettings
Service *clustertypes.ServiceConfig
Ports nat.PortMap
SandboxKey string
@ -22,3 +22,11 @@ type Settings struct {
SecondaryIPv6Addresses []networktypes.Address
IsAnonymousEndpoint bool
}
// EndpointSettings is a package local wrapper for
// networktypes.EndpointSettings which stores Endpoint state that
// needs to be persisted to disk but not exposed in the api.
type EndpointSettings struct {
*networktypes.EndpointSettings
IPAMOperational bool
}

View File

@ -298,6 +298,8 @@ func (s *DockerSwarmSuite) TearDownTest(c *check.C) {
if err := os.RemoveAll(walDir); err != nil {
c.Logf("error removing %v: %v", walDir, err)
}
cleanupExecRoot(c, d.execRoot)
}
s.daemons = nil
s.daemonsLock.Unlock()

View File

@ -2,7 +2,29 @@
package main
import "syscall"
import (
"os"
"path/filepath"
"syscall"
"github.com/go-check/check"
)
func cleanupExecRoot(c *check.C, execRoot string) {
// Cleanup network namespaces in the exec root of this
// daemon because this exec root is specific to this
// daemon instance and has no chance of getting
// cleaned up when a new daemon is instantiated with a
// new exec root.
netnsPath := filepath.Join(execRoot, "netns")
filepath.Walk(netnsPath, func(path string, info os.FileInfo, err error) error {
if err := syscall.Unmount(path, syscall.MNT_FORCE); err != nil {
c.Logf("unmount of %s failed: %v", path, err)
}
os.Remove(path)
return nil
})
}
func signalDaemonDump(pid int) {
syscall.Kill(pid, syscall.SIGQUIT)

View File

@ -5,6 +5,8 @@ import (
"strconv"
"syscall"
"unsafe"
"github.com/go-check/check"
)
func openEvent(desiredAccess uint32, inheritHandle bool, name string, proc *syscall.LazyProc) (handle syscall.Handle, err error) {
@ -45,3 +47,6 @@ func signalDaemonDump(pid int) {
func signalDaemonReload(pid int) error {
return fmt.Errorf("daemon reload not supported")
}
func cleanupExecRoot(c *check.C, execRoot string) {
}