Procházet zdrojové kódy

Merge pull request #100 from mrjana/cnm_integ

Add basic /etc/hosts file management support in libnetwork
Madhu Venugopal před 10 roky
rodič
revize
d1e53e93bf

+ 15 - 10
libnetwork/Godeps/Godeps.json

@@ -1,5 +1,5 @@
 {
-	"ImportPath": "github.com/docker/libnetwork",
+	"ImportPath": "github.com/docker/docker/vendor/src/github.com/docker/libnetwork",
 	"GoVersion": "go1.4.2",
 	"Packages": [
 		"./..."
@@ -11,45 +11,50 @@
 			"Rev": "467d9d55c2d2c17248441a8fc661561161f40d5e"
 		},
 		{
-			"ImportPath": "github.com/docker/docker/pkg/iptables",
+			"ImportPath": "github.com/docker/docker/pkg/etchosts",
 			"Comment": "v1.4.1-2492-ge690ad9",
 			"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
 		},
 		{
-			"ImportPath": "github.com/docker/docker/pkg/parsers/kernel",
+			"ImportPath": "github.com/docker/docker/pkg/homedir",
 			"Comment": "v1.4.1-2492-ge690ad9",
 			"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
 		},
 		{
-			"ImportPath": "github.com/docker/docker/pkg/proxy",
+			"ImportPath": "github.com/docker/docker/pkg/iptables",
 			"Comment": "v1.4.1-2492-ge690ad9",
 			"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
 		},
 		{
-			"ImportPath": "github.com/docker/docker/pkg/reexec",
+			"ImportPath": "github.com/docker/docker/pkg/mflag",
 			"Comment": "v1.4.1-2492-ge690ad9",
 			"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
 		},
 		{
-			"ImportPath": "github.com/docker/docker/pkg/mflag",
+			"ImportPath": "github.com/docker/docker/pkg/parsers/kernel",
 			"Comment": "v1.4.1-2492-ge690ad9",
 			"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
 		},
 		{
-			"ImportPath": "github.com/docker/docker/pkg/homedir",
+			"ImportPath": "github.com/docker/docker/pkg/proxy",
 			"Comment": "v1.4.1-2492-ge690ad9",
 			"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
 		},
 		{
-			"ImportPath": "github.com/docker/libcontainer/user",
-			"Comment": "v1.4.0",
-			"Rev": "53eca435e63db58b06cf796d3a9326db5fd42253"
+			"ImportPath": "github.com/docker/docker/pkg/reexec",
+			"Comment": "v1.4.1-2492-ge690ad9",
+			"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
 		},
 		{
 			"ImportPath": "github.com/docker/docker/pkg/stringid",
 			"Comment": "v1.4.1-2492-ge690ad9",
 			"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
 		},
+		{
+			"ImportPath": "github.com/docker/libcontainer/user",
+			"Comment": "v1.4.0",
+			"Rev": "53eca435e63db58b06cf796d3a9326db5fd42253"
+		},
 		{
 			"ImportPath": "github.com/vishvananda/netlink",
 			"Rev": "8eb64238879fed52fd51c5b30ad20b928fb4c36c"

+ 67 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/etchosts/etchosts.go

@@ -0,0 +1,67 @@
+package etchosts
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"regexp"
+)
+
+type Record struct {
+	Hosts string
+	IP    string
+}
+
+func (r Record) WriteTo(w io.Writer) (int64, error) {
+	n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts)
+	return int64(n), err
+}
+
+var defaultContent = []Record{
+	{Hosts: "localhost", IP: "127.0.0.1"},
+	{Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"},
+	{Hosts: "ip6-localnet", IP: "fe00::0"},
+	{Hosts: "ip6-mcastprefix", IP: "ff00::0"},
+	{Hosts: "ip6-allnodes", IP: "ff02::1"},
+	{Hosts: "ip6-allrouters", IP: "ff02::2"},
+}
+
+func Build(path, IP, hostname, domainname string, extraContent []Record) error {
+	content := bytes.NewBuffer(nil)
+	if IP != "" {
+		var mainRec Record
+		mainRec.IP = IP
+		if domainname != "" {
+			mainRec.Hosts = fmt.Sprintf("%s.%s %s", hostname, domainname, hostname)
+		} else {
+			mainRec.Hosts = hostname
+		}
+		if _, err := mainRec.WriteTo(content); err != nil {
+			return err
+		}
+	}
+
+	for _, r := range defaultContent {
+		if _, err := r.WriteTo(content); err != nil {
+			return err
+		}
+	}
+
+	for _, r := range extraContent {
+		if _, err := r.WriteTo(content); err != nil {
+			return err
+		}
+	}
+
+	return ioutil.WriteFile(path, content.Bytes(), 0644)
+}
+
+func Update(path, IP, hostname string) error {
+	old, err := ioutil.ReadFile(path)
+	if err != nil {
+		return err
+	}
+	var re = regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)", regexp.QuoteMeta(hostname)))
+	return ioutil.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2")), 0644)
+}

