From adfccb7c1c4978234b0340bf6056f4c430a41c4b Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Thu, 8 Jan 2015 12:15:03 -0800 Subject: [PATCH] Updating to latest libcontainer. --- Godeps/Godeps.json | 4 +- .../github.com/docker/libcontainer/Dockerfile | 3 +- .../docker/libcontainer/MAINTAINERS | 1 + .../github.com/docker/libcontainer/Makefile | 6 +- .../github.com/docker/libcontainer/SPEC.md | 25 ++++ .../libcontainer/cgroups/fs/apply_raw.go | 2 +- .../docker/libcontainer/cgroups/fs/memory.go | 9 +- .../cgroups/fs/stats_util_test.go | 6 +- .../docker/libcontainer/cgroups/utils.go | 34 ++++- .../github.com/docker/libcontainer/config.go | 50 ++++++- .../docker/libcontainer/config_test.go | 19 ++- .../libcontainer/integration/exec_test.go | 18 +-- .../libcontainer/integration/execin_test.go | 140 ++++++++++++++++++ .../libcontainer/integration/init_test.go | 67 +++++++-- .../libcontainer/integration/template_test.go | 14 +- .../docker/libcontainer/namespaces/execin.go | 4 + .../docker/libcontainer/namespaces/init.go | 8 +- .../libcontainer/namespaces/nsenter/nsenter.c | 15 +- .../docker/libcontainer/namespaces/utils.go | 18 +-- .../libcontainer/netlink/netlink_linux.go | 5 +- .../docker/libcontainer/network/network.go | 12 ++ .../{cgroups/fs => }/notify_linux.go | 52 ++----- .../{cgroups/fs => }/notify_linux_test.go | 42 ++++-- .../libcontainer/sample_configs/apparmor.json | 10 +- .../sample_configs/attach_to_bridge.json | 10 +- .../libcontainer/sample_configs/minimal.json | 10 +- .../route_source_address_selection.json | 10 +- .../libcontainer/sample_configs/selinux.json | 10 +- .../docker/libcontainer/user/MAINTAINERS | 1 + .../docker/libcontainer/user/lookup_unix.go | 16 +- .../libcontainer/user/lookup_unsupported.go | 4 +- .../docker/libcontainer/user/user.go | 4 +- container/docker/handler.go | 2 +- 33 files changed, 456 insertions(+), 175 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/integration/execin_test.go rename Godeps/_workspace/src/github.com/docker/libcontainer/{cgroups/fs => }/notify_linux.go (54%) rename Godeps/_workspace/src/github.com/docker/libcontainer/{cgroups/fs => }/notify_linux_test.go (67%) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 96890b1c..bf08b438 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -59,8 +59,8 @@ }, { "ImportPath": "github.com/docker/libcontainer", - "Comment": "v1.2.0-173-g58fc931", - "Rev": "58fc93160e03387a4f41dcf4aed2e376c4a92db4" + "Comment": "v1.4.0-52-gd7dea0e", + "Rev": "d7dea0e925315bab640115053204c16718839b1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient", diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/Dockerfile b/Godeps/_workspace/src/github.com/docker/libcontainer/Dockerfile index 614e5979..0771c808 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/Dockerfile +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/Dockerfile @@ -1,6 +1,5 @@ -FROM crosbymichael/golang +FROM golang:1.4 -RUN apt-get update && apt-get install -y gcc make RUN go get golang.org/x/tools/cmd/cover ENV GOPATH $GOPATH:/go/src/github.com/docker/libcontainer/vendor diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/libcontainer/MAINTAINERS index 7295c603..52351317 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/MAINTAINERS +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/MAINTAINERS @@ -2,4 +2,5 @@ Michael Crosby (@crosbymichael) Rohit Jnagal (@rjnagal) Victor Marmol (@vmarmol) Mrunal Patel (@mrunalp) +Alexandr Morozov (@LK4D4) update-vendor.sh: Tianon Gravi (@tianon) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile b/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile index 0c4dda7c..f94171b0 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/SPEC.md b/Godeps/_workspace/src/github.com/docker/libcontainer/SPEC.md index f5afaadc..d83d758d 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/SPEC.md +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go index 6f85793d..f05377f2 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go @@ -105,7 +105,7 @@ func GetStats(systemPaths map[string]string) (*cgroups.Stats, error) { stats := cgroups.NewStats() for name, path := range systemPaths { sys, ok := subsystems[name] - if !ok { + if !ok || !cgroups.PathExists(path) { continue } if err := sys.GetStats(path, stats); err != nil { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go index 3f9647c2..01713fd7 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go index 1a9e590f..c55ba938 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go @@ -53,7 +53,7 @@ func expectBlkioStatsEquals(t *testing.T, expected, actual cgroups.BlkioStats) { } if err := blkioStatEntryEquals(expected.IoMergedRecursive, actual.IoMergedRecursive); err != nil { - log.Printf("blkio IoMergedRecursive do not match - %s vs %s\n", expected.IoMergedRecursive, actual.IoMergedRecursive) + log.Printf("blkio IoMergedRecursive do not match - %v vs %v\n", expected.IoMergedRecursive, actual.IoMergedRecursive) t.Fail() } @@ -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/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/utils.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/utils.go index 224a20b9..a360904c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/utils.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/utils.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/docker/docker/pkg/mount" ) @@ -173,7 +174,7 @@ func ParseCgroupFile(subsystem string, r io.Reader) (string, error) { return "", NewNotFoundError(subsystem) } -func pathExists(path string) bool { +func PathExists(path string) bool { if _, err := os.Stat(path); err != nil { return false } @@ -182,7 +183,7 @@ func pathExists(path string) bool { func EnterPid(cgroupPaths map[string]string, pid int) error { for _, path := range cgroupPaths { - if pathExists(path) { + if PathExists(path) { if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { return err @@ -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/Godeps/_workspace/src/github.com/docker/libcontainer/config.go b/Godeps/_workspace/src/github.com/docker/libcontainer/config.go index 94c2bd98..7ab9a9a7 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/config.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/config.go @@ -10,11 +10,55 @@ 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 { - Name string `json:"name"` - Path string `json:"path,omitempty"` + 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. @@ -45,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 []Namespace `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 diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/config_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/config_test.go index b4e16bf0..f2287fc7 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/config_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/config_test.go @@ -64,12 +64,12 @@ func TestConfigJsonFormat(t *testing.T) { t.Fail() } - if getNamespaceIndex(container, "NEWNET") == -1 { + if !container.Namespaces.Contains(NEWNET) { t.Log("namespaces should contain NEWNET") t.Fail() } - if getNamespaceIndex(container, "NEWUSER") != -1 { + if container.Namespaces.Contains(NEWUSER) { t.Log("namespaces should not contain NEWUSER") t.Fail() } @@ -159,11 +159,14 @@ func TestSelinuxLabels(t *testing.T) { } } -func getNamespaceIndex(config *Config, name string) int { - for i, v := range config.Namespaces { - if v.Name == name { - return i - } +func TestRemoveNamespace(t *testing.T) { + ns := Namespaces{ + {Type: NEWNET}, + } + if !ns.Remove(NEWNET) { + t.Fatal("NEWNET was not removed") + } + if len(ns) != 0 { + t.Fatalf("namespaces should have 0 items but reports %d", len(ns)) } - return -1 } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/exec_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/exec_test.go index cf749efb..f0728c58 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/exec_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/exec_test.go @@ -88,8 +88,7 @@ func TestIPCHost(t *testing.T) { } config := newTemplateConfig(rootfs) - i := getNamespaceIndex(config, "NEWIPC") - config.Namespaces = append(config.Namespaces[:i], config.Namespaces[i+1:]...) + config.Namespaces.Remove(libcontainer.NEWIPC) buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") if err != nil { t.Fatal(err) @@ -121,8 +120,7 @@ func TestIPCJoinPath(t *testing.T) { } config := newTemplateConfig(rootfs) - i := getNamespaceIndex(config, "NEWIPC") - config.Namespaces[i].Path = "/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 { @@ -150,8 +148,7 @@ func TestIPCBadPath(t *testing.T) { defer remove(rootfs) config := newTemplateConfig(rootfs) - i := getNamespaceIndex(config, "NEWIPC") - config.Namespaces[i].Path = "/proc/1/ns/ipcc" + config.Namespaces.Add(libcontainer.NEWIPC, "/proc/1/ns/ipcc") _, _, err = runContainer(config, "", "true") if err == nil { @@ -179,12 +176,3 @@ func TestRlimit(t *testing.T) { t.Fatalf("expected rlimit to be 1024, got %s", limit) } } - -func getNamespaceIndex(config *libcontainer.Config, name string) int { - for i, v := range config.Namespaces { - if v.Name == name { - return i - } - } - return -1 -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/execin_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/execin_test.go new file mode 100644 index 00000000..86d9c5c2 --- /dev/null +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/integration/init_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/init_test.go index 9954c0f8..3106a5fb 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/init_test.go +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/integration/template_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/template_test.go index f37070ff..98846eb1 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/template_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/template_test.go @@ -32,13 +32,13 @@ func newTemplateConfig(rootfs string) *libcontainer.Config { "KILL", "AUDIT_WRITE", }, - Namespaces: []libcontainer.Namespace{ - {Name: "NEWNS"}, - {Name: "NEWUTS"}, - {Name: "NEWIPC"}, - {Name: "NEWPID"}, - {Name: "NEWNET"}, - }, + Namespaces: libcontainer.Namespaces([]libcontainer.Namespace{ + {Type: libcontainer.NEWNS}, + {Type: libcontainer.NEWUTS}, + {Type: libcontainer.NEWIPC}, + {Type: libcontainer.NEWPID}, + {Type: libcontainer.NEWNET}, + }), Cgroups: &cgroups.Cgroup{ Parent: "integration", AllowAllDevices: false, diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/execin.go b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/execin.go index 430dc72f..7ce82c81 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/execin.go +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/init.go b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/init.go index 5c7e1a71..a4400bdd 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/init.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/init.go @@ -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) } @@ -318,7 +318,7 @@ func joinExistingNamespaces(namespaces []libcontainer.Namespace) error { if err != nil { return err } - err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Name])) + err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Type])) f.Close() if err != nil { return err diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c index f060f63b..b735b1fa 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c @@ -32,8 +32,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); @@ -89,17 +89,20 @@ void nsenter() return; } + #ifdef PR_SET_CHILD_SUBREAPER 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); + } + #endif 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/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/utils.go b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/utils.go index 556ea669..de71a379 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/utils.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/utils.go @@ -17,13 +17,13 @@ func (i initError) Error() string { return i.Message } -var namespaceInfo = map[string]int{ - "NEWNET": syscall.CLONE_NEWNET, - "NEWNS": syscall.CLONE_NEWNS, - "NEWUSER": syscall.CLONE_NEWUSER, - "NEWIPC": syscall.CLONE_NEWIPC, - "NEWUTS": syscall.CLONE_NEWUTS, - "NEWPID": syscall.CLONE_NEWPID, +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 @@ -37,9 +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 []libcontainer.Namespace) (flag int) { +func GetNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { for _, v := range namespaces { - flag |= namespaceInfo[v.Name] + flag |= namespaceInfo[v.Type] } return flag } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux.go index 1bf70430..3cc3cc94 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux.go +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/network/network.go b/Godeps/_workspace/src/github.com/docker/libcontainer/network/network.go index ba8f6f74..40b25b13 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/network/network.go +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/notify_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/notify_linux.go similarity index 54% rename from Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/notify_linux.go rename to Godeps/_workspace/src/github.com/docker/libcontainer/notify_linux.go index d92063ba..a4923273 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/notify_linux.go +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/notify_linux_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/notify_linux_test.go similarity index 67% rename from Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/notify_linux_test.go rename to Godeps/_workspace/src/github.com/docker/libcontainer/notify_linux_test.go index a11880cb..5d1d5457 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/notify_linux_test.go +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/apparmor.json b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/apparmor.json index 50421ec8..96f73cb7 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/apparmor.json +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/apparmor.json @@ -177,11 +177,11 @@ ], "hostname": "koye", "namespaces": [ - {"name":"NEWIPC"}, - {"name": "NEWNET"}, - {"name": "NEWNS"}, - {"name": "NEWPID"}, - {"name": "NEWUTS"} + {"type":"NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} ], "networks": [ { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/attach_to_bridge.json b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/attach_to_bridge.json index 9b190293..e5c03a7e 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/attach_to_bridge.json +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/attach_to_bridge.json @@ -176,11 +176,11 @@ ], "hostname": "koye", "namespaces": [ - {"name": "NEWIPC"}, - {"name": "NEWNET"}, - {"name": "NEWNS"}, - {"name": "NEWPID"}, - {"name": "NEWUTS"} + {"type": "NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} ], "networks": [ { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/minimal.json b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/minimal.json index 720be64f..01de4674 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/minimal.json +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/minimal.json @@ -182,11 +182,11 @@ ], "hostname": "koye", "namespaces": [ - {"name": "NEWIPC"}, - {"name": "NEWNET"}, - {"name": "NEWNS"}, - {"name": "NEWPID"}, - {"name": "NEWUTS"} + {"type": "NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} ], "networks": [ { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/route_source_address_selection.json b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/route_source_address_selection.json index f403996d..9c62045a 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/route_source_address_selection.json +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/route_source_address_selection.json @@ -176,11 +176,11 @@ ], "hostname": "koye", "namespaces": [ - {"name": "NEWIPC"}, - {"name": "NEWNET"}, - {"name": "NEWNS"}, - {"name": "NEWPID"}, - {"name": "NEWUTS"} + {"type": "NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} ], "networks": [ { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/selinux.json b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/selinux.json index cfb83e09..15556488 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/selinux.json +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/sample_configs/selinux.json @@ -178,11 +178,11 @@ ], "hostname": "koye", "namespaces": [ - {"name": "NEWIPC"}, - {"name": "NEWNET"}, - {"name": "NEWNS"}, - {"name": "NEWPID"}, - {"name": "NEWUTS"} + {"type": "NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} ], "networks": [ { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS index 18e05a30..edbe2006 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS @@ -1 +1,2 @@ Tianon Gravi (@tianon) +Aleksa Sarai (@cyphar) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go index 409c114e..758b734c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go index 0f15c57d..72179488 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go +++ b/Godeps/_workspace/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/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go index 69387f2e..d7439f12 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go +++ b/Godeps/_workspace/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 diff --git a/container/docker/handler.go b/container/docker/handler.go index 6ec8fdf6..435f7253 100644 --- a/container/docker/handler.go +++ b/container/docker/handler.go @@ -161,7 +161,7 @@ func (self *dockerContainerHandler) readLibcontainerConfig() (*libcontainer.Conf config = oldConfig.Config for ns := range oldConfig.OldNamespaces { config.Namespaces = append(config.Namespaces, libcontainer.Namespace{ - Name: ns, + Type: libcontainer.NamespaceType(ns), }) } }