فهرست منبع

- Added support for JoinInfo so that driver can override certain
container config.
- Added JoinOption processing for extra /etc/hosts record.
- Added support for updating /etc/hosts entries of other containers.
- Added sandbox support for adding a sandbox without the OS level create.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>

Jana Radhakrishnan 10 سال پیش
والد
کامیت
66eb3e1cd4

+ 2 - 2
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
 		}

+ 9 - 1
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
+}

+ 2 - 2
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.

+ 2 - 2
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 {

+ 2 - 2
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.

+ 119 - 18
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,33 +175,49 @@ 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
 	}
-	defer func() {
-		if err != nil {
-			ep.network.ctrlr.sandboxRm(sboxKey)
+
+	create := true
+	if joinInfo != nil {
+		if joinInfo.SandboxKey != "" {
+			sboxKey = joinInfo.SandboxKey
 		}
-	}()
 
-	n := ep.network
-	err = n.driver.Join(n.id, ep.id, sboxKey, ep.container.config.generic)
+		if joinInfo.NoSandboxCreate {
+			create = false
+		}
+	}
+
+	sb, err := ep.network.ctrlr.sandboxAdd(sboxKey, create)
 	if err != nil {
 		return nil, err
 	}
+	defer func() {
+		if err != nil {
+			ep.network.ctrlr.sandboxRm(sboxKey)
+		}
+	}()
 
 	sinfo := ep.SandboxInfo()
 	if sinfo != nil {
@@ -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 {

+ 58 - 6
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)
+	}
+}

+ 21 - 14
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) {

+ 1 - 1
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)
 	}