+ 134 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/etchosts/etchosts_test.go

@@ -0,0 +1,134 @@
+package etchosts
+
+import (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+func TestBuildDefault(t *testing.T) {
+	file, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(file.Name())
+
+	// check that /etc/hosts has consistent ordering
+	for i := 0; i <= 5; i++ {
+		err = Build(file.Name(), "", "", "", nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		content, err := ioutil.ReadFile(file.Name())
+		if err != nil {
+			t.Fatal(err)
+		}
+		expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n"
+
+		if expected != string(content) {
+			t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+		}
+	}
+}
+
+func TestBuildHostnameDomainname(t *testing.T) {
+	file, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(file.Name())
+
+	err = Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	content, err := ioutil.ReadFile(file.Name())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+	}
+}
+
+func TestBuildHostname(t *testing.T) {
+	file, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(file.Name())
+
+	err = Build(file.Name(), "10.11.12.13", "testhostname", "", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	content, err := ioutil.ReadFile(file.Name())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if expected := "10.11.12.13\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+	}
+}
+
+func TestBuildNoIP(t *testing.T) {
+	file, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(file.Name())
+
+	err = Build(file.Name(), "", "testhostname", "", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	content, err := ioutil.ReadFile(file.Name())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if expected := ""; !bytes.Contains(content, []byte(expected)) {
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+	}
+}
+
+func TestUpdate(t *testing.T) {
+	file, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(file.Name())
+
+	if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil); err != nil {
+		t.Fatal(err)
+	}
+
+	content, err := ioutil.ReadFile(file.Name())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+	}
+
+	if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil {
+		t.Fatal(err)
+	}
+
+	content, err = ioutil.ReadFile(file.Name())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+	}
+}

+ 2 - 1
libnetwork/drivers.go

@@ -3,6 +3,7 @@ package libnetwork
 import (
 	"github.com/docker/libnetwork/driverapi"
 	"github.com/docker/libnetwork/drivers/bridge"
+	"github.com/docker/libnetwork/drivers/null"
 )
 
 type driverTable map[string]driverapi.Driver
