Merge pull request #426 from vmarmol/fix-bug

Updating to latest libcontainer.
This commit is contained in:
Vish Kannan 2015-01-08 12:25:38 -08:00
commit 1cb300eecc
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", "ImportPath": "github.com/docker/libcontainer",
"Comment": "v1.2.0-173-g58fc931", "Comment": "v1.4.0-52-gd7dea0e",
"Rev": "58fc93160e03387a4f41dcf4aed2e376c4a92db4" "Rev": "d7dea0e925315bab640115053204c16718839b1e"
}, },
{ {
"ImportPath": "github.com/fsouza/go-dockerclient", "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 RUN go get golang.org/x/tools/cmd/cover
ENV GOPATH $GOPATH:/go/src/github.com/docker/libcontainer/vendor 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) Rohit Jnagal <jnagal@google.com> (@rjnagal)
Victor Marmol <vmarmol@google.com> (@vmarmol) Victor Marmol <vmarmol@google.com> (@vmarmol)
Mrunal Patel <mpatel@redhat.com> (@mrunalp) Mrunal Patel <mpatel@redhat.com> (@mrunalp)
Alexandr Morozov <lk4d4@docker.com> (@LK4D4)
update-vendor.sh: Tianon Gravi <admwiggin@gmail.com> (@tianon) update-vendor.sh: Tianon Gravi <admwiggin@gmail.com> (@tianon)

View File

@ -1,13 +1,13 @@
all: all:
docker build -t docker/libcontainer . docker build -t dockercore/libcontainer .
test: test:
# we need NET_ADMIN for the netlink tests and SYS_ADMIN for mounting # 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: 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) 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 | | Resume | Resume all processes inside the container if paused |
| Exec | Execute a new process inside of the container ( requires setns ) | | 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() stats := cgroups.NewStats()
for name, path := range systemPaths { for name, path := range systemPaths {
sys, ok := subsystems[name] sys, ok := subsystems[name]
if !ok { if !ok || !cgroups.PathExists(path) {
continue continue
} }
if err := sys.GetStats(path, stats); err != nil { 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. // By default, MemorySwap is set to twice the size of RAM.
// If you want to omit MemorySwap, set it to `-1'. // If you want to omit MemorySwap, set it to '-1'.
if d.c.MemorySwap != -1 { if d.c.MemorySwap == 0 {
if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.Memory*2, 10)); err != nil { if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.Memory*2, 10)); err != nil {
return err 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 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 { 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() t.Fail()
} }
@ -90,4 +90,8 @@ func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats)
t.Fail() 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" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/mount"
) )
@ -173,7 +174,7 @@ func ParseCgroupFile(subsystem string, r io.Reader) (string, error) {
return "", NewNotFoundError(subsystem) return "", NewNotFoundError(subsystem)
} }
func pathExists(path string) bool { func PathExists(path string) bool {
if _, err := os.Stat(path); err != nil { if _, err := os.Stat(path); err != nil {
return false return false
} }
@ -182,7 +183,7 @@ func pathExists(path string) bool {
func EnterPid(cgroupPaths map[string]string, pid int) error { func EnterPid(cgroupPaths map[string]string, pid int) error {
for _, path := range cgroupPaths { for _, path := range cgroupPaths {
if pathExists(path) { if PathExists(path) {
if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"),
[]byte(strconv.Itoa(pid)), 0700); err != nil { []byte(strconv.Itoa(pid)), 0700); err != nil {
return err return err
@ -193,13 +194,30 @@ func EnterPid(cgroupPaths map[string]string, pid int) error {
} }
// RemovePaths iterates over the provided paths removing them. // RemovePaths iterates over the provided paths removing them.
// If an error is encountered the removal proceeds and the first error is // We trying to remove all paths five times with increasing delay between tries.
// returned to ensure a partial removal is not possible. // If after all there are not removed cgroups - appropriate error will be
// returned.
func RemovePaths(paths map[string]string) (err error) { func RemovePaths(paths map[string]string) (err error) {
for _, path := range paths { delay := 10 * time.Millisecond
if rerr := os.RemoveAll(path); err == nil { for i := 0; i < 5; i++ {
err = rerr 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 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 // Namespace defines configuration for each namespace. It specifies an
// alternate path that is able to be joined via setns. // alternate path that is able to be joined via setns.
type Namespace struct { type Namespace struct {
Name string `json:"name"` Type NamespaceType `json:"type"`
Path string `json:"path,omitempty"` 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. // 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 // 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 // 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 // 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 // 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() t.Fail()
} }
if getNamespaceIndex(container, "NEWNET") == -1 { if !container.Namespaces.Contains(NEWNET) {
t.Log("namespaces should contain NEWNET") t.Log("namespaces should contain NEWNET")
t.Fail() t.Fail()
} }
if getNamespaceIndex(container, "NEWUSER") != -1 { if container.Namespaces.Contains(NEWUSER) {
t.Log("namespaces should not contain NEWUSER") t.Log("namespaces should not contain NEWUSER")
t.Fail() t.Fail()
} }
@ -159,11 +159,14 @@ func TestSelinuxLabels(t *testing.T) {
} }
} }
func getNamespaceIndex(config *Config, name string) int { func TestRemoveNamespace(t *testing.T) {
for i, v := range config.Namespaces { ns := Namespaces{
if v.Name == name { {Type: NEWNET},
return i }
} 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) config := newTemplateConfig(rootfs)
i := getNamespaceIndex(config, "NEWIPC") config.Namespaces.Remove(libcontainer.NEWIPC)
config.Namespaces = append(config.Namespaces[:i], config.Namespaces[i+1:]...)
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -121,8 +120,7 @@ func TestIPCJoinPath(t *testing.T) {
} }
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
i := getNamespaceIndex(config, "NEWIPC") config.Namespaces.Add(libcontainer.NEWIPC, "/proc/1/ns/ipc")
config.Namespaces[i].Path = "/proc/1/ns/ipc"
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
if err != nil { if err != nil {
@ -150,8 +148,7 @@ func TestIPCBadPath(t *testing.T) {
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
i := getNamespaceIndex(config, "NEWIPC") config.Namespaces.Add(libcontainer.NEWIPC, "/proc/1/ns/ipcc")
config.Namespaces[i].Path = "/proc/1/ns/ipcc"
_, _, err = runContainer(config, "", "true") _, _, err = runContainer(config, "", "true")
if err == nil { if err == nil {
@ -179,12 +176,3 @@ func TestRlimit(t *testing.T) {
t.Fatalf("expected rlimit to be 1024, got %s", limit) 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 package integration
import ( import (
"encoding/json"
"log" "log"
"os" "os"
"runtime" "runtime"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/namespaces" "github.com/docker/libcontainer/namespaces"
_ "github.com/docker/libcontainer/namespaces/nsenter"
) )
// init runs the libcontainer initialization code because of the busybox style needs // init runs the libcontainer initialization code because of the busybox style needs
// to work around the go runtime and the issues with forking // to work around the go runtime and the issues with forking
func init() { func init() {
if len(os.Args) < 2 || os.Args[1] != "init" { if len(os.Args) < 2 {
return return
} }
runtime.LockOSThread() // handle init
if len(os.Args) >= 2 && os.Args[1] == "init" {
runtime.LockOSThread()
container, err := loadConfig() container, err := loadConfig()
if err != nil { if err != nil {
log.Fatal(err) 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() // handle execin
if err != nil { if len(os.Args) >= 2 && os.Args[0] == "nsenter-exec" {
log.Fatal(err) runtime.LockOSThread()
}
if err := namespaces.Init(container, rootfs, "", os.NewFile(3, "pipe"), os.Args[3:]); err != nil { // User args are passed after '--' in the command line.
log.Fatalf("unable to initialize for container: %s", err) 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", "KILL",
"AUDIT_WRITE", "AUDIT_WRITE",
}, },
Namespaces: []libcontainer.Namespace{ Namespaces: libcontainer.Namespaces([]libcontainer.Namespace{
{Name: "NEWNS"}, {Type: libcontainer.NEWNS},
{Name: "NEWUTS"}, {Type: libcontainer.NEWUTS},
{Name: "NEWIPC"}, {Type: libcontainer.NEWIPC},
{Name: "NEWPID"}, {Type: libcontainer.NEWPID},
{Name: "NEWNET"}, {Type: libcontainer.NEWNET},
}, }),
Cgroups: &cgroups.Cgroup{ Cgroups: &cgroups.Cgroup{
Parent: "integration", Parent: "integration",
AllowAllDevices: false, AllowAllDevices: false,

View File

@ -97,6 +97,10 @@ func FinalizeSetns(container *libcontainer.Config, args []string) error {
return err return err
} }
if err := setupRlimits(container); err != nil {
return fmt.Errorf("setup rlimits %s", err)
}
if err := FinalizeNamespace(container); err != nil { if err := FinalizeNamespace(container); err != nil {
return err return err
} }

View File

@ -178,17 +178,17 @@ func SetupUser(u string) error {
Home: "/", Home: "/",
} }
passwdFile, err := user.GetPasswdFile() passwdPath, err := user.GetPasswdPath()
if err != nil { if err != nil {
return err return err
} }
groupFile, err := user.GetGroupFile() groupPath, err := user.GetGroupPath()
if err != nil { if err != nil {
return err return err
} }
execUser, err := user.GetExecUserFile(u, &defaultExecUser, passwdFile, groupFile) execUser, err := user.GetExecUserPath(u, &defaultExecUser, passwdPath, groupPath)
if err != nil { if err != nil {
return fmt.Errorf("get supplementary groups %s", err) return fmt.Errorf("get supplementary groups %s", err)
} }
@ -318,7 +318,7 @@ func joinExistingNamespaces(namespaces []libcontainer.Namespace) error {
if err != nil { if err != nil {
return err return err
} }
err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Name])) err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Type]))
f.Close() f.Close()
if err != nil { if err != nil {
return err return err

View File

@ -32,8 +32,8 @@ void get_args(int *argc, char ***argv)
contents_size += kBufSize; contents_size += kBufSize;
contents = (char *)realloc(contents, contents_size); contents = (char *)realloc(contents, contents_size);
bytes_read = bytes_read =
read(fd, contents + contents_offset, read(fd, contents + contents_offset,
contents_size - contents_offset); contents_size - contents_offset);
contents_offset += bytes_read; contents_offset += bytes_read;
} }
while (bytes_read > 0); while (bytes_read > 0);
@ -89,17 +89,20 @@ void nsenter()
return; return;
} }
#ifdef PR_SET_CHILD_SUBREAPER
if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) == -1) { if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) == -1) {
fprintf(stderr, "nsenter: failed to set child subreaper: %s", strerror(errno)); fprintf(stderr, "nsenter: failed to set child subreaper: %s",
exit(1); strerror(errno));
} exit(1);
}
#endif
static const struct option longopts[] = { static const struct option longopts[] = {
{"nspid", required_argument, NULL, 'n'}, {"nspid", required_argument, NULL, 'n'},
{"console", required_argument, NULL, 't'}, {"console", required_argument, NULL, 't'},
{NULL, 0, NULL, 0} {NULL, 0, NULL, 0}
}; };
pid_t init_pid = -1; pid_t init_pid = -1;
char *init_pid_str = NULL; char *init_pid_str = NULL;
char *console = NULL; char *console = NULL;

View File

@ -17,13 +17,13 @@ func (i initError) Error() string {
return i.Message return i.Message
} }
var namespaceInfo = map[string]int{ var namespaceInfo = map[libcontainer.NamespaceType]int{
"NEWNET": syscall.CLONE_NEWNET, libcontainer.NEWNET: syscall.CLONE_NEWNET,
"NEWNS": syscall.CLONE_NEWNS, libcontainer.NEWNS: syscall.CLONE_NEWNS,
"NEWUSER": syscall.CLONE_NEWUSER, libcontainer.NEWUSER: syscall.CLONE_NEWUSER,
"NEWIPC": syscall.CLONE_NEWIPC, libcontainer.NEWIPC: syscall.CLONE_NEWIPC,
"NEWUTS": syscall.CLONE_NEWUTS, libcontainer.NEWUTS: syscall.CLONE_NEWUTS,
"NEWPID": syscall.CLONE_NEWPID, libcontainer.NEWPID: syscall.CLONE_NEWPID,
} }
// New returns a newly initialized Pipe for communication between processes // 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 // GetNamespaceFlags parses the container's Namespaces options to set the correct
// flags on clone, unshare, and setns // flags on clone, unshare, and setns
func GetNamespaceFlags(namespaces []libcontainer.Namespace) (flag int) { func GetNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) {
for _, v := range namespaces { for _, v := range namespaces {
flag |= namespaceInfo[v.Name] flag |= namespaceInfo[v.Type]
} }
return flag return flag
} }

View File

@ -522,11 +522,10 @@ func NetworkSetMacAddress(iface *net.Interface, macaddr string) error {
var ( var (
MULTICAST byte = 0x1 MULTICAST byte = 0x1
LOCALOUI byte = 0x2
) )
if hwaddr[0]&0x1 == MULTICAST || hwaddr[0]&0x2 != LOCALOUI { if hwaddr[0]&0x1 == MULTICAST {
return fmt.Errorf("Incorrect Local MAC Address specified: %s", macaddr) return fmt.Errorf("Multicast MAC Address is not supported: %s", macaddr)
} }
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK) 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) 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 { func SetMtu(name string, mtu int) error {
iface, err := net.InterfaceByName(name) iface, err := net.InterfaceByName(name)
if err != nil { if err != nil {

View File

@ -1,33 +1,29 @@
// +build linux // +build linux
package fs package libcontainer
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"syscall" "syscall"
"github.com/docker/libcontainer/cgroups"
) )
// NotifyOnOOM sends signals on the returned channel when the cgroup reaches const oomCgroupName = "memory"
// its memory limit. The channel is closed when the cgroup is removed.
func NotifyOnOOM(c *cgroups.Cgroup) (<-chan struct{}, error) { // NotifyOnOOM returns channel on which you can expect event about OOM,
d, err := getCgroupData(c, 0) // 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 { if err != nil {
return nil, err 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) fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
if syserr != 0 { if syserr != 0 {
return nil, syserr return nil, syserr
@ -35,48 +31,32 @@ func notifyOnOOM(d *data) (<-chan struct{}, error) {
eventfd := os.NewFile(fd, "eventfd") eventfd := os.NewFile(fd, "eventfd")
oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control")) eventControlPath := filepath.Join(dir, "cgroup.event_control")
if err != nil { data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd())
eventfd.Close() if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil {
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 {
eventfd.Close() eventfd.Close()
oomControl.Close() oomControl.Close()
return nil, err return nil, err
} }
ch := make(chan struct{}) ch := make(chan struct{})
go func() { go func() {
defer func() { defer func() {
close(ch) close(ch)
eventfd.Close() eventfd.Close()
oomControl.Close() oomControl.Close()
}() }()
buf := make([]byte, 8) buf := make([]byte, 8)
for { for {
if _, err := eventfd.Read(buf); err != nil { if _, err := eventfd.Read(buf); err != nil {
return return
} }
// When a cgroup is destroyed, an event is sent to eventfd. // When a cgroup is destroyed, an event is sent to eventfd.
// So if the control path is gone, return instead of notifying. // So if the control path is gone, return instead of notifying.
if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) { if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) {
return return
} }
ch <- struct{}{} ch <- struct{}{}
} }
}() }()
return ch, nil return ch, nil
} }

View File

@ -1,38 +1,48 @@
// +build linux // +build linux
package fs package libcontainer
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall" "syscall"
"testing" "testing"
"time" "time"
) )
func TestNotifyOnOOM(t *testing.T) { func TestNotifyOnOOM(t *testing.T) {
helper := NewCgroupTestUtil("memory", t) memoryPath, err := ioutil.TempDir("", "testnotifyoom-")
defer helper.cleanup() if err != nil {
t.Fatal(err)
helper.writeFileContents(map[string]string{ }
"memory.oom_control": "", oomPath := filepath.Join(memoryPath, "memory.oom_control")
"cgroup.event_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 var eventFd, oomControlFd int
st := &State{
ooms, err := notifyOnOOM(helper.CgroupData) CgroupPaths: map[string]string{
"memory": memoryPath,
},
}
ooms, err := NotifyOnOOM(st)
if err != nil { if err != nil {
t.Fatal("expected no error, got:", err) t.Fatal("expected no error, got:", err)
} }
memoryPath, _ := helper.CgroupData.path("memory") data, err := ioutil.ReadFile(eventPath)
data, err := readFile(memoryPath, "cgroup.event_control")
if err != nil { if err != nil {
t.Fatal("couldn't read event control file:", err) 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) 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 // simulate what happens when a cgroup is destroyed by cleaning up and then
// writing to the eventfd. // writing to the eventfd.
helper.cleanup() if err := os.RemoveAll(memoryPath); err != nil {
t.Fatal(err)
}
if _, err := syscall.Write(efd, buf); err != nil { if _, err := syscall.Write(efd, buf); err != nil {
t.Fatal("unable to write to eventfd:", err) t.Fatal("unable to write to eventfd:", err)
} }

View File

@ -177,11 +177,11 @@
], ],
"hostname": "koye", "hostname": "koye",
"namespaces": [ "namespaces": [
{"name":"NEWIPC"}, {"type":"NEWIPC"},
{"name": "NEWNET"}, {"type": "NEWNET"},
{"name": "NEWNS"}, {"type": "NEWNS"},
{"name": "NEWPID"}, {"type": "NEWPID"},
{"name": "NEWUTS"} {"type": "NEWUTS"}
], ],
"networks": [ "networks": [
{ {

View File

@ -176,11 +176,11 @@
], ],
"hostname": "koye", "hostname": "koye",
"namespaces": [ "namespaces": [
{"name": "NEWIPC"}, {"type": "NEWIPC"},
{"name": "NEWNET"}, {"type": "NEWNET"},
{"name": "NEWNS"}, {"type": "NEWNS"},
{"name": "NEWPID"}, {"type": "NEWPID"},
{"name": "NEWUTS"} {"type": "NEWUTS"}
], ],
"networks": [ "networks": [
{ {

View File

@ -182,11 +182,11 @@
], ],
"hostname": "koye", "hostname": "koye",
"namespaces": [ "namespaces": [
{"name": "NEWIPC"}, {"type": "NEWIPC"},
{"name": "NEWNET"}, {"type": "NEWNET"},
{"name": "NEWNS"}, {"type": "NEWNS"},
{"name": "NEWPID"}, {"type": "NEWPID"},
{"name": "NEWUTS"} {"type": "NEWUTS"}
], ],
"networks": [ "networks": [
{ {

View File

@ -176,11 +176,11 @@
], ],
"hostname": "koye", "hostname": "koye",
"namespaces": [ "namespaces": [
{"name": "NEWIPC"}, {"type": "NEWIPC"},
{"name": "NEWNET"}, {"type": "NEWNET"},
{"name": "NEWNS"}, {"type": "NEWNS"},
{"name": "NEWPID"}, {"type": "NEWPID"},
{"name": "NEWUTS"} {"type": "NEWUTS"}
], ],
"networks": [ "networks": [
{ {

View File

@ -178,11 +178,11 @@
], ],
"hostname": "koye", "hostname": "koye",
"namespaces": [ "namespaces": [
{"name": "NEWIPC"}, {"type": "NEWIPC"},
{"name": "NEWNET"}, {"type": "NEWNET"},
{"name": "NEWNS"}, {"type": "NEWNS"},
{"name": "NEWPID"}, {"type": "NEWPID"},
{"name": "NEWUTS"} {"type": "NEWUTS"}
], ],
"networks": [ "networks": [
{ {

View File

@ -1 +1,2 @@
Tianon Gravi <admwiggin@gmail.com> (@tianon) 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. // Unix-specific path to the passwd and group formatted files.
const ( const (
unixPasswdFile = "/etc/passwd" unixPasswdPath = "/etc/passwd"
unixGroupFile = "/etc/group" unixGroupPath = "/etc/group"
) )
func GetPasswdFile() (string, error) { func GetPasswdPath() (string, error) {
return unixPasswdFile, nil return unixPasswdPath, nil
} }
func GetPasswd() (io.ReadCloser, error) { func GetPasswd() (io.ReadCloser, error) {
return os.Open(unixPasswdFile) return os.Open(unixPasswdPath)
} }
func GetGroupFile() (string, error) { func GetGroupPath() (string, error) {
return unixGroupFile, nil return unixGroupPath, nil
} }
func GetGroup() (io.ReadCloser, error) { func GetGroup() (io.ReadCloser, error) {
return os.Open(unixGroupFile) return os.Open(unixGroupPath)
} }

View File

@ -4,7 +4,7 @@ package user
import "io" import "io"
func GetPasswdFile() (string, error) { func GetPasswdPath() (string, error) {
return "", ErrUnsupported return "", ErrUnsupported
} }
@ -12,7 +12,7 @@ func GetPasswd() (io.ReadCloser, error) {
return nil, ErrUnsupported return nil, ErrUnsupported
} }
func GetGroupFile() (string, error) { func GetGroupPath() (string, error) {
return "", ErrUnsupported return "", ErrUnsupported
} }

View File

@ -197,11 +197,11 @@ type ExecUser struct {
Home string 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 // 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 // files cannot be opened for any reason, the error is ignored and a nil
// io.Reader is passed instead. // 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) passwd, err := os.Open(passwdPath)
if err != nil { if err != nil {
passwd = nil passwd = nil

View File

@ -161,7 +161,7 @@ func (self *dockerContainerHandler) readLibcontainerConfig() (*libcontainer.Conf
config = oldConfig.Config config = oldConfig.Config
for ns := range oldConfig.OldNamespaces { for ns := range oldConfig.OldNamespaces {
config.Namespaces = append(config.Namespaces, libcontainer.Namespace{ config.Namespaces = append(config.Namespaces, libcontainer.Namespace{
Name: ns, Type: libcontainer.NamespaceType(ns),
}) })
} }
} }