Updating to latest libcontainer.

This commit is contained in:
Victor Marmol 2015-01-08 12:15:03 -08:00
parent 62b8382921
commit adfccb7c1c
33 changed files with 456 additions and 175 deletions

4
Godeps/Godeps.json generated
View File

@ -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",

View File

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

View File

@ -2,4 +2,5 @@ Michael Crosby <michael@docker.com> (@crosbymichael)
Rohit Jnagal <jnagal@google.com> (@rjnagal)
Victor Marmol <vmarmol@google.com> (@vmarmol)
Mrunal Patel <mpatel@redhat.com> (@mrunalp)
Alexandr Morozov <lk4d4@docker.com> (@LK4D4)
update-vendor.sh: Tianon Gravi <admwiggin@gmail.com> (@tianon)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": [
{

View File

@ -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": [
{

View File

@ -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": [
{

View File

@ -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": [
{

View File

@ -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": [
{

View File

@ -1 +1,2 @@
Tianon Gravi <admwiggin@gmail.com> (@tianon)
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)

View File

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

View File

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

View File

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

View File

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