@@ -10,7 +11,7 @@ type driverTable map[string]driverapi.Driver
 func enumerateDrivers() driverTable {
 	drivers := make(driverTable)
 
-	for _, fn := range [](func() (string, driverapi.Driver)){bridge.New} {
+	for _, fn := range [](func() (string, driverapi.Driver)){bridge.New, null.New} {
 		name, driver := fn()
 		drivers[name] = driver
 	}

+ 40 - 0
libnetwork/drivers/null/null.go

@@ -0,0 +1,40 @@
+package null
+
+import (
+	"github.com/docker/libnetwork/driverapi"
+	"github.com/docker/libnetwork/sandbox"
+	"github.com/docker/libnetwork/types"
+)
+
+const networkType = "null"
+
+type driver struct{}
+
+// New provides a new instance of null driver
+func New() (string, driverapi.Driver) {
+	return networkType, &driver{}
+}
+
+func (d *driver) Config(option interface{}) error {
+	return nil
+}
+
+func (d *driver) CreateNetwork(id types.UUID, option interface{}) error {
+	return nil
+}
+
+func (d *driver) DeleteNetwork(nid types.UUID) error {
+	return nil
+}
+
+func (d *driver) CreateEndpoint(nid, eid types.UUID, epOptions interface{}) (*sandbox.Info, error) {
+	return nil, nil
+}
+
+func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
+	return nil
+}
+
+func (d *driver) Type() string {
+	return networkType
+}

+ 137 - 14
libnetwork/endpoint.go

@@ -1,6 +1,10 @@
 package libnetwork
 
 import (
+	"os"
+	"path/filepath"
+
+	"github.com/docker/docker/pkg/etchosts"
 	"github.com/docker/libnetwork/sandbox"
 	"github.com/docker/libnetwork/types"
 )
@@ -19,7 +23,7 @@ type Endpoint interface {
 	// Join creates a new sandbox for the given container ID and populates the
 	// network resources allocated for the endpoint and joins the sandbox to
 	// the endpoint. It returns the sandbox key to the caller
-	Join(containerID string) (string, error)
+	Join(containerID string, options ...JoinOption) (*ContainerData, error)
 
 	// Leave removes the sandbox associated with  container ID and detaches
 	// the network resources populated in the sandbox
@@ -32,15 +36,38 @@ type Endpoint interface {
 	Delete() error
 }
 
+// ContainerData is a set of data returned when a container joins an endpoint.
+type ContainerData struct {
+	SandboxKey string
+	HostsPath  string
+}
+
+// JoinOption is a option setter function type used to pass varios options to
+// endpoint Join method.
+type JoinOption func(ep Endpoint)
+
+type containerConfig struct {
+	Hostname   string
+	Domainname string
+}
+
+type containerInfo struct {
+	ID     string
+	Config containerConfig
+	Data   ContainerData
+}
+
 type endpoint struct {
 	name        string
 	id          types.UUID
 	network     *network
 	sandboxInfo *sandbox.Info
 	sandBox     sandbox.Sandbox
-	containerID string
+	container   *containerInfo
 }
 
+const prefix = "/var/lib/docker/network/files"
+
 func (ep *endpoint) ID() string {
 	return string(ep.id)
 }
@@ -60,19 +87,69 @@ func (ep *endpoint) SandboxInfo() *sandbox.Info {
 	return ep.sandboxInfo.GetCopy()
 }
 
-func (ep *endpoint) Join(containerID string) (string, error) {
+func createBasePath(dir string) error {
+	err := os.MkdirAll(dir, 0644)
+	if err != nil && !os.IsExist(err) {
+		return err
+	}
+
+	return nil
+}
+
+func createHostsFile(path string) error {
+	var f *os.File
+
+	dir, _ := filepath.Split(path)
+	err := createBasePath(dir)
+	if err != nil {
+		return err
+	}
+
+	f, err = os.Create(path)
+	if err == nil {
+		f.Close()
+	}
+
+	return err
+}
+
+func (ep *endpoint) Join(containerID string, options ...JoinOption) (*ContainerData, error) {
+	var err error
+
 	if containerID == "" {
-		return "", InvalidContainerIDError(containerID)
+		return nil, InvalidContainerIDError(containerID)
 	}
 
-	if ep.containerID != "" {
-		return "", ErrInvalidJoin
+	if ep.container != nil {
+		return nil, ErrInvalidJoin
+	}
+
+	ep.container = &containerInfo{}
+	defer func() {
+		if err != nil {
+			ep.container = nil
+		}
+	}()
+
+	if options != nil {
+		ep.processOptions(options...)
+	}
+
+	ep.container.Data.HostsPath = prefix + "/" + containerID + "/hosts"
+	err = createHostsFile(ep.container.Data.HostsPath)
+	if err != nil {
+		return nil, err
+	}
+
+	err = ep.buildHostsFiles()
+	if err != nil {
+		return nil, err
 	}
 
 	sboxKey := sandbox.GenerateKey(containerID)
 	sb, err := ep.network.ctrlr.sandboxAdd(sboxKey)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	defer func() {
 		if err != nil {
@@ -85,32 +162,36 @@ func (ep *endpoint) Join(containerID string) (string, error) {
 		for _, i := range sinfo.Interfaces {
 			err = sb.AddInterface(i)
 			if err != nil {
-				return "", err
+				return nil, err
 			}
 		}
 
 		err = sb.SetGateway(sinfo.Gateway)
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 
 		err = sb.SetGatewayIPv6(sinfo.GatewayIPv6)
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 	}
 
-	ep.containerID = containerID
-	return sb.Key(), nil
+	ep.container.ID = containerID
+	ep.container.Data.SandboxKey = sb.Key()
+
+	cData := ep.container.Data
+	return &cData, nil
 }
 
 func (ep *endpoint) Leave(containerID string) error {
-	if ep.containerID == "" || containerID == "" || ep.containerID != containerID {
+	if ep.container == nil || ep.container.ID == "" ||
+		containerID == "" || ep.container.ID != containerID {
 		return InvalidContainerIDError(containerID)
 	}
 
 	ep.network.ctrlr.sandboxRm(sandbox.GenerateKey(containerID))
-	ep.containerID = ""
+	ep.container = nil
 	return nil
 }
 
@@ -138,3 +219,45 @@ func (ep *endpoint) Delete() error {
 	err = n.driver.DeleteEndpoint(n.id, ep.id)
 	return err
 }
+
+func (ep *endpoint) buildHostsFiles() error {
+	var extraContent []etchosts.Record
+
+	name := ep.container.Config.Hostname
+	if ep.container.Config.Domainname != "" {
+		name = name + "." + ep.container.Config.Domainname
+	}
+
+	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,
+		ep.container.Config.Domainname, extraContent)
+}
+
+// JoinOptionHostname function returns an option setter for hostname option to
+// be passed to endpoint Join method.
+func JoinOptionHostname(name string) JoinOption {
+	return func(e Endpoint) {
+		ep := e.(*endpoint)
+		ep.container.Config.Hostname = name
+	}
+}
+
+// JoinOptionDomainname function returns an option setter for domainname option to
+// be passed to endpoint Join method.
+func JoinOptionDomainname(name string) JoinOption {
+	return func(e Endpoint) {
+		ep := e.(*endpoint)
+		ep.container.Config.Domainname = name
+	}
+}
+
+func (ep *endpoint) processOptions(options ...JoinOption) {
+	for _, opt := range options {
+		opt(ep)
+	}
+}

+ 43 - 3
libnetwork/libnetwork_test.go

@@ -31,6 +31,38 @@ func createTestNetwork(networkType, networkName string, option options.Generic)
 	return network, nil
 }
 
+func TestNull(t *testing.T) {
+	network, err := createTestNetwork("null", "testnetwork", options.Generic{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ep, err := network.CreateEndpoint("testep", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = ep.Join(containerID,
+		libnetwork.JoinOptionHostname("test"),
+		libnetwork.JoinOptionDomainname("docker.io"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = ep.Leave(containerID)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep.Delete(); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := network.Delete(); err != nil {
+		t.Fatal(err)
+	}
+}
+
 func TestBridge(t *testing.T) {
 	defer netutils.SetupTestNetNS(t)()
 	ip, subnet, err := net.ParseCIDR("192.168.100.1/24")
@@ -485,7 +517,9 @@ func TestEndpointJoin(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	_, err = ep.Join(containerID)
+	_, err = ep.Join(containerID,
+		libnetwork.JoinOptionHostname("test"),
+		libnetwork.JoinOptionDomainname("docker.io"))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -532,7 +566,10 @@ func TestEndpointMultipleJoins(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	_, err = ep.Join(containerID)
+	_, err = ep.Join(containerID,
+		libnetwork.JoinOptionHostname("test"),
+		libnetwork.JoinOptionDomainname("docker.io"))
+
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -574,7 +611,10 @@ func TestEndpointInvalidLeave(t *testing.T) {
 		t.Fatalf("Failed for unexpected reason: %v", err)
 	}
 
-	_, err = ep.Join(containerID)
+	_, err = ep.Join(containerID,
+		libnetwork.JoinOptionHostname("test"),
+		libnetwork.JoinOptionDomainname("docker.io"))
+
 	if err != nil {
 		t.Fatal(err)
 	}

+ 2 - 2
libnetwork/sandbox/namespace_linux.go

@@ -24,7 +24,7 @@ type networkNamespace struct {
 	sinfo *Info
 }
 
-func creatBasePath() {
+func createBasePath() {
 	err := os.MkdirAll(prefix, 0644)
 	if err != nil && !os.IsExist(err) {
 		panic("Could not create net namespace path directory")
@@ -87,7 +87,7 @@ func createNetworkNamespace(path string) (Sandbox, error) {
 func createNamespaceFile(path string) (err error) {
 	var f *os.File
 
-	once.Do(creatBasePath)
+	once.Do(createBasePath)
 	if f, err = os.Create(path); err == nil {
 		f.Close()
 	}