From 50905a6d6ce2fdd1ab0c33ec0b7a26895e0cbeea Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 23 Dec 2014 12:10:03 -0800 Subject: [PATCH] Update libcontainer to 1597c68f7b941fd97881155d7f077852e2914e7b This commit contains changes for docker: * user.GetGroupFile to user.GetGroupPath docker/libcontainer#301 * Add systemd support for OOM docker/libcontainer#307 * Support for custom namespaces docker/libcontainer#279, docker/libcontainer#312 * Fixes #9699 docker/libcontainer#308 Signed-off-by: Alexander Morozov --- api/server/server.go | 2 +- daemon/execdriver/native/create.go | 11 +- daemon/execdriver/native/driver.go | 15 +- .../native/template/default_template.go | 12 +- project/vendor.sh | 2 +- .../github.com/docker/libcontainer/Makefile | 6 +- .../github.com/docker/libcontainer/SPEC.md | 25 ++++ .../docker/libcontainer/cgroups/cgroups.go | 1 + .../docker/libcontainer/cgroups/fs/cpuset.go | 17 ++- .../docker/libcontainer/cgroups/fs/memory.go | 9 +- .../cgroups/fs/stats_util_test.go | 4 + .../cgroups/systemd/apply_systemd.go | 2 +- .../docker/libcontainer/cgroups/utils.go | 30 +++- .../github.com/docker/libcontainer/config.go | 56 ++++++- .../docker/libcontainer/config_test.go | 4 +- .../libcontainer/integration/exec_test.go | 11 +- .../libcontainer/integration/execin_test.go | 140 ++++++++++++++++++ .../libcontainer/integration/init_test.go | 67 +++++++-- .../libcontainer/integration/template_test.go | 12 +- .../github.com/docker/libcontainer/ipc/ipc.go | 29 ---- .../docker/libcontainer/namespaces/execin.go | 4 + .../docker/libcontainer/namespaces/init.go | 35 ++++- .../libcontainer/namespaces/nsenter/nsenter.c | 17 ++- .../docker/libcontainer/namespaces/types.go | 50 ------- .../libcontainer/namespaces/types_linux.go | 16 -- .../libcontainer/namespaces/types_test.go | 30 ---- .../docker/libcontainer/namespaces/utils.go | 21 ++- .../libcontainer/netlink/netlink_linux.go | 5 +- .../docker/libcontainer/network/netns.go | 39 ----- .../docker/libcontainer/network/network.go | 12 ++ .../docker/libcontainer/network/strategy.go | 1 - .../docker/libcontainer/network/types.go | 5 - .../{cgroups/fs => }/notify_linux.go | 52 ++----- .../{cgroups/fs => }/notify_linux_test.go | 42 ++++-- .../libcontainer/sample_configs/apparmor.json | 14 +- .../sample_configs/attach_to_bridge.json | 14 +- .../libcontainer/sample_configs/minimal.json | 14 +- .../route_source_address_selection.json | 14 +- .../libcontainer/sample_configs/selinux.json | 14 +- .../docker/libcontainer/user/MAINTAINERS | 1 + .../docker/libcontainer/user/lookup_unix.go | 16 +- .../libcontainer/user/lookup_unsupported.go | 4 +- .../docker/libcontainer/user/user.go | 4 +- 43 files changed, 517 insertions(+), 362 deletions(-) create mode 100644 vendor/src/github.com/docker/libcontainer/integration/execin_test.go delete mode 100644 vendor/src/github.com/docker/libcontainer/ipc/ipc.go delete mode 100644 vendor/src/github.com/docker/libcontainer/namespaces/types.go delete mode 100644 vendor/src/github.com/docker/libcontainer/namespaces/types_linux.go delete mode 100644 vendor/src/github.com/docker/libcontainer/namespaces/types_test.go delete mode 100644 vendor/src/github.com/docker/libcontainer/network/netns.go rename vendor/src/github.com/docker/libcontainer/{cgroups/fs => }/notify_linux.go (54%) rename vendor/src/github.com/docker/libcontainer/{cgroups/fs => }/notify_linux_test.go (67%) diff --git a/api/server/server.go b/api/server/server.go index f1bfcb6ac4..6b15962b27 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1399,7 +1399,7 @@ func serveFd(addr string, job *engine.Job) error { } func lookupGidByName(nameOrGid string) (int, error) { - groupFile, err := user.GetGroupFile() + groupFile, err := user.GetGroupPath() if err != nil { return -1, err } diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index de103eca8a..188f78ec0d 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -82,7 +82,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Command) error { if c.Network.HostNetworking { - container.Namespaces["NEWNET"] = false + container.Namespaces.Remove(libcontainer.NEWNET) return nil } @@ -119,10 +119,7 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com cmd := active.cmd nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net") - container.Networks = append(container.Networks, &libcontainer.Network{ - Type: "netns", - NsPath: nspath, - }) + container.Namespaces.Add(libcontainer.NEWNET, nspath) } return nil @@ -130,7 +127,7 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command) error { if c.Ipc.HostIpc { - container.Namespaces["NEWIPC"] = false + container.Namespaces.Remove(libcontainer.NEWIPC) return nil } @@ -144,7 +141,7 @@ func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command } cmd := active.cmd - container.IpcNsPath = filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "ipc") + container.Namespaces.Add(libcontainer.NEWIPC, filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "ipc")) } return nil diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index a036abc212..f6099bd049 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -61,10 +61,6 @@ func NewDriver(root, initPath string) (*driver, error) { }, nil } -func (d *driver) notifyOnOOM(config *libcontainer.Config) (<-chan struct{}, error) { - return fs.NotifyOnOOM(config.Cgroups) -} - type execOutput struct { exitCode int err error @@ -152,11 +148,16 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba } oomKill := false - oomKillNotification, err := d.notifyOnOOM(container) + state, err := libcontainer.GetState(filepath.Join(d.root, c.ID)) if err == nil { - _, oomKill = <-oomKillNotification + oomKillNotification, err := libcontainer.NotifyOnOOM(state) + if err == nil { + _, oomKill = <-oomKillNotification + } else { + log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err) + } } else { - log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err) + log.Warnf("Failed to get container state, oom notify will not work: %s", err) } // wait for the container to exit. execOutput := <-execOutputChan diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index be3dd5a5c1..6aa213d6bc 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -25,12 +25,12 @@ func New() *libcontainer.Config { "KILL", "AUDIT_WRITE", }, - Namespaces: map[string]bool{ - "NEWNS": true, - "NEWUTS": true, - "NEWIPC": true, - "NEWPID": true, - "NEWNET": true, + Namespaces: libcontainer.Namespaces{ + {Type: "NEWNS"}, + {Type: "NEWUTS"}, + {Type: "NEWIPC"}, + {Type: "NEWPID"}, + {Type: "NEWNET"}, }, Cgroups: &cgroups.Cgroup{ Parent: "docker", diff --git a/project/vendor.sh b/project/vendor.sh index 0b56cb1b69..2c84e45eee 100755 --- a/project/vendor.sh +++ b/project/vendor.sh @@ -66,7 +66,7 @@ if [ "$1" = '--go' ]; then mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar fi -clone git github.com/docker/libcontainer 53eca435e63db58b06cf796d3a9326db5fd42253 +clone git github.com/docker/libcontainer 1597c68f7b941fd97881155d7f077852e2914e7b # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) rm -rf src/github.com/docker/libcontainer/vendor eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')" diff --git a/vendor/src/github.com/docker/libcontainer/Makefile b/vendor/src/github.com/docker/libcontainer/Makefile index 0c4dda7c9b..f94171b0fc 100644 --- a/vendor/src/github.com/docker/libcontainer/Makefile +++ b/vendor/src/github.com/docker/libcontainer/Makefile @@ -1,13 +1,13 @@ all: - docker build -t docker/libcontainer . + docker build -t dockercore/libcontainer . test: # we need NET_ADMIN for the netlink tests and SYS_ADMIN for mounting - docker run --rm -it --privileged docker/libcontainer + docker run --rm -it --privileged dockercore/libcontainer sh: - docker run --rm -it --privileged -w /busybox docker/libcontainer nsinit exec sh + docker run --rm -it --privileged -w /busybox dockercore/libcontainer nsinit exec sh GO_PACKAGES = $(shell find . -not \( -wholename ./vendor -prune -o -wholename ./.git -prune \) -name '*.go' -print0 | xargs -0n1 dirname | sort -u) diff --git a/vendor/src/github.com/docker/libcontainer/SPEC.md b/vendor/src/github.com/docker/libcontainer/SPEC.md index f5afaadc51..d83d758ddd 100644 --- a/vendor/src/github.com/docker/libcontainer/SPEC.md +++ b/vendor/src/github.com/docker/libcontainer/SPEC.md @@ -318,4 +318,29 @@ a container. | Resume | Resume all processes inside the container if paused | | Exec | Execute a new process inside of the container ( requires setns ) | +### Execute a new process inside of a running container. +User can execute a new process inside of a running container. Any binaries to be +executed must be accessible within the container's rootfs. + +The started process will run inside the container's rootfs. Any changes +made by the process to the container's filesystem will persist after the +process finished executing. + +The started process will join all the container's existing namespaces. When the +container is paused, the process will also be paused and will resume when +the container is unpaused. The started process will only run when the container's +primary process (PID 1) is running, and will not be restarted when the container +is restarted. + +#### Planned additions + +The started process will have its own cgroups nested inside the container's +cgroups. This is used for process tracking and optionally resource allocation +handling for the new process. Freezer cgroup is required, the rest of the cgroups +are optional. The process executor must place its pid inside the correct +cgroups before starting the process. This is done so that no child processes or +threads can escape the cgroups. + +When the process is stopped, the process executor will try (in a best-effort way) +to stop all its children and remove the sub-cgroups. diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/cgroups.go b/vendor/src/github.com/docker/libcontainer/cgroups/cgroups.go index fe3600597b..106698d18f 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/cgroups.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/cgroups.go @@ -50,6 +50,7 @@ type Cgroup struct { CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default. CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use + CpusetMems string `json:"cpuset_mems,omitempty"` // MEM to use Freezer FreezerState `json:"freezer,omitempty"` // set the freeze value for the process Slice string `json:"slice,omitempty"` // Parent slice to use for systemd } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/cpuset.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/cpuset.go index 54d2ed5725..ff67a53e87 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/cpuset.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/cpuset.go @@ -18,7 +18,7 @@ func (s *CpusetGroup) Set(d *data) error { if err != nil { return err } - return s.SetDir(dir, d.c.CpusetCpus, d.pid) + return s.SetDir(dir, d.c.CpusetCpus, d.c.CpusetMems, d.pid) } func (s *CpusetGroup) Remove(d *data) error { @@ -29,7 +29,7 @@ func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error { return nil } -func (s *CpusetGroup) SetDir(dir, value string, pid int) error { +func (s *CpusetGroup) SetDir(dir, cpus string, mems string, pid int) error { if err := s.ensureParent(dir); err != nil { return err } @@ -40,10 +40,15 @@ func (s *CpusetGroup) SetDir(dir, value string, pid int) error { return err } - // If we don't use --cpuset, the default cpuset.cpus is set in - // s.ensureParent, otherwise, use the value we set - if value != "" { - if err := writeFile(dir, "cpuset.cpus", value); err != nil { + // If we don't use --cpuset-xxx, the default value inherit from parent cgroup + // is set in s.ensureParent, otherwise, use the value we set + if cpus != "" { + if err := writeFile(dir, "cpuset.cpus", cpus); err != nil { + return err + } + } + if mems != "" { + if err := writeFile(dir, "cpuset.mems", mems); err != nil { return err } } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go index 3f9647c2fd..01713fd790 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go @@ -38,12 +38,17 @@ func (s *MemoryGroup) Set(d *data) error { } } // By default, MemorySwap is set to twice the size of RAM. - // If you want to omit MemorySwap, set it to `-1'. - if d.c.MemorySwap != -1 { + // If you want to omit MemorySwap, set it to '-1'. + if d.c.MemorySwap == 0 { if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.Memory*2, 10)); err != nil { return err } } + if d.c.MemorySwap > 0 { + if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.MemorySwap, 10)); err != nil { + return err + } + } } return nil } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go index 1a9e590f59..6e237c0409 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go @@ -90,4 +90,8 @@ func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats) t.Fail() } } + if expected.Failcnt != actual.Failcnt { + log.Printf("Expected memory failcnt %d but found %d\n", expected.Failcnt, actual.Failcnt) + t.Fail() + } } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go index 3d89811433..41dce3117d 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go @@ -313,5 +313,5 @@ func joinCpuset(c *cgroups.Cgroup, pid int) error { s := &fs.CpusetGroup{} - return s.SetDir(path, c.CpusetCpus, pid) + return s.SetDir(path, c.CpusetCpus, c.CpusetMems, pid) } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/utils.go b/vendor/src/github.com/docker/libcontainer/cgroups/utils.go index 224a20b9b2..5753ca453a 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/utils.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/utils.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/docker/docker/pkg/mount" ) @@ -193,13 +194,30 @@ func EnterPid(cgroupPaths map[string]string, pid int) error { } // RemovePaths iterates over the provided paths removing them. -// If an error is encountered the removal proceeds and the first error is -// returned to ensure a partial removal is not possible. +// We trying to remove all paths five times with increasing delay between tries. +// If after all there are not removed cgroups - appropriate error will be +// returned. func RemovePaths(paths map[string]string) (err error) { - for _, path := range paths { - if rerr := os.RemoveAll(path); err == nil { - err = rerr + delay := 10 * time.Millisecond + for i := 0; i < 5; i++ { + if i != 0 { + time.Sleep(delay) + delay *= 2 + } + for s, p := range paths { + os.RemoveAll(p) + // TODO: here probably should be logging + _, err := os.Stat(p) + // We need this strange way of checking cgroups existence because + // RemoveAll almost always returns error, even on already removed + // cgroups + if os.IsNotExist(err) { + delete(paths, s) + } + } + if len(paths) == 0 { + return nil } } - return err + return fmt.Errorf("Failed to remove paths: %s", paths) } diff --git a/vendor/src/github.com/docker/libcontainer/config.go b/vendor/src/github.com/docker/libcontainer/config.go index 915e00660c..7f1bcc9621 100644 --- a/vendor/src/github.com/docker/libcontainer/config.go +++ b/vendor/src/github.com/docker/libcontainer/config.go @@ -10,6 +10,57 @@ type MountConfig mount.MountConfig type Network network.Network +type NamespaceType string + +const ( + NEWNET NamespaceType = "NEWNET" + NEWPID NamespaceType = "NEWPID" + NEWNS NamespaceType = "NEWNS" + NEWUTS NamespaceType = "NEWUTS" + NEWIPC NamespaceType = "NEWIPC" + NEWUSER NamespaceType = "NEWUSER" +) + +// Namespace defines configuration for each namespace. It specifies an +// alternate path that is able to be joined via setns. +type Namespace struct { + Type NamespaceType `json:"type"` + Path string `json:"path,omitempty"` +} + +type Namespaces []Namespace + +func (n Namespaces) Remove(t NamespaceType) bool { + i := n.index(t) + if i == -1 { + return false + } + n = append(n[:i], n[i+1:]...) + return true +} + +func (n Namespaces) Add(t NamespaceType, path string) { + i := n.index(t) + if i == -1 { + n = append(n, Namespace{Type: t, Path: path}) + return + } + n[i].Path = path +} + +func (n Namespaces) index(t NamespaceType) int { + for i, ns := range n { + if ns.Type == t { + return i + } + } + return -1 +} + +func (n Namespaces) Contains(t NamespaceType) bool { + return n.index(t) != -1 +} + // Config defines configuration options for executing a process inside a contained environment. type Config struct { // Mount specific options. @@ -38,7 +89,7 @@ type Config struct { // Namespaces specifies the container's namespaces that it should setup when cloning the init process // If a namespace is not provided that namespace is shared from the container's parent process - Namespaces map[string]bool `json:"namespaces,omitempty"` + Namespaces Namespaces `json:"namespaces,omitempty"` // Capabilities specify the capabilities to keep when executing the process inside the container // All capbilities not specified will be dropped from the processes capability mask @@ -47,9 +98,6 @@ type Config struct { // Networks specifies the container's network setup to be created Networks []*Network `json:"networks,omitempty"` - // Ipc specifies the container's ipc setup to be created - IpcNsPath string `json:"ipc,omitempty"` - // Routes can be specified to create entries in the route table as the container is started Routes []*Route `json:"routes,omitempty"` diff --git a/vendor/src/github.com/docker/libcontainer/config_test.go b/vendor/src/github.com/docker/libcontainer/config_test.go index 5981281153..5c73f84af5 100644 --- a/vendor/src/github.com/docker/libcontainer/config_test.go +++ b/vendor/src/github.com/docker/libcontainer/config_test.go @@ -64,12 +64,12 @@ func TestConfigJsonFormat(t *testing.T) { t.Fail() } - if !container.Namespaces["NEWNET"] { + if !container.Namespaces.Contains(NEWNET) { t.Log("namespaces should contain NEWNET") t.Fail() } - if container.Namespaces["NEWUSER"] { + if container.Namespaces.Contains(NEWUSER) { t.Log("namespaces should not contain NEWUSER") t.Fail() } diff --git a/vendor/src/github.com/docker/libcontainer/integration/exec_test.go b/vendor/src/github.com/docker/libcontainer/integration/exec_test.go index 8f4dae0f9e..f0728c5817 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/exec_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/exec_test.go @@ -4,6 +4,8 @@ import ( "os" "strings" "testing" + + "github.com/docker/libcontainer" ) func TestExecPS(t *testing.T) { @@ -55,7 +57,6 @@ func TestIPCPrivate(t *testing.T) { } config := newTemplateConfig(rootfs) - config.Namespaces["NEWIPC"] = true buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") if err != nil { t.Fatal(err) @@ -87,7 +88,7 @@ func TestIPCHost(t *testing.T) { } config := newTemplateConfig(rootfs) - config.Namespaces["NEWIPC"] = false + config.Namespaces.Remove(libcontainer.NEWIPC) buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") if err != nil { t.Fatal(err) @@ -119,8 +120,7 @@ func TestIPCJoinPath(t *testing.T) { } config := newTemplateConfig(rootfs) - config.Namespaces["NEWIPC"] = false - config.IpcNsPath = "/proc/1/ns/ipc" + config.Namespaces.Add(libcontainer.NEWIPC, "/proc/1/ns/ipc") buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") if err != nil { @@ -148,8 +148,7 @@ func TestIPCBadPath(t *testing.T) { defer remove(rootfs) config := newTemplateConfig(rootfs) - config.Namespaces["NEWIPC"] = false - config.IpcNsPath = "/proc/1/ns/ipcc" + config.Namespaces.Add(libcontainer.NEWIPC, "/proc/1/ns/ipcc") _, _, err = runContainer(config, "", "true") if err == nil { diff --git a/vendor/src/github.com/docker/libcontainer/integration/execin_test.go b/vendor/src/github.com/docker/libcontainer/integration/execin_test.go new file mode 100644 index 0000000000..86d9c5c260 --- /dev/null +++ b/vendor/src/github.com/docker/libcontainer/integration/execin_test.go @@ -0,0 +1,140 @@ +package integration + +import ( + "os" + "os/exec" + "strings" + "sync" + "testing" + + "github.com/docker/libcontainer" + "github.com/docker/libcontainer/namespaces" +) + +func TestExecIn(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootFs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + if err := writeConfig(config); err != nil { + t.Fatalf("failed to write config %s", err) + } + + containerCmd, statePath, containerErr := startLongRunningContainer(config) + defer func() { + // kill the container + if containerCmd.Process != nil { + containerCmd.Process.Kill() + } + if err := <-containerErr; err != nil { + t.Fatal(err) + } + }() + + // start the exec process + state, err := libcontainer.GetState(statePath) + if err != nil { + t.Fatalf("failed to get state %s", err) + } + buffers := newStdBuffers() + execErr := make(chan error, 1) + go func() { + _, err := namespaces.ExecIn(config, state, []string{"ps"}, + os.Args[0], "exec", buffers.Stdin, buffers.Stdout, buffers.Stderr, + "", nil) + execErr <- err + }() + if err := <-execErr; err != nil { + t.Fatalf("exec finished with error %s", err) + } + + out := buffers.Stdout.String() + if !strings.Contains(out, "sleep 10") || !strings.Contains(out, "ps") { + t.Fatalf("unexpected running process, output %q", out) + } +} + +func TestExecInRlimit(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootFs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + if err := writeConfig(config); err != nil { + t.Fatalf("failed to write config %s", err) + } + + containerCmd, statePath, containerErr := startLongRunningContainer(config) + defer func() { + // kill the container + if containerCmd.Process != nil { + containerCmd.Process.Kill() + } + if err := <-containerErr; err != nil { + t.Fatal(err) + } + }() + + // start the exec process + state, err := libcontainer.GetState(statePath) + if err != nil { + t.Fatalf("failed to get state %s", err) + } + buffers := newStdBuffers() + execErr := make(chan error, 1) + go func() { + _, err := namespaces.ExecIn(config, state, []string{"/bin/sh", "-c", "ulimit -n"}, + os.Args[0], "exec", buffers.Stdin, buffers.Stdout, buffers.Stderr, + "", nil) + execErr <- err + }() + if err := <-execErr; err != nil { + t.Fatalf("exec finished with error %s", err) + } + + out := buffers.Stdout.String() + if limit := strings.TrimSpace(out); limit != "1024" { + t.Fatalf("expected rlimit to be 1024, got %s", limit) + } +} + +// start a long-running container so we have time to inspect execin processes +func startLongRunningContainer(config *libcontainer.Config) (*exec.Cmd, string, chan error) { + containerErr := make(chan error, 1) + containerCmd := &exec.Cmd{} + var statePath string + + createCmd := func(container *libcontainer.Config, console, dataPath, init string, + pipe *os.File, args []string) *exec.Cmd { + containerCmd = namespaces.DefaultCreateCommand(container, console, dataPath, init, pipe, args) + statePath = dataPath + return containerCmd + } + + var containerStart sync.WaitGroup + containerStart.Add(1) + go func() { + buffers := newStdBuffers() + _, err := namespaces.Exec(config, + buffers.Stdin, buffers.Stdout, buffers.Stderr, + "", config.RootFs, []string{"sleep", "10"}, + createCmd, containerStart.Done) + containerErr <- err + }() + containerStart.Wait() + + return containerCmd, statePath, containerErr +} diff --git a/vendor/src/github.com/docker/libcontainer/integration/init_test.go b/vendor/src/github.com/docker/libcontainer/integration/init_test.go index 9954c0f8e5..3106a5fb1e 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/init_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/init_test.go @@ -1,33 +1,76 @@ package integration import ( + "encoding/json" "log" "os" "runtime" + "github.com/docker/libcontainer" "github.com/docker/libcontainer/namespaces" + _ "github.com/docker/libcontainer/namespaces/nsenter" ) // init runs the libcontainer initialization code because of the busybox style needs // to work around the go runtime and the issues with forking func init() { - if len(os.Args) < 2 || os.Args[1] != "init" { + if len(os.Args) < 2 { return } - runtime.LockOSThread() + // handle init + if len(os.Args) >= 2 && os.Args[1] == "init" { + runtime.LockOSThread() - container, err := loadConfig() - if err != nil { - log.Fatal(err) + container, err := loadConfig() + if err != nil { + log.Fatal(err) + } + + rootfs, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + + if err := namespaces.Init(container, rootfs, "", os.NewFile(3, "pipe"), os.Args[3:]); err != nil { + log.Fatalf("unable to initialize for container: %s", err) + } + os.Exit(1) } - rootfs, err := os.Getwd() - if err != nil { - log.Fatal(err) - } + // handle execin + if len(os.Args) >= 2 && os.Args[0] == "nsenter-exec" { + runtime.LockOSThread() - if err := namespaces.Init(container, rootfs, "", os.NewFile(3, "pipe"), os.Args[3:]); err != nil { - log.Fatalf("unable to initialize for container: %s", err) + // User args are passed after '--' in the command line. + userArgs := findUserArgs() + + config, err := loadConfigFromFd() + if err != nil { + log.Fatalf("docker-exec: unable to receive config from sync pipe: %s", err) + } + + if err := namespaces.FinalizeSetns(config, userArgs); err != nil { + log.Fatalf("docker-exec: failed to exec: %s", err) + } + os.Exit(1) } - os.Exit(1) +} + +func findUserArgs() []string { + for i, a := range os.Args { + if a == "--" { + return os.Args[i+1:] + } + } + return []string{} +} + +// loadConfigFromFd loads a container's config from the sync pipe that is provided by +// fd 3 when running a process +func loadConfigFromFd() (*libcontainer.Config, error) { + var config *libcontainer.Config + if err := json.NewDecoder(os.NewFile(3, "child")).Decode(&config); err != nil { + return nil, err + } + return config, nil } diff --git a/vendor/src/github.com/docker/libcontainer/integration/template_test.go b/vendor/src/github.com/docker/libcontainer/integration/template_test.go index efcf6d5b90..7e56628c2b 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/template_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/template_test.go @@ -32,12 +32,12 @@ func newTemplateConfig(rootfs string) *libcontainer.Config { "KILL", "AUDIT_WRITE", }, - Namespaces: map[string]bool{ - "NEWNS": true, - "NEWUTS": true, - "NEWIPC": true, - "NEWPID": true, - "NEWNET": true, + Namespaces: libcontainer.Namespaces{ + {Type: libcontainer.NEWNS}, + {Type: libcontainer.NEWUTS}, + {Type: libcontainer.NEWIPC}, + {Type: libcontainer.NEWPID}, + {Type: libcontainer.NEWNET}, }, Cgroups: &cgroups.Cgroup{ Parent: "integration", diff --git a/vendor/src/github.com/docker/libcontainer/ipc/ipc.go b/vendor/src/github.com/docker/libcontainer/ipc/ipc.go deleted file mode 100644 index 147cf5571e..0000000000 --- a/vendor/src/github.com/docker/libcontainer/ipc/ipc.go +++ /dev/null @@ -1,29 +0,0 @@ -package ipc - -import ( - "fmt" - "os" - "syscall" - - "github.com/docker/libcontainer/system" -) - -// Join the IPC Namespace of specified ipc path if it exists. -// If the path does not exist then you are not joining a container. -func Initialize(nsPath string) error { - if nsPath == "" { - return nil - } - f, err := os.OpenFile(nsPath, os.O_RDONLY, 0) - if err != nil { - return fmt.Errorf("failed get IPC namespace fd: %v", err) - } - - err = system.Setns(f.Fd(), syscall.CLONE_NEWIPC) - f.Close() - - if err != nil { - return fmt.Errorf("failed to setns current IPC namespace: %v", err) - } - return nil -} diff --git a/vendor/src/github.com/docker/libcontainer/namespaces/execin.go b/vendor/src/github.com/docker/libcontainer/namespaces/execin.go index 430dc72fe6..7ce82c81bb 100644 --- a/vendor/src/github.com/docker/libcontainer/namespaces/execin.go +++ b/vendor/src/github.com/docker/libcontainer/namespaces/execin.go @@ -97,6 +97,10 @@ func FinalizeSetns(container *libcontainer.Config, args []string) error { return err } + if err := setupRlimits(container); err != nil { + return fmt.Errorf("setup rlimits %s", err) + } + if err := FinalizeNamespace(container); err != nil { return err } diff --git a/vendor/src/github.com/docker/libcontainer/namespaces/init.go b/vendor/src/github.com/docker/libcontainer/namespaces/init.go index 7c83b13761..a4400bddbc 100644 --- a/vendor/src/github.com/docker/libcontainer/namespaces/init.go +++ b/vendor/src/github.com/docker/libcontainer/namespaces/init.go @@ -13,7 +13,6 @@ import ( "github.com/docker/libcontainer" "github.com/docker/libcontainer/apparmor" "github.com/docker/libcontainer/console" - "github.com/docker/libcontainer/ipc" "github.com/docker/libcontainer/label" "github.com/docker/libcontainer/mount" "github.com/docker/libcontainer/netlink" @@ -65,7 +64,10 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip if err := json.NewDecoder(pipe).Decode(&networkState); err != nil { return err } - + // join any namespaces via a path to the namespace fd if provided + if err := joinExistingNamespaces(container.Namespaces); err != nil { + return err + } if consolePath != "" { if err := console.OpenAndDup(consolePath); err != nil { return err @@ -79,9 +81,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip return fmt.Errorf("setctty %s", err) } } - if err := ipc.Initialize(container.IpcNsPath); err != nil { - return fmt.Errorf("setup IPC %s", err) - } + if err := setupNetwork(container, networkState); err != nil { return fmt.Errorf("setup networking %s", err) } @@ -178,17 +178,17 @@ func SetupUser(u string) error { Home: "/", } - passwdFile, err := user.GetPasswdFile() + passwdPath, err := user.GetPasswdPath() if err != nil { return err } - groupFile, err := user.GetGroupFile() + groupPath, err := user.GetGroupPath() if err != nil { return err } - execUser, err := user.GetExecUserFile(u, &defaultExecUser, passwdFile, groupFile) + execUser, err := user.GetExecUserPath(u, &defaultExecUser, passwdPath, groupPath) if err != nil { return fmt.Errorf("get supplementary groups %s", err) } @@ -308,3 +308,22 @@ func LoadContainerEnvironment(container *libcontainer.Config) error { } return nil } + +// joinExistingNamespaces gets all the namespace paths specified for the container and +// does a setns on the namespace fd so that the current process joins the namespace. +func joinExistingNamespaces(namespaces []libcontainer.Namespace) error { + for _, ns := range namespaces { + if ns.Path != "" { + f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0) + if err != nil { + return err + } + err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Type])) + f.Close() + if err != nil { + return err + } + } + } + return nil +} diff --git a/vendor/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c b/vendor/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c index f060f63b13..1a81c31572 100644 --- a/vendor/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c +++ b/vendor/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c @@ -15,6 +15,10 @@ #include #include +#ifndef PR_SET_CHILD_SUBREAPER +#define PR_SET_CHILD_SUBREAPER 36 +#endif + static const kBufSize = 256; static const char *kNsEnter = "nsenter"; @@ -32,8 +36,8 @@ void get_args(int *argc, char ***argv) contents_size += kBufSize; contents = (char *)realloc(contents, contents_size); bytes_read = - read(fd, contents + contents_offset, - contents_size - contents_offset); + read(fd, contents + contents_offset, + contents_size - contents_offset); contents_offset += bytes_read; } while (bytes_read > 0); @@ -90,16 +94,17 @@ void nsenter() } if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) == -1) { - fprintf(stderr, "nsenter: failed to set child subreaper: %s", strerror(errno)); - exit(1); - } + fprintf(stderr, "nsenter: failed to set child subreaper: %s", + strerror(errno)); + exit(1); + } static const struct option longopts[] = { {"nspid", required_argument, NULL, 'n'}, {"console", required_argument, NULL, 't'}, {NULL, 0, NULL, 0} }; - + pid_t init_pid = -1; char *init_pid_str = NULL; char *console = NULL; diff --git a/vendor/src/github.com/docker/libcontainer/namespaces/types.go b/vendor/src/github.com/docker/libcontainer/namespaces/types.go deleted file mode 100644 index 16ce981e85..0000000000 --- a/vendor/src/github.com/docker/libcontainer/namespaces/types.go +++ /dev/null @@ -1,50 +0,0 @@ -package namespaces - -import "errors" - -type ( - Namespace struct { - Key string `json:"key,omitempty"` - Value int `json:"value,omitempty"` - File string `json:"file,omitempty"` - } - Namespaces []*Namespace -) - -// namespaceList is used to convert the libcontainer types -// into the names of the files located in /proc//ns/* for -// each namespace -var ( - namespaceList = Namespaces{} - ErrUnkownNamespace = errors.New("Unknown namespace") - ErrUnsupported = errors.New("Unsupported method") -) - -func (ns *Namespace) String() string { - return ns.Key -} - -func GetNamespace(key string) *Namespace { - for _, ns := range namespaceList { - if ns.Key == key { - cpy := *ns - return &cpy - } - } - return nil -} - -// Contains returns true if the specified Namespace is -// in the slice -func (n Namespaces) Contains(ns string) bool { - return n.Get(ns) != nil -} - -func (n Namespaces) Get(ns string) *Namespace { - for _, nsp := range n { - if nsp != nil && nsp.Key == ns { - return nsp - } - } - return nil -} diff --git a/vendor/src/github.com/docker/libcontainer/namespaces/types_linux.go b/vendor/src/github.com/docker/libcontainer/namespaces/types_linux.go deleted file mode 100644 index d3079944c7..0000000000 --- a/vendor/src/github.com/docker/libcontainer/namespaces/types_linux.go +++ /dev/null @@ -1,16 +0,0 @@ -package namespaces - -import ( - "syscall" -) - -func init() { - namespaceList = Namespaces{ - {Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt"}, - {Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts"}, - {Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc"}, - {Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user"}, - {Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid"}, - {Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net"}, - } -} diff --git a/vendor/src/github.com/docker/libcontainer/namespaces/types_test.go b/vendor/src/github.com/docker/libcontainer/namespaces/types_test.go deleted file mode 100644 index 4d0a72c9b2..0000000000 --- a/vendor/src/github.com/docker/libcontainer/namespaces/types_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package namespaces - -import ( - "testing" -) - -func TestNamespacesContains(t *testing.T) { - ns := Namespaces{ - GetNamespace("NEWPID"), - GetNamespace("NEWNS"), - GetNamespace("NEWUTS"), - } - - if ns.Contains("NEWNET") { - t.Fatal("namespaces should not contain NEWNET") - } - - if !ns.Contains("NEWPID") { - t.Fatal("namespaces should contain NEWPID but does not") - } - - withNil := Namespaces{ - GetNamespace("UNDEFINED"), // this element will be nil - GetNamespace("NEWPID"), - } - - if !withNil.Contains("NEWPID") { - t.Fatal("namespaces should contain NEWPID but does not") - } -} diff --git a/vendor/src/github.com/docker/libcontainer/namespaces/utils.go b/vendor/src/github.com/docker/libcontainer/namespaces/utils.go index bf60cd8f0e..de71a379f8 100644 --- a/vendor/src/github.com/docker/libcontainer/namespaces/utils.go +++ b/vendor/src/github.com/docker/libcontainer/namespaces/utils.go @@ -5,6 +5,8 @@ package namespaces import ( "os" "syscall" + + "github.com/docker/libcontainer" ) type initError struct { @@ -15,6 +17,15 @@ func (i initError) Error() string { return i.Message } +var namespaceInfo = map[libcontainer.NamespaceType]int{ + libcontainer.NEWNET: syscall.CLONE_NEWNET, + libcontainer.NEWNS: syscall.CLONE_NEWNS, + libcontainer.NEWUSER: syscall.CLONE_NEWUSER, + libcontainer.NEWIPC: syscall.CLONE_NEWIPC, + libcontainer.NEWUTS: syscall.CLONE_NEWUTS, + libcontainer.NEWPID: syscall.CLONE_NEWPID, +} + // New returns a newly initialized Pipe for communication between processes func newInitPipe() (parent *os.File, child *os.File, err error) { fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) @@ -26,13 +37,9 @@ func newInitPipe() (parent *os.File, child *os.File, err error) { // GetNamespaceFlags parses the container's Namespaces options to set the correct // flags on clone, unshare, and setns -func GetNamespaceFlags(namespaces map[string]bool) (flag int) { - for key, enabled := range namespaces { - if enabled { - if ns := GetNamespace(key); ns != nil { - flag |= ns.Value - } - } +func GetNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { + for _, v := range namespaces { + flag |= namespaceInfo[v.Type] } return flag } diff --git a/vendor/src/github.com/docker/libcontainer/netlink/netlink_linux.go b/vendor/src/github.com/docker/libcontainer/netlink/netlink_linux.go index 1bf70430f2..3cc3cc94f7 100644 --- a/vendor/src/github.com/docker/libcontainer/netlink/netlink_linux.go +++ b/vendor/src/github.com/docker/libcontainer/netlink/netlink_linux.go @@ -522,11 +522,10 @@ func NetworkSetMacAddress(iface *net.Interface, macaddr string) error { var ( MULTICAST byte = 0x1 - LOCALOUI byte = 0x2 ) - if hwaddr[0]&0x1 == MULTICAST || hwaddr[0]&0x2 != LOCALOUI { - return fmt.Errorf("Incorrect Local MAC Address specified: %s", macaddr) + if hwaddr[0]&0x1 == MULTICAST { + return fmt.Errorf("Multicast MAC Address is not supported: %s", macaddr) } wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK) diff --git a/vendor/src/github.com/docker/libcontainer/network/netns.go b/vendor/src/github.com/docker/libcontainer/network/netns.go deleted file mode 100644 index 73cd8de530..0000000000 --- a/vendor/src/github.com/docker/libcontainer/network/netns.go +++ /dev/null @@ -1,39 +0,0 @@ -// +build linux - -package network - -import ( - "fmt" - "os" - "syscall" - - "github.com/docker/libcontainer/system" -) - -// crosbymichael: could make a network strategy that instead of returning veth pair names it returns a pid to an existing network namespace -type NetNS struct { -} - -func (v *NetNS) Create(n *Network, nspid int, networkState *NetworkState) error { - networkState.NsPath = n.NsPath - return nil -} - -func (v *NetNS) Initialize(config *Network, networkState *NetworkState) error { - if networkState.NsPath == "" { - return fmt.Errorf("nspath does is not specified in NetworkState") - } - - f, err := os.OpenFile(networkState.NsPath, os.O_RDONLY, 0) - if err != nil { - return fmt.Errorf("failed get network namespace fd: %v", err) - } - - if err := system.Setns(f.Fd(), syscall.CLONE_NEWNET); err != nil { - f.Close() - return fmt.Errorf("failed to setns current network namespace: %v", err) - } - - f.Close() - return nil -} diff --git a/vendor/src/github.com/docker/libcontainer/network/network.go b/vendor/src/github.com/docker/libcontainer/network/network.go index ba8f6f74e7..40b25b135b 100644 --- a/vendor/src/github.com/docker/libcontainer/network/network.go +++ b/vendor/src/github.com/docker/libcontainer/network/network.go @@ -88,6 +88,18 @@ func SetInterfaceIp(name string, rawIp string) error { return netlink.NetworkLinkAddIp(iface, ip, ipNet) } +func DeleteInterfaceIp(name string, rawIp string) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + ip, ipNet, err := net.ParseCIDR(rawIp) + if err != nil { + return err + } + return netlink.NetworkLinkDelIp(iface, ip, ipNet) +} + func SetMtu(name string, mtu int) error { iface, err := net.InterfaceByName(name) if err != nil { diff --git a/vendor/src/github.com/docker/libcontainer/network/strategy.go b/vendor/src/github.com/docker/libcontainer/network/strategy.go index be5ec93b71..019fe62f41 100644 --- a/vendor/src/github.com/docker/libcontainer/network/strategy.go +++ b/vendor/src/github.com/docker/libcontainer/network/strategy.go @@ -13,7 +13,6 @@ var ( var strategies = map[string]NetworkStrategy{ "veth": &Veth{}, "loopback": &Loopback{}, - "netns": &NetNS{}, } // NetworkStrategy represents a specific network configuration for diff --git a/vendor/src/github.com/docker/libcontainer/network/types.go b/vendor/src/github.com/docker/libcontainer/network/types.go index ea0741be1c..dcf00420f3 100644 --- a/vendor/src/github.com/docker/libcontainer/network/types.go +++ b/vendor/src/github.com/docker/libcontainer/network/types.go @@ -8,9 +8,6 @@ type Network struct { // Type sets the networks type, commonly veth and loopback Type string `json:"type,omitempty"` - // Path to network namespace - NsPath string `json:"ns_path,omitempty"` - // The bridge to use. Bridge string `json:"bridge,omitempty"` @@ -50,6 +47,4 @@ type NetworkState struct { VethHost string `json:"veth_host,omitempty"` // The name of the veth interface created inside the container for the child. VethChild string `json:"veth_child,omitempty"` - // Net namespace path. - NsPath string `json:"ns_path,omitempty"` } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/notify_linux.go b/vendor/src/github.com/docker/libcontainer/notify_linux.go similarity index 54% rename from vendor/src/github.com/docker/libcontainer/cgroups/fs/notify_linux.go rename to vendor/src/github.com/docker/libcontainer/notify_linux.go index d92063bade..a4923273a3 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/notify_linux.go +++ b/vendor/src/github.com/docker/libcontainer/notify_linux.go @@ -1,33 +1,29 @@ // +build linux -package fs +package libcontainer import ( "fmt" + "io/ioutil" "os" "path/filepath" "syscall" - - "github.com/docker/libcontainer/cgroups" ) -// NotifyOnOOM sends signals on the returned channel when the cgroup reaches -// its memory limit. The channel is closed when the cgroup is removed. -func NotifyOnOOM(c *cgroups.Cgroup) (<-chan struct{}, error) { - d, err := getCgroupData(c, 0) +const oomCgroupName = "memory" + +// NotifyOnOOM returns channel on which you can expect event about OOM, +// if process died without OOM this channel will be closed. +// s is current *libcontainer.State for container. +func NotifyOnOOM(s *State) (<-chan struct{}, error) { + dir := s.CgroupPaths[oomCgroupName] + if dir == "" { + return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName) + } + oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control")) if err != nil { return nil, err } - - return notifyOnOOM(d) -} - -func notifyOnOOM(d *data) (<-chan struct{}, error) { - dir, err := d.path("memory") - if err != nil { - return nil, err - } - fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0) if syserr != 0 { return nil, syserr @@ -35,48 +31,32 @@ func notifyOnOOM(d *data) (<-chan struct{}, error) { eventfd := os.NewFile(fd, "eventfd") - oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control")) - if err != nil { - eventfd.Close() - return nil, err - } - - var ( - eventControlPath = filepath.Join(dir, "cgroup.event_control") - data = fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd()) - ) - - if err := writeFile(dir, "cgroup.event_control", data); err != nil { + eventControlPath := filepath.Join(dir, "cgroup.event_control") + data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd()) + if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil { eventfd.Close() oomControl.Close() return nil, err } - ch := make(chan struct{}) - go func() { defer func() { close(ch) eventfd.Close() oomControl.Close() }() - buf := make([]byte, 8) - for { if _, err := eventfd.Read(buf); err != nil { return } - // When a cgroup is destroyed, an event is sent to eventfd. // So if the control path is gone, return instead of notifying. if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) { return } - ch <- struct{}{} } }() - return ch, nil } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/notify_linux_test.go b/vendor/src/github.com/docker/libcontainer/notify_linux_test.go similarity index 67% rename from vendor/src/github.com/docker/libcontainer/cgroups/fs/notify_linux_test.go rename to vendor/src/github.com/docker/libcontainer/notify_linux_test.go index a11880cb66..5d1d54576b 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/notify_linux_test.go +++ b/vendor/src/github.com/docker/libcontainer/notify_linux_test.go @@ -1,38 +1,48 @@ // +build linux -package fs +package libcontainer import ( "encoding/binary" "fmt" + "io/ioutil" + "os" + "path/filepath" "syscall" "testing" "time" ) func TestNotifyOnOOM(t *testing.T) { - helper := NewCgroupTestUtil("memory", t) - defer helper.cleanup() - - helper.writeFileContents(map[string]string{ - "memory.oom_control": "", - "cgroup.event_control": "", - }) - + memoryPath, err := ioutil.TempDir("", "testnotifyoom-") + if err != nil { + t.Fatal(err) + } + oomPath := filepath.Join(memoryPath, "memory.oom_control") + eventPath := filepath.Join(memoryPath, "cgroup.event_control") + if err := ioutil.WriteFile(oomPath, []byte{}, 0700); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(eventPath, []byte{}, 0700); err != nil { + t.Fatal(err) + } var eventFd, oomControlFd int - - ooms, err := notifyOnOOM(helper.CgroupData) + st := &State{ + CgroupPaths: map[string]string{ + "memory": memoryPath, + }, + } + ooms, err := NotifyOnOOM(st) if err != nil { t.Fatal("expected no error, got:", err) } - memoryPath, _ := helper.CgroupData.path("memory") - data, err := readFile(memoryPath, "cgroup.event_control") + data, err := ioutil.ReadFile(eventPath) if err != nil { t.Fatal("couldn't read event control file:", err) } - if _, err := fmt.Sscanf(data, "%d %d", &eventFd, &oomControlFd); err != nil { + if _, err := fmt.Sscanf(string(data), "%d %d", &eventFd, &oomControlFd); err != nil { t.Fatalf("invalid control data %q: %s", data, err) } @@ -62,7 +72,9 @@ func TestNotifyOnOOM(t *testing.T) { // simulate what happens when a cgroup is destroyed by cleaning up and then // writing to the eventfd. - helper.cleanup() + if err := os.RemoveAll(memoryPath); err != nil { + t.Fatal(err) + } if _, err := syscall.Write(efd, buf); err != nil { t.Fatal("unable to write to eventfd:", err) } diff --git a/vendor/src/github.com/docker/libcontainer/sample_configs/apparmor.json b/vendor/src/github.com/docker/libcontainer/sample_configs/apparmor.json index f739df1006..96f73cb794 100644 --- a/vendor/src/github.com/docker/libcontainer/sample_configs/apparmor.json +++ b/vendor/src/github.com/docker/libcontainer/sample_configs/apparmor.json @@ -176,13 +176,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"type":"NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", diff --git a/vendor/src/github.com/docker/libcontainer/sample_configs/attach_to_bridge.json b/vendor/src/github.com/docker/libcontainer/sample_configs/attach_to_bridge.json index 0795e6c143..e5c03a7ef4 100644 --- a/vendor/src/github.com/docker/libcontainer/sample_configs/attach_to_bridge.json +++ b/vendor/src/github.com/docker/libcontainer/sample_configs/attach_to_bridge.json @@ -175,13 +175,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"type": "NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", diff --git a/vendor/src/github.com/docker/libcontainer/sample_configs/minimal.json b/vendor/src/github.com/docker/libcontainer/sample_configs/minimal.json index 8d85ddf7da..01de467468 100644 --- a/vendor/src/github.com/docker/libcontainer/sample_configs/minimal.json +++ b/vendor/src/github.com/docker/libcontainer/sample_configs/minimal.json @@ -181,13 +181,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"type": "NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", diff --git a/vendor/src/github.com/docker/libcontainer/sample_configs/route_source_address_selection.json b/vendor/src/github.com/docker/libcontainer/sample_configs/route_source_address_selection.json index d4baf94cde..9c62045a4b 100644 --- a/vendor/src/github.com/docker/libcontainer/sample_configs/route_source_address_selection.json +++ b/vendor/src/github.com/docker/libcontainer/sample_configs/route_source_address_selection.json @@ -175,13 +175,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"type": "NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", diff --git a/vendor/src/github.com/docker/libcontainer/sample_configs/selinux.json b/vendor/src/github.com/docker/libcontainer/sample_configs/selinux.json index ce383e2cc2..15556488a2 100644 --- a/vendor/src/github.com/docker/libcontainer/sample_configs/selinux.json +++ b/vendor/src/github.com/docker/libcontainer/sample_configs/selinux.json @@ -177,13 +177,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"type": "NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", diff --git a/vendor/src/github.com/docker/libcontainer/user/MAINTAINERS b/vendor/src/github.com/docker/libcontainer/user/MAINTAINERS index 18e05a3070..edbe200669 100644 --- a/vendor/src/github.com/docker/libcontainer/user/MAINTAINERS +++ b/vendor/src/github.com/docker/libcontainer/user/MAINTAINERS @@ -1 +1,2 @@ Tianon Gravi (@tianon) +Aleksa Sarai (@cyphar) diff --git a/vendor/src/github.com/docker/libcontainer/user/lookup_unix.go b/vendor/src/github.com/docker/libcontainer/user/lookup_unix.go index 409c114e26..758b734c22 100644 --- a/vendor/src/github.com/docker/libcontainer/user/lookup_unix.go +++ b/vendor/src/github.com/docker/libcontainer/user/lookup_unix.go @@ -9,22 +9,22 @@ import ( // Unix-specific path to the passwd and group formatted files. const ( - unixPasswdFile = "/etc/passwd" - unixGroupFile = "/etc/group" + unixPasswdPath = "/etc/passwd" + unixGroupPath = "/etc/group" ) -func GetPasswdFile() (string, error) { - return unixPasswdFile, nil +func GetPasswdPath() (string, error) { + return unixPasswdPath, nil } func GetPasswd() (io.ReadCloser, error) { - return os.Open(unixPasswdFile) + return os.Open(unixPasswdPath) } -func GetGroupFile() (string, error) { - return unixGroupFile, nil +func GetGroupPath() (string, error) { + return unixGroupPath, nil } func GetGroup() (io.ReadCloser, error) { - return os.Open(unixGroupFile) + return os.Open(unixGroupPath) } diff --git a/vendor/src/github.com/docker/libcontainer/user/lookup_unsupported.go b/vendor/src/github.com/docker/libcontainer/user/lookup_unsupported.go index 0f15c57d82..7217948870 100644 --- a/vendor/src/github.com/docker/libcontainer/user/lookup_unsupported.go +++ b/vendor/src/github.com/docker/libcontainer/user/lookup_unsupported.go @@ -4,7 +4,7 @@ package user import "io" -func GetPasswdFile() (string, error) { +func GetPasswdPath() (string, error) { return "", ErrUnsupported } @@ -12,7 +12,7 @@ func GetPasswd() (io.ReadCloser, error) { return nil, ErrUnsupported } -func GetGroupFile() (string, error) { +func GetGroupPath() (string, error) { return "", ErrUnsupported } diff --git a/vendor/src/github.com/docker/libcontainer/user/user.go b/vendor/src/github.com/docker/libcontainer/user/user.go index 69387f2ef6..d7439f12e3 100644 --- a/vendor/src/github.com/docker/libcontainer/user/user.go +++ b/vendor/src/github.com/docker/libcontainer/user/user.go @@ -197,11 +197,11 @@ type ExecUser struct { Home string } -// GetExecUserFile is a wrapper for GetExecUser. It reads data from each of the +// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the // given file paths and uses that data as the arguments to GetExecUser. If the // files cannot be opened for any reason, the error is ignored and a nil // io.Reader is passed instead. -func GetExecUserFile(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) { +func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) { passwd, err := os.Open(passwdPath) if err != nil { passwd = nil