diff --git a/libnetwork/Godeps/Godeps.json b/libnetwork/Godeps/Godeps.json index c9f40b02df..44d9482cf5 100644 --- a/libnetwork/Godeps/Godeps.json +++ b/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": [ "./..." @@ -10,11 +10,26 @@ "Comment": "v0.6.4-12-g467d9d5", "Rev": "467d9d55c2d2c17248441a8fc661561161f40d5e" }, + { + "ImportPath": "github.com/docker/docker/pkg/etchosts", + "Comment": "v1.4.1-2492-ge690ad9", + "Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6" + }, + { + "ImportPath": "github.com/docker/docker/pkg/homedir", + "Comment": "v1.4.1-2492-ge690ad9", + "Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6" + }, { "ImportPath": "github.com/docker/docker/pkg/iptables", "Comment": "v1.4.1-2492-ge690ad9", "Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6" }, + { + "ImportPath": "github.com/docker/docker/pkg/mflag", + "Comment": "v1.4.1-2492-ge690ad9", + "Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6" + }, { "ImportPath": "github.com/docker/docker/pkg/parsers/kernel", "Comment": "v1.4.1-2492-ge690ad9", @@ -31,12 +46,7 @@ "Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6" }, { - "ImportPath": "github.com/docker/docker/pkg/mflag", - "Comment": "v1.4.1-2492-ge690ad9", - "Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6" - }, - { - "ImportPath": "github.com/docker/docker/pkg/homedir", + "ImportPath": "github.com/docker/docker/pkg/stringid", "Comment": "v1.4.1-2492-ge690ad9", "Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6" }, @@ -45,11 +55,6 @@ "Comment": "v1.4.0", "Rev": "53eca435e63db58b06cf796d3a9326db5fd42253" }, - { - "ImportPath": "github.com/docker/docker/pkg/stringid", - "Comment": "v1.4.1-2492-ge690ad9", - "Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6" - }, { "ImportPath": "github.com/vishvananda/netlink", "Rev": "8eb64238879fed52fd51c5b30ad20b928fb4c36c" diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/etchosts/etchosts.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/etchosts/etchosts.go new file mode 100644 index 0000000000..d7edef27f6 --- /dev/null +++ b/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) +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/etchosts/etchosts_test.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/etchosts/etchosts_test.go new file mode 100644 index 0000000000..c033904c31 --- /dev/null +++ b/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) + } +} diff --git a/libnetwork/drivers.go b/libnetwork/drivers.go index 8a8fdf2b3d..135eba4011 100644 --- a/libnetwork/drivers.go +++ b/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 } diff --git a/libnetwork/drivers/null/null.go b/libnetwork/drivers/null/null.go new file mode 100644 index 0000000000..aa09a06ea4 --- /dev/null +++ b/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 +} diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index cf76feb1d3..12113ad3e4 100644 --- a/libnetwork/endpoint.go +++ b/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) { - if containerID == "" { - return "", InvalidContainerIDError(containerID) +func createBasePath(dir string) error { + err := os.MkdirAll(dir, 0644) + if err != nil && !os.IsExist(err) { + return err } - if ep.containerID != "" { - return "", ErrInvalidJoin + 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 nil, InvalidContainerIDError(containerID) + } + + 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) + } +} diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index e58ad9f4b8..4582f23d6e 100644 --- a/libnetwork/libnetwork_test.go +++ b/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) } diff --git a/libnetwork/sandbox/namespace_linux.go b/libnetwork/sandbox/namespace_linux.go index 85cef29b4e..1d8a6a4293 100644 --- a/libnetwork/sandbox/namespace_linux.go +++ b/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() }