From f0d30a4b26d88013f3c6d31ea503adb41730a88b Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan Date: Tue, 23 Aug 2016 16:50:15 -0700 Subject: [PATCH] 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 Upstream-commit: 99a98ccc14a9427be47c8006e130750710db0a16 Component: engine --- .../engine/api/client/network/create.go | 3 + components/engine/api/client/service/opts.go | 5 +- .../server/router/network/network_routes.go | 21 +- components/engine/container/container.go | 4 +- components/engine/daemon/cluster/cluster.go | 139 ++++++++++- .../engine/daemon/cluster/convert/network.go | 3 + .../engine/daemon/cluster/convert/service.go | 25 +- .../engine/daemon/cluster/convert/task.go | 6 + .../engine/daemon/cluster/executor/backend.go | 2 + .../cluster/executor/container/adapter.go | 40 ++- .../cluster/executor/container/attachment.go | 80 ++++++ .../cluster/executor/container/container.go | 40 ++- .../cluster/executor/container/executor.go | 4 + .../engine/daemon/container_operations.go | 233 ++++++++++++++---- .../daemon/container_operations_unix.go | 20 +- components/engine/daemon/create.go | 2 +- components/engine/daemon/inspect.go | 13 +- components/engine/daemon/list.go | 5 +- components/engine/daemon/network.go | 24 ++ components/engine/daemon/network/settings.go | 10 +- .../engine/integration-cli/check_test.go | 2 + .../engine/integration-cli/daemon_unix.go | 24 +- .../engine/integration-cli/daemon_windows.go | 5 + 23 files changed, 606 insertions(+), 104 deletions(-) create mode 100644 components/engine/daemon/cluster/executor/container/attachment.go diff --git a/components/engine/api/client/network/create.go b/components/engine/api/client/network/create.go index 9bec170a7c..8de2c9cd4c 100644 --- a/components/engine/api/client/network/create.go +++ b/components/engine/api/client/network/create.go @@ -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), } diff --git a/components/engine/api/client/service/opts.go b/components/engine/api/client/service/opts.go index 7151bca4b0..7236980e80 100644 --- a/components/engine/api/client/service/opts.go +++ b/components/engine/api/client/service/opts.go @@ -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(), } diff --git a/components/engine/api/server/router/network/network_routes.go b/components/engine/api/server/router/network/network_routes.go index 1e508ab302..d6e7334800 100644 --- a/components/engine/api/server/router/network/network_routes.go +++ b/components/engine/api/server/router/network/network_routes.go @@ -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) } diff --git a/components/engine/container/container.go b/components/engine/container/container.go index b4c59f4ab7..243450db3f 100644 --- a/components/engine/container/container.go +++ b/components/engine/container/container.go @@ -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() diff --git a/components/engine/daemon/cluster/cluster.go b/components/engine/daemon/cluster/cluster.go index 5b84cef1d1..8106115837 100644 --- a/components/engine/daemon/cluster/cluster.go +++ b/components/engine/daemon/cluster/cluster.go @@ -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 } diff --git a/components/engine/daemon/cluster/convert/network.go b/components/engine/daemon/cluster/convert/network.go index decc320151..f643f620a6 100644 --- a/components/engine/daemon/cluster/convert/network.go +++ b/components/engine/daemon/cluster/convert/network.go @@ -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{ diff --git a/components/engine/daemon/cluster/convert/service.go b/components/engine/daemon/cluster/convert/service.go index 5c20bf6c4f..b87b3259c9 100644 --- a/components/engine/daemon/cluster/convert/service.go +++ b/components/engine/daemon/cluster/convert/service.go @@ -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) diff --git a/components/engine/daemon/cluster/convert/task.go b/components/engine/daemon/cluster/convert/task.go index f23bccb545..2d60458ac9 100644 --- a/components/engine/daemon/cluster/convert/task.go +++ b/components/engine/daemon/cluster/convert/task.go @@ -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())), diff --git a/components/engine/daemon/cluster/executor/backend.go b/components/engine/daemon/cluster/executor/backend.go index af09be55d4..756e1bb30e 100644 --- a/components/engine/daemon/cluster/executor/backend.go +++ b/components/engine/daemon/cluster/executor/backend.go @@ -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 } diff --git a/components/engine/daemon/cluster/executor/container/adapter.go b/components/engine/daemon/cluster/executor/container/adapter.go index 85a7017ba1..bac9a1542c 100644 --- a/components/engine/daemon/cluster/executor/container/adapter.go +++ b/components/engine/daemon/cluster/executor/container/adapter.go @@ -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 { diff --git a/components/engine/daemon/cluster/executor/container/attachment.go b/components/engine/daemon/cluster/executor/container/attachment.go new file mode 100644 index 0000000000..f3b738f70b --- /dev/null +++ b/components/engine/daemon/cluster/executor/container/attachment.go @@ -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 +} diff --git a/components/engine/daemon/cluster/executor/container/container.go b/components/engine/daemon/cluster/executor/container/container.go index c48873121b..c0415fa2bf 100644 --- a/components/engine/daemon/cluster/executor/container/container.go +++ b/components/engine/daemon/cluster/executor/container/container.go @@ -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, diff --git a/components/engine/daemon/cluster/executor/container/executor.go b/components/engine/daemon/cluster/executor/container/executor.go index 2295c44108..844821b83e 100644 --- a/components/engine/daemon/cluster/executor/container/executor.go +++ b/components/engine/daemon/cluster/executor/container/executor.go @@ -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 diff --git a/components/engine/daemon/container_operations.go b/components/engine/daemon/container_operations.go index 68bd1b458a..818c82ccbf 100644 --- a/components/engine/daemon/container_operations.go +++ b/components/engine/daemon/container_operations.go @@ -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, } diff --git a/components/engine/daemon/container_operations_unix.go b/components/engine/daemon/container_operations_unix.go index fc44932d4e..b39115c710 100644 --- a/components/engine/daemon/container_operations_unix.go +++ b/components/engine/daemon/container_operations_unix.go @@ -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 { diff --git a/components/engine/daemon/create.go b/components/engine/daemon/create.go index b9dbc8e83d..81cb7ac365 100644 --- a/components/engine/daemon/create.go +++ b/components/engine/daemon/create.go @@ -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)) } diff --git a/components/engine/daemon/inspect.go b/components/engine/daemon/inspect.go index 3577beaaea..e3fe03da76 100644 --- a/components/engine/daemon/inspect.go +++ b/components/engine/daemon/inspect.go @@ -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 diff --git a/components/engine/daemon/list.go b/components/engine/daemon/list.go index 91ccdd9adc..cb634333c3 100644 --- a/components/engine/daemon/list.go +++ b/components/engine/daemon/list.go @@ -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{ diff --git a/components/engine/daemon/network.go b/components/engine/daemon/network.go index 48015c00a8..b15906b519 100644 --- a/components/engine/daemon/network.go +++ b/components/engine/daemon/network.go @@ -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) diff --git a/components/engine/daemon/network/settings.go b/components/engine/daemon/network/settings.go index 4320573c9a..1958f2330a 100644 --- a/components/engine/daemon/network/settings.go +++ b/components/engine/daemon/network/settings.go @@ -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 +} diff --git a/components/engine/integration-cli/check_test.go b/components/engine/integration-cli/check_test.go index 3b5965e571..e33fc1f035 100644 --- a/components/engine/integration-cli/check_test.go +++ b/components/engine/integration-cli/check_test.go @@ -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() diff --git a/components/engine/integration-cli/daemon_unix.go b/components/engine/integration-cli/daemon_unix.go index 6f84fbbdfd..6ca7daf21c 100644 --- a/components/engine/integration-cli/daemon_unix.go +++ b/components/engine/integration-cli/daemon_unix.go @@ -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) diff --git a/components/engine/integration-cli/daemon_windows.go b/components/engine/integration-cli/daemon_windows.go index c6310975d8..a6e9a09615 100644 --- a/components/engine/integration-cli/daemon_windows.go +++ b/components/engine/integration-cli/daemon_windows.go @@ -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) { +}