- 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>
This commit is contained in:
Jana Radhakrishnan 2015-05-03 20:29:43 +00:00
parent 68cae04fe9
commit 66eb3e1cd4
9 changed files with 216 additions and 48 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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.

View file

@ -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 {

View file

@ -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.

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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) {

View file

@ -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)
}