diff --git a/libnetwork/controller.go b/libnetwork/controller.go index e25a620a15..28eae30d8b 100644 --- a/libnetwork/controller.go +++ b/libnetwork/controller.go @@ -198,13 +198,13 @@ func (c *controller) NetworkByID(id string) Network { return nil } -func (c *controller) sandboxAdd(key string) (sandbox.Sandbox, error) { +func (c *controller) sandboxAdd(key string, create bool) (sandbox.Sandbox, error) { c.Lock() defer c.Unlock() sData, ok := c.sandboxes[key] if !ok { - sb, err := sandbox.NewSandbox(key) + sb, err := sandbox.NewSandbox(key, create) if err != nil { return nil, err } diff --git a/libnetwork/driverapi/driverapi.go b/libnetwork/driverapi/driverapi.go index 963058d14e..48c1bc701a 100644 --- a/libnetwork/driverapi/driverapi.go +++ b/libnetwork/driverapi/driverapi.go @@ -44,7 +44,7 @@ type Driver interface { EndpointInfo(nid, eid types.UUID) (map[string]interface{}, error) // Join method is invoked when a Sandbox is attached to an endpoint. - Join(nid, eid types.UUID, sboxKey string, options map[string]interface{}) error + Join(nid, eid types.UUID, sboxKey string, options map[string]interface{}) (*JoinInfo, error) // Leave method is invoked when a Sandbox detaches from an endpoint. Leave(nid, eid types.UUID, options map[string]interface{}) error @@ -52,3 +52,11 @@ type Driver interface { // Type returns the the type of this driver, the network type this driver manages Type() string } + +// JoinInfo represents a set of resources that the driver has the ability to provide during +// join time. +type JoinInfo struct { + SandboxKey string + NoSandboxCreate bool + HostsPath string +} diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index 41820c99cb..0feedf8c3b 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/libnetwork/drivers/bridge/bridge.go @@ -618,12 +618,12 @@ func (d *driver) EndpointInfo(nid, eid types.UUID) (map[string]interface{}, erro } // Join method is invoked when a Sandbox is attached to an endpoint. -func (d *driver) Join(nid, eid types.UUID, sboxKey string, options map[string]interface{}) error { +func (d *driver) Join(nid, eid types.UUID, sboxKey string, options map[string]interface{}) (*driverapi.JoinInfo, error) { var err error if !d.config.EnableICC { err = d.link(nid, eid, options, true) } - return err + return nil, err } // Leave method is invoked when a Sandbox detaches from an endpoint. diff --git a/libnetwork/drivers/bridge/bridge_test.go b/libnetwork/drivers/bridge/bridge_test.go index e27bfd6e08..d60041ae57 100644 --- a/libnetwork/drivers/bridge/bridge_test.go +++ b/libnetwork/drivers/bridge/bridge_test.go @@ -231,7 +231,7 @@ func TestLinkContainers(t *testing.T) { genericOption = make(map[string]interface{}) genericOption[options.GenericData] = cConfig - err = d.Join("net1", "ep2", "", genericOption) + _, err = d.Join("net1", "ep2", "", genericOption) if err != nil { t.Fatalf("Failed to link ep1 and ep2") } @@ -281,7 +281,7 @@ func TestLinkContainers(t *testing.T) { genericOption = make(map[string]interface{}) genericOption[options.GenericData] = cConfig - err = d.Join("net1", "ep2", "", genericOption) + _, err = d.Join("net1", "ep2", "", genericOption) if err != nil { out, err = iptables.Raw("-L", "DOCKER") for _, pm := range portMappings { diff --git a/libnetwork/drivers/null/null.go b/libnetwork/drivers/null/null.go index 57577c6d57..0f9cb2d76d 100644 --- a/libnetwork/drivers/null/null.go +++ b/libnetwork/drivers/null/null.go @@ -40,8 +40,8 @@ func (d *driver) EndpointInfo(nid, eid types.UUID) (map[string]interface{}, erro } // Join method is invoked when a Sandbox is attached to an endpoint. -func (d *driver) Join(nid, eid types.UUID, sboxKey string, options map[string]interface{}) error { - return nil +func (d *driver) Join(nid, eid types.UUID, sboxKey string, options map[string]interface{}) (*driverapi.JoinInfo, error) { + return nil, nil } // Leave method is invoked when a Sandbox detaches from an endpoint. diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index 5f7170148d..a31429ac6e 100644 --- a/libnetwork/endpoint.go +++ b/libnetwork/endpoint.go @@ -1,10 +1,12 @@ package libnetwork import ( + "io/ioutil" "os" "path/filepath" "github.com/docker/docker/pkg/etchosts" + "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/netutils" "github.com/docker/libnetwork/pkg/options" "github.com/docker/libnetwork/sandbox" @@ -49,13 +51,26 @@ type EndpointOption func(ep *endpoint) // ContainerData is a set of data returned when a container joins an endpoint. type ContainerData struct { SandboxKey string - HostsPath string } type containerConfig struct { - hostName string - domainName string - generic map[string]interface{} + hostName string + domainName string + generic map[string]interface{} + hostsPath string + ExtraHosts []extraHost + parentUpdates []parentUpdate +} + +type extraHost struct { + name string + IP string +} + +type parentUpdate struct { + eid string + name string + ip string } type containerInfo struct { @@ -70,13 +85,14 @@ type endpoint struct { network *network sandboxInfo *sandbox.Info sandBox sandbox.Sandbox + joinInfo *driverapi.JoinInfo container *containerInfo exposedPorts []netutils.TransportPort generic map[string]interface{} context map[string]interface{} } -const prefix = "/var/lib/docker/network/files" +const defaultPrefix = "/var/lib/docker/network/files" func (ep *endpoint) ID() string { return string(ep.id) @@ -118,7 +134,7 @@ func createBasePath(dir string) error { return nil } -func createHostsFile(path string) error { +func createFile(path string) error { var f *os.File dir, _ := filepath.Split(path) @@ -146,7 +162,11 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*Contai return nil, ErrInvalidJoin } - ep.container = &containerInfo{} + ep.container = &containerInfo{ + config: containerConfig{ + ExtraHosts: []extraHost{}, + parentUpdates: []parentUpdate{}, + }} defer func() { if err != nil { ep.container = nil @@ -155,19 +175,41 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*Contai ep.processOptions(options...) - ep.container.data.HostsPath = prefix + "/" + containerID + "/hosts" - err = createHostsFile(ep.container.data.HostsPath) + if ep.container.config.hostsPath == "" { + ep.container.config.hostsPath = defaultPrefix + "/" + containerID + "/hosts" + } + + sboxKey := sandbox.GenerateKey(containerID) + + joinInfo, err := ep.network.driver.Join(ep.network.id, ep.id, + sboxKey, ep.container.config.generic) if err != nil { return nil, err } + ep.joinInfo = joinInfo err = ep.buildHostsFiles() if err != nil { return nil, err } - sboxKey := sandbox.GenerateKey(containerID) - sb, err := ep.network.ctrlr.sandboxAdd(sboxKey) + err = ep.updateParentHosts() + if err != nil { + return nil, err + } + + create := true + if joinInfo != nil { + if joinInfo.SandboxKey != "" { + sboxKey = joinInfo.SandboxKey + } + + if joinInfo.NoSandboxCreate { + create = false + } + } + + sb, err := ep.network.ctrlr.sandboxAdd(sboxKey, create) if err != nil { return nil, err } @@ -177,12 +219,6 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*Contai } }() - n := ep.network - err = n.driver.Join(n.id, ep.id, sboxKey, ep.container.config.generic) - if err != nil { - return nil, err - } - sinfo := ep.SandboxInfo() if sinfo != nil { for _, i := range sinfo.Interfaces { @@ -254,18 +290,40 @@ func (ep *endpoint) Delete() error { func (ep *endpoint) buildHostsFiles() error { var extraContent []etchosts.Record + dir, _ := filepath.Split(ep.container.config.hostsPath) + err := createBasePath(dir) + if err != nil { + return err + } + + if ep.joinInfo != nil && ep.joinInfo.HostsPath != "" { + content, err := ioutil.ReadFile(ep.joinInfo.HostsPath) + if err != nil && !os.IsNotExist(err) { + return err + } + + if err == nil { + return ioutil.WriteFile(ep.container.config.hostsPath, content, 0644) + } + } + name := ep.container.config.hostName if ep.container.config.domainName != "" { name = name + "." + ep.container.config.domainName } + for _, extraHost := range ep.container.config.ExtraHosts { + extraContent = append(extraContent, + etchosts.Record{Hosts: extraHost.name, IP: extraHost.IP}) + } + IP := "" if ep.sandboxInfo != nil && ep.sandboxInfo.Interfaces[0] != nil && ep.sandboxInfo.Interfaces[0].Address != nil { IP = ep.sandboxInfo.Interfaces[0].Address.IP.String() } - return etchosts.Build(ep.container.data.HostsPath, IP, ep.container.config.hostName, + return etchosts.Build(ep.container.config.hostsPath, IP, ep.container.config.hostName, ep.container.config.domainName, extraContent) } @@ -279,6 +337,25 @@ func EndpointOptionGeneric(generic map[string]interface{}) EndpointOption { } } +func (ep *endpoint) updateParentHosts() error { + for _, update := range ep.container.config.parentUpdates { + ep.network.Lock() + pep, ok := ep.network.endpoints[types.UUID(update.eid)] + if !ok { + ep.network.Unlock() + continue + } + ep.network.Unlock() + + if err := etchosts.Update(pep.container.config.hostsPath, + update.ip, update.name); err != nil { + return err + } + } + + return nil +} + // JoinOptionHostname function returns an option setter for hostname option to // be passed to endpoint Join method. func JoinOptionHostname(name string) EndpointOption { @@ -295,6 +372,30 @@ func JoinOptionDomainname(name string) EndpointOption { } } +// JoinOptionHostsPath function returns an option setter for hostspath option to +// be passed to endpoint Join method. +func JoinOptionHostsPath(path string) EndpointOption { + return func(ep *endpoint) { + ep.container.config.hostsPath = path + } +} + +// JoinOptionExtraHost function returns an option setter for extra /etc/hosts options +// which is a name and IP as strings. +func JoinOptionExtraHost(name string, IP string) EndpointOption { + return func(ep *endpoint) { + ep.container.config.ExtraHosts = append(ep.container.config.ExtraHosts, extraHost{name: name, IP: IP}) + } +} + +// JoinOptionParentUpdate function returns an option setter for parent container +// which needs to update the IP address for the linked container. +func JoinOptionParentUpdate(eid string, name, ip string) EndpointOption { + return func(ep *endpoint) { + ep.container.config.parentUpdates = append(ep.container.config.parentUpdates, parentUpdate{eid: eid, name: name, ip: ip}) + } +} + // CreateOptionPortMapping function returns an option setter for the container exposed // ports option to be passed to network.CreateEndpoint() method. func CreateOptionPortMapping(portBindings []netutils.PortBinding) EndpointOption { diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index f663d453dc..d9c0ab1192 100644 --- a/libnetwork/libnetwork_test.go +++ b/libnetwork/libnetwork_test.go @@ -67,14 +67,15 @@ func TestNull(t *testing.T) { t.Fatal(err) } - _, err = ep.Join(containerID, + _, err = ep.Join("null_container", libnetwork.JoinOptionHostname("test"), - libnetwork.JoinOptionDomainname("docker.io")) + libnetwork.JoinOptionDomainname("docker.io"), + libnetwork.JoinOptionExtraHost("web", "192.168.0.1")) if err != nil { t.Fatal(err) } - err = ep.Leave(containerID) + err = ep.Leave("null_container") if err != nil { t.Fatal(err) } @@ -556,7 +557,8 @@ func TestEndpointJoin(t *testing.T) { _, err = ep.Join(containerID, libnetwork.JoinOptionHostname("test"), - libnetwork.JoinOptionDomainname("docker.io")) + libnetwork.JoinOptionDomainname("docker.io"), + libnetwork.JoinOptionExtraHost("web", "192.168.0.1")) if err != nil { t.Fatal(err) } @@ -605,7 +607,8 @@ func TestEndpointMultipleJoins(t *testing.T) { _, err = ep.Join(containerID, libnetwork.JoinOptionHostname("test"), - libnetwork.JoinOptionDomainname("docker.io")) + libnetwork.JoinOptionDomainname("docker.io"), + libnetwork.JoinOptionExtraHost("web", "192.168.0.1")) if err != nil { t.Fatal(err) @@ -650,7 +653,8 @@ func TestEndpointInvalidLeave(t *testing.T) { _, err = ep.Join(containerID, libnetwork.JoinOptionHostname("test"), - libnetwork.JoinOptionDomainname("docker.io")) + libnetwork.JoinOptionDomainname("docker.io"), + libnetwork.JoinOptionExtraHost("web", "192.168.0.1")) if err != nil { t.Fatal(err) @@ -679,3 +683,51 @@ func TestEndpointInvalidLeave(t *testing.T) { t.Fatal(err) } } + +func TestEndpointUpdateParent(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + n, err := createTestNetwork("bridge", "testnetwork", options.Generic{}) + if err != nil { + t.Fatal(err) + } + + ep1, err := n.CreateEndpoint("ep1", nil) + if err != nil { + t.Fatal(err) + } + + _, err = ep1.Join(containerID, + libnetwork.JoinOptionHostname("test1"), + libnetwork.JoinOptionDomainname("docker.io"), + libnetwork.JoinOptionExtraHost("web", "192.168.0.1")) + + if err != nil { + t.Fatal(err) + } + + ep2, err := n.CreateEndpoint("ep2", nil) + if err != nil { + t.Fatal(err) + } + + _, err = ep2.Join("container2", + libnetwork.JoinOptionHostname("test2"), + libnetwork.JoinOptionDomainname("docker.io"), + libnetwork.JoinOptionHostsPath("/var/lib/docker/test_network/container2/hosts"), + libnetwork.JoinOptionParentUpdate(ep1.ID(), "web", "192.168.0.2")) + + if err != nil { + t.Fatal(err) + } + + err = ep2.Leave("container2") + if err != nil { + t.Fatal(err) + } + + err = ep1.Leave(containerID) + if err != nil { + t.Fatal(err) + } +} diff --git a/libnetwork/sandbox/namespace_linux.go b/libnetwork/sandbox/namespace_linux.go index 1d8a6a4293..92781b90fe 100644 --- a/libnetwork/sandbox/namespace_linux.go +++ b/libnetwork/sandbox/namespace_linux.go @@ -12,7 +12,7 @@ import ( "github.com/vishvananda/netns" ) -const prefix = "/var/lib/docker/network" +const prefix = "/var/lib/docker/netns" var once sync.Once @@ -44,11 +44,16 @@ func GenerateKey(containerID string) string { // NewSandbox provides a new sandbox instance created in an os specific way // provided a key which uniquely identifies the sandbox -func NewSandbox(key string) (Sandbox, error) { - return createNetworkNamespace(key) +func NewSandbox(key string, create bool) (Sandbox, error) { + info, err := createNetworkNamespace(key, create) + if err != nil { + return nil, err + } + + return &networkNamespace{path: key, sinfo: info}, nil } -func createNetworkNamespace(path string) (Sandbox, error) { +func createNetworkNamespace(path string, create bool) (*Info, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -62,15 +67,17 @@ func createNetworkNamespace(path string) (Sandbox, error) { return nil, err } - defer netns.Set(origns) - newns, err := netns.New() - if err != nil { - return nil, err - } - defer newns.Close() + if create { + defer netns.Set(origns) + newns, err := netns.New() + if err != nil { + return nil, err + } + defer newns.Close() - if err := loopbackUp(); err != nil { - return nil, err + if err := loopbackUp(); err != nil { + return nil, err + } } procNet := fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), syscall.Gettid()) @@ -80,8 +87,8 @@ func createNetworkNamespace(path string) (Sandbox, error) { } interfaces := []*Interface{} - sinfo := &Info{Interfaces: interfaces} - return &networkNamespace{path: path, sinfo: sinfo}, nil + info := &Info{Interfaces: interfaces} + return info, nil } func createNamespaceFile(path string) (err error) { diff --git a/libnetwork/sandbox/sandbox_test.go b/libnetwork/sandbox/sandbox_test.go index e803a8fe8d..e596113a7c 100644 --- a/libnetwork/sandbox/sandbox_test.go +++ b/libnetwork/sandbox/sandbox_test.go @@ -11,7 +11,7 @@ func TestSandboxCreate(t *testing.T) { t.Fatalf("Failed to obtain a key: %v", err) } - s, err := NewSandbox(key) + s, err := NewSandbox(key, true) if err != nil { t.Fatalf("Failed to create a new sandbox: %v", err) }