- 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:
parent
68cae04fe9
commit
66eb3e1cd4
9 changed files with 216 additions and 48 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue