mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Update libcontainer to 1597c68f7b941fd97881155d7f077852e2914e7b
This commit contains changes for docker: * user.GetGroupFile to user.GetGroupPath docker/libcontainer#301 * Add systemd support for OOM docker/libcontainer#307 * Support for custom namespaces docker/libcontainer#279, docker/libcontainer#312 * Fixes #9699 docker/libcontainer#308 Signed-off-by: Alexander Morozov <lk4d4@docker.com>
This commit is contained in:
parent
043e33c2c1
commit
50905a6d6c
43 changed files with 517 additions and 362 deletions
|
@ -1399,7 +1399,7 @@ func serveFd(addr string, job *engine.Job) error {
|
|||
}
|
||||
|
||||
func lookupGidByName(nameOrGid string) (int, error) {
|
||||
groupFile, err := user.GetGroupFile()
|
||||
groupFile, err := user.GetGroupPath()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
|
|||
|
||||
func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Command) error {
|
||||
if c.Network.HostNetworking {
|
||||
container.Namespaces["NEWNET"] = false
|
||||
container.Namespaces.Remove(libcontainer.NEWNET)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -119,10 +119,7 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com
|
|||
cmd := active.cmd
|
||||
|
||||
nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net")
|
||||
container.Networks = append(container.Networks, &libcontainer.Network{
|
||||
Type: "netns",
|
||||
NsPath: nspath,
|
||||
})
|
||||
container.Namespaces.Add(libcontainer.NEWNET, nspath)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -130,7 +127,7 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com
|
|||
|
||||
func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command) error {
|
||||
if c.Ipc.HostIpc {
|
||||
container.Namespaces["NEWIPC"] = false
|
||||
container.Namespaces.Remove(libcontainer.NEWIPC)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -144,7 +141,7 @@ func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command
|
|||
}
|
||||
cmd := active.cmd
|
||||
|
||||
container.IpcNsPath = filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "ipc")
|
||||
container.Namespaces.Add(libcontainer.NEWIPC, filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "ipc"))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -61,10 +61,6 @@ func NewDriver(root, initPath string) (*driver, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (d *driver) notifyOnOOM(config *libcontainer.Config) (<-chan struct{}, error) {
|
||||
return fs.NotifyOnOOM(config.Cgroups)
|
||||
}
|
||||
|
||||
type execOutput struct {
|
||||
exitCode int
|
||||
err error
|
||||
|
@ -152,11 +148,16 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||
}
|
||||
|
||||
oomKill := false
|
||||
oomKillNotification, err := d.notifyOnOOM(container)
|
||||
state, err := libcontainer.GetState(filepath.Join(d.root, c.ID))
|
||||
if err == nil {
|
||||
_, oomKill = <-oomKillNotification
|
||||
oomKillNotification, err := libcontainer.NotifyOnOOM(state)
|
||||
if err == nil {
|
||||
_, oomKill = <-oomKillNotification
|
||||
} else {
|
||||
log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err)
|
||||
log.Warnf("Failed to get container state, oom notify will not work: %s", err)
|
||||
}
|
||||
// wait for the container to exit.
|
||||
execOutput := <-execOutputChan
|
||||
|
|
|
@ -25,12 +25,12 @@ func New() *libcontainer.Config {
|
|||
"KILL",
|
||||
"AUDIT_WRITE",
|
||||
},
|
||||
Namespaces: map[string]bool{
|
||||
"NEWNS": true,
|
||||
"NEWUTS": true,
|
||||
"NEWIPC": true,
|
||||
"NEWPID": true,
|
||||
"NEWNET": true,
|
||||
Namespaces: libcontainer.Namespaces{
|
||||
{Type: "NEWNS"},
|
||||
{Type: "NEWUTS"},
|
||||
{Type: "NEWIPC"},
|
||||
{Type: "NEWPID"},
|
||||
{Type: "NEWNET"},
|
||||
},
|
||||
Cgroups: &cgroups.Cgroup{
|
||||
Parent: "docker",
|
||||
|
|
|
@ -66,7 +66,7 @@ if [ "$1" = '--go' ]; then
|
|||
mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
|
||||
fi
|
||||
|
||||
clone git github.com/docker/libcontainer 53eca435e63db58b06cf796d3a9326db5fd42253
|
||||
clone git github.com/docker/libcontainer 1597c68f7b941fd97881155d7f077852e2914e7b
|
||||
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
|
||||
rm -rf src/github.com/docker/libcontainer/vendor
|
||||
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -50,6 +50,7 @@ type Cgroup struct {
|
|||
CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period.
|
||||
CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default.
|
||||
CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use
|
||||
CpusetMems string `json:"cpuset_mems,omitempty"` // MEM to use
|
||||
Freezer FreezerState `json:"freezer,omitempty"` // set the freeze value for the process
|
||||
Slice string `json:"slice,omitempty"` // Parent slice to use for systemd
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func (s *CpusetGroup) Set(d *data) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.SetDir(dir, d.c.CpusetCpus, d.pid)
|
||||
return s.SetDir(dir, d.c.CpusetCpus, d.c.CpusetMems, d.pid)
|
||||
}
|
||||
|
||||
func (s *CpusetGroup) Remove(d *data) error {
|
||||
|
@ -29,7 +29,7 @@ func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *CpusetGroup) SetDir(dir, value string, pid int) error {
|
||||
func (s *CpusetGroup) SetDir(dir, cpus string, mems string, pid int) error {
|
||||
if err := s.ensureParent(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -40,10 +40,15 @@ func (s *CpusetGroup) SetDir(dir, value string, pid int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// If we don't use --cpuset, the default cpuset.cpus is set in
|
||||
// s.ensureParent, otherwise, use the value we set
|
||||
if value != "" {
|
||||
if err := writeFile(dir, "cpuset.cpus", value); err != nil {
|
||||
// If we don't use --cpuset-xxx, the default value inherit from parent cgroup
|
||||
// is set in s.ensureParent, otherwise, use the value we set
|
||||
if cpus != "" {
|
||||
if err := writeFile(dir, "cpuset.cpus", cpus); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if mems != "" {
|
||||
if err := writeFile(dir, "cpuset.mems", mems); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -313,5 +313,5 @@ func joinCpuset(c *cgroups.Cgroup, pid int) error {
|
|||
|
||||
s := &fs.CpusetGroup{}
|
||||
|
||||
return s.SetDir(path, c.CpusetCpus, pid)
|
||||
return s.SetDir(path, c.CpusetCpus, c.CpusetMems, pid)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
)
|
||||
|
@ -193,13 +194,30 @@ func EnterPid(cgroupPaths map[string]string, pid int) error {
|
|||
}
|
||||
|
||||
// RemovePaths iterates over the provided paths removing them.
|
||||
// If an error is encountered the removal proceeds and the first error is
|
||||
// returned to ensure a partial removal is not possible.
|
||||
// We trying to remove all paths five times with increasing delay between tries.
|
||||
// If after all there are not removed cgroups - appropriate error will be
|
||||
// returned.
|
||||
func RemovePaths(paths map[string]string) (err error) {
|
||||
for _, path := range paths {
|
||||
if rerr := os.RemoveAll(path); err == nil {
|
||||
err = rerr
|
||||
delay := 10 * time.Millisecond
|
||||
for i := 0; i < 5; i++ {
|
||||
if i != 0 {
|
||||
time.Sleep(delay)
|
||||
delay *= 2
|
||||
}
|
||||
for s, p := range paths {
|
||||
os.RemoveAll(p)
|
||||
// TODO: here probably should be logging
|
||||
_, err := os.Stat(p)
|
||||
// We need this strange way of checking cgroups existence because
|
||||
// RemoveAll almost always returns error, even on already removed
|
||||
// cgroups
|
||||
if os.IsNotExist(err) {
|
||||
delete(paths, s)
|
||||
}
|
||||
}
|
||||
if len(paths) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
return fmt.Errorf("Failed to remove paths: %s", paths)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,57 @@ type MountConfig mount.MountConfig
|
|||
|
||||
type Network network.Network
|
||||
|
||||
type NamespaceType string
|
||||
|
||||
const (
|
||||
NEWNET NamespaceType = "NEWNET"
|
||||
NEWPID NamespaceType = "NEWPID"
|
||||
NEWNS NamespaceType = "NEWNS"
|
||||
NEWUTS NamespaceType = "NEWUTS"
|
||||
NEWIPC NamespaceType = "NEWIPC"
|
||||
NEWUSER NamespaceType = "NEWUSER"
|
||||
)
|
||||
|
||||
// Namespace defines configuration for each namespace. It specifies an
|
||||
// alternate path that is able to be joined via setns.
|
||||
type Namespace struct {
|
||||
Type NamespaceType `json:"type"`
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
type Namespaces []Namespace
|
||||
|
||||
func (n Namespaces) Remove(t NamespaceType) bool {
|
||||
i := n.index(t)
|
||||
if i == -1 {
|
||||
return false
|
||||
}
|
||||
n = append(n[:i], n[i+1:]...)
|
||||
return true
|
||||
}
|
||||
|
||||
func (n Namespaces) Add(t NamespaceType, path string) {
|
||||
i := n.index(t)
|
||||
if i == -1 {
|
||||
n = append(n, Namespace{Type: t, Path: path})
|
||||
return
|
||||
}
|
||||
n[i].Path = path
|
||||
}
|
||||
|
||||
func (n Namespaces) index(t NamespaceType) int {
|
||||
for i, ns := range n {
|
||||
if ns.Type == t {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (n Namespaces) Contains(t NamespaceType) bool {
|
||||
return n.index(t) != -1
|
||||
}
|
||||
|
||||
// Config defines configuration options for executing a process inside a contained environment.
|
||||
type Config struct {
|
||||
// Mount specific options.
|
||||
|
@ -38,7 +89,7 @@ type Config struct {
|
|||
|
||||
// Namespaces specifies the container's namespaces that it should setup when cloning the init process
|
||||
// If a namespace is not provided that namespace is shared from the container's parent process
|
||||
Namespaces map[string]bool `json:"namespaces,omitempty"`
|
||||
Namespaces Namespaces `json:"namespaces,omitempty"`
|
||||
|
||||
// Capabilities specify the capabilities to keep when executing the process inside the container
|
||||
// All capbilities not specified will be dropped from the processes capability mask
|
||||
|
@ -47,9 +98,6 @@ type Config struct {
|
|||
// Networks specifies the container's network setup to be created
|
||||
Networks []*Network `json:"networks,omitempty"`
|
||||
|
||||
// Ipc specifies the container's ipc setup to be created
|
||||
IpcNsPath string `json:"ipc,omitempty"`
|
||||
|
||||
// Routes can be specified to create entries in the route table as the container is started
|
||||
Routes []*Route `json:"routes,omitempty"`
|
||||
|
||||
|
|
|
@ -64,12 +64,12 @@ func TestConfigJsonFormat(t *testing.T) {
|
|||
t.Fail()
|
||||
}
|
||||
|
||||
if !container.Namespaces["NEWNET"] {
|
||||
if !container.Namespaces.Contains(NEWNET) {
|
||||
t.Log("namespaces should contain NEWNET")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if container.Namespaces["NEWUSER"] {
|
||||
if container.Namespaces.Contains(NEWUSER) {
|
||||
t.Log("namespaces should not contain NEWUSER")
|
||||
t.Fail()
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
)
|
||||
|
||||
func TestExecPS(t *testing.T) {
|
||||
|
@ -55,7 +57,6 @@ func TestIPCPrivate(t *testing.T) {
|
|||
}
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces["NEWIPC"] = true
|
||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -87,7 +88,7 @@ func TestIPCHost(t *testing.T) {
|
|||
}
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces["NEWIPC"] = false
|
||||
config.Namespaces.Remove(libcontainer.NEWIPC)
|
||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -119,8 +120,7 @@ func TestIPCJoinPath(t *testing.T) {
|
|||
}
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces["NEWIPC"] = false
|
||||
config.IpcNsPath = "/proc/1/ns/ipc"
|
||||
config.Namespaces.Add(libcontainer.NEWIPC, "/proc/1/ns/ipc")
|
||||
|
||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||
if err != nil {
|
||||
|
@ -148,8 +148,7 @@ func TestIPCBadPath(t *testing.T) {
|
|||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces["NEWIPC"] = false
|
||||
config.IpcNsPath = "/proc/1/ns/ipcc"
|
||||
config.Namespaces.Add(libcontainer.NEWIPC, "/proc/1/ns/ipcc")
|
||||
|
||||
_, _, err = runContainer(config, "", "true")
|
||||
if err == nil {
|
||||
|
|
140
vendor/src/github.com/docker/libcontainer/integration/execin_test.go
vendored
Normal file
140
vendor/src/github.com/docker/libcontainer/integration/execin_test.go
vendored
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -32,12 +32,12 @@ func newTemplateConfig(rootfs string) *libcontainer.Config {
|
|||
"KILL",
|
||||
"AUDIT_WRITE",
|
||||
},
|
||||
Namespaces: map[string]bool{
|
||||
"NEWNS": true,
|
||||
"NEWUTS": true,
|
||||
"NEWIPC": true,
|
||||
"NEWPID": true,
|
||||
"NEWNET": true,
|
||||
Namespaces: libcontainer.Namespaces{
|
||||
{Type: libcontainer.NEWNS},
|
||||
{Type: libcontainer.NEWUTS},
|
||||
{Type: libcontainer.NEWIPC},
|
||||
{Type: libcontainer.NEWPID},
|
||||
{Type: libcontainer.NEWNET},
|
||||
},
|
||||
Cgroups: &cgroups.Cgroup{
|
||||
Parent: "integration",
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
package ipc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer/system"
|
||||
)
|
||||
|
||||
// Join the IPC Namespace of specified ipc path if it exists.
|
||||
// If the path does not exist then you are not joining a container.
|
||||
func Initialize(nsPath string) error {
|
||||
if nsPath == "" {
|
||||
return nil
|
||||
}
|
||||
f, err := os.OpenFile(nsPath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed get IPC namespace fd: %v", err)
|
||||
}
|
||||
|
||||
err = system.Setns(f.Fd(), syscall.CLONE_NEWIPC)
|
||||
f.Close()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setns current IPC namespace: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/apparmor"
|
||||
"github.com/docker/libcontainer/console"
|
||||
"github.com/docker/libcontainer/ipc"
|
||||
"github.com/docker/libcontainer/label"
|
||||
"github.com/docker/libcontainer/mount"
|
||||
"github.com/docker/libcontainer/netlink"
|
||||
|
@ -65,7 +64,10 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip
|
|||
if err := json.NewDecoder(pipe).Decode(&networkState); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// join any namespaces via a path to the namespace fd if provided
|
||||
if err := joinExistingNamespaces(container.Namespaces); err != nil {
|
||||
return err
|
||||
}
|
||||
if consolePath != "" {
|
||||
if err := console.OpenAndDup(consolePath); err != nil {
|
||||
return err
|
||||
|
@ -79,9 +81,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip
|
|||
return fmt.Errorf("setctty %s", err)
|
||||
}
|
||||
}
|
||||
if err := ipc.Initialize(container.IpcNsPath); err != nil {
|
||||
return fmt.Errorf("setup IPC %s", err)
|
||||
}
|
||||
|
||||
if err := setupNetwork(container, networkState); err != nil {
|
||||
return fmt.Errorf("setup networking %s", err)
|
||||
}
|
||||
|
@ -178,17 +178,17 @@ func SetupUser(u string) error {
|
|||
Home: "/",
|
||||
}
|
||||
|
||||
passwdFile, err := user.GetPasswdFile()
|
||||
passwdPath, err := user.GetPasswdPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupFile, err := user.GetGroupFile()
|
||||
groupPath, err := user.GetGroupPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
execUser, err := user.GetExecUserFile(u, &defaultExecUser, passwdFile, groupFile)
|
||||
execUser, err := user.GetExecUserPath(u, &defaultExecUser, passwdPath, groupPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get supplementary groups %s", err)
|
||||
}
|
||||
|
@ -308,3 +308,22 @@ func LoadContainerEnvironment(container *libcontainer.Config) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// joinExistingNamespaces gets all the namespace paths specified for the container and
|
||||
// does a setns on the namespace fd so that the current process joins the namespace.
|
||||
func joinExistingNamespaces(namespaces []libcontainer.Namespace) error {
|
||||
for _, ns := range namespaces {
|
||||
if ns.Path != "" {
|
||||
f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Type]))
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#ifndef PR_SET_CHILD_SUBREAPER
|
||||
#define PR_SET_CHILD_SUBREAPER 36
|
||||
#endif
|
||||
|
||||
static const kBufSize = 256;
|
||||
static const char *kNsEnter = "nsenter";
|
||||
|
||||
|
@ -32,8 +36,8 @@ void get_args(int *argc, char ***argv)
|
|||
contents_size += kBufSize;
|
||||
contents = (char *)realloc(contents, contents_size);
|
||||
bytes_read =
|
||||
read(fd, contents + contents_offset,
|
||||
contents_size - contents_offset);
|
||||
read(fd, contents + contents_offset,
|
||||
contents_size - contents_offset);
|
||||
contents_offset += bytes_read;
|
||||
}
|
||||
while (bytes_read > 0);
|
||||
|
@ -90,16 +94,17 @@ void nsenter()
|
|||
}
|
||||
|
||||
if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) == -1) {
|
||||
fprintf(stderr, "nsenter: failed to set child subreaper: %s", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
fprintf(stderr, "nsenter: failed to set child subreaper: %s",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static const struct option longopts[] = {
|
||||
{"nspid", required_argument, NULL, 'n'},
|
||||
{"console", required_argument, NULL, 't'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
|
||||
pid_t init_pid = -1;
|
||||
char *init_pid_str = NULL;
|
||||
char *console = NULL;
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package namespaces
|
||||
|
||||
import "errors"
|
||||
|
||||
type (
|
||||
Namespace struct {
|
||||
Key string `json:"key,omitempty"`
|
||||
Value int `json:"value,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
}
|
||||
Namespaces []*Namespace
|
||||
)
|
||||
|
||||
// namespaceList is used to convert the libcontainer types
|
||||
// into the names of the files located in /proc/<pid>/ns/* for
|
||||
// each namespace
|
||||
var (
|
||||
namespaceList = Namespaces{}
|
||||
ErrUnkownNamespace = errors.New("Unknown namespace")
|
||||
ErrUnsupported = errors.New("Unsupported method")
|
||||
)
|
||||
|
||||
func (ns *Namespace) String() string {
|
||||
return ns.Key
|
||||
}
|
||||
|
||||
func GetNamespace(key string) *Namespace {
|
||||
for _, ns := range namespaceList {
|
||||
if ns.Key == key {
|
||||
cpy := *ns
|
||||
return &cpy
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Contains returns true if the specified Namespace is
|
||||
// in the slice
|
||||
func (n Namespaces) Contains(ns string) bool {
|
||||
return n.Get(ns) != nil
|
||||
}
|
||||
|
||||
func (n Namespaces) Get(ns string) *Namespace {
|
||||
for _, nsp := range n {
|
||||
if nsp != nil && nsp.Key == ns {
|
||||
return nsp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package namespaces
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
namespaceList = Namespaces{
|
||||
{Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt"},
|
||||
{Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts"},
|
||||
{Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc"},
|
||||
{Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user"},
|
||||
{Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid"},
|
||||
{Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net"},
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package namespaces
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNamespacesContains(t *testing.T) {
|
||||
ns := Namespaces{
|
||||
GetNamespace("NEWPID"),
|
||||
GetNamespace("NEWNS"),
|
||||
GetNamespace("NEWUTS"),
|
||||
}
|
||||
|
||||
if ns.Contains("NEWNET") {
|
||||
t.Fatal("namespaces should not contain NEWNET")
|
||||
}
|
||||
|
||||
if !ns.Contains("NEWPID") {
|
||||
t.Fatal("namespaces should contain NEWPID but does not")
|
||||
}
|
||||
|
||||
withNil := Namespaces{
|
||||
GetNamespace("UNDEFINED"), // this element will be nil
|
||||
GetNamespace("NEWPID"),
|
||||
}
|
||||
|
||||
if !withNil.Contains("NEWPID") {
|
||||
t.Fatal("namespaces should contain NEWPID but does not")
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ package namespaces
|
|||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
)
|
||||
|
||||
type initError struct {
|
||||
|
@ -15,6 +17,15 @@ func (i initError) Error() string {
|
|||
return i.Message
|
||||
}
|
||||
|
||||
var namespaceInfo = map[libcontainer.NamespaceType]int{
|
||||
libcontainer.NEWNET: syscall.CLONE_NEWNET,
|
||||
libcontainer.NEWNS: syscall.CLONE_NEWNS,
|
||||
libcontainer.NEWUSER: syscall.CLONE_NEWUSER,
|
||||
libcontainer.NEWIPC: syscall.CLONE_NEWIPC,
|
||||
libcontainer.NEWUTS: syscall.CLONE_NEWUTS,
|
||||
libcontainer.NEWPID: syscall.CLONE_NEWPID,
|
||||
}
|
||||
|
||||
// New returns a newly initialized Pipe for communication between processes
|
||||
func newInitPipe() (parent *os.File, child *os.File, err error) {
|
||||
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
|
||||
|
@ -26,13 +37,9 @@ func newInitPipe() (parent *os.File, child *os.File, err error) {
|
|||
|
||||
// GetNamespaceFlags parses the container's Namespaces options to set the correct
|
||||
// flags on clone, unshare, and setns
|
||||
func GetNamespaceFlags(namespaces map[string]bool) (flag int) {
|
||||
for key, enabled := range namespaces {
|
||||
if enabled {
|
||||
if ns := GetNamespace(key); ns != nil {
|
||||
flag |= ns.Value
|
||||
}
|
||||
}
|
||||
func GetNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) {
|
||||
for _, v := range namespaces {
|
||||
flag |= namespaceInfo[v.Type]
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer/system"
|
||||
)
|
||||
|
||||
// crosbymichael: could make a network strategy that instead of returning veth pair names it returns a pid to an existing network namespace
|
||||
type NetNS struct {
|
||||
}
|
||||
|
||||
func (v *NetNS) Create(n *Network, nspid int, networkState *NetworkState) error {
|
||||
networkState.NsPath = n.NsPath
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *NetNS) Initialize(config *Network, networkState *NetworkState) error {
|
||||
if networkState.NsPath == "" {
|
||||
return fmt.Errorf("nspath does is not specified in NetworkState")
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(networkState.NsPath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed get network namespace fd: %v", err)
|
||||
}
|
||||
|
||||
if err := system.Setns(f.Fd(), syscall.CLONE_NEWNET); err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("failed to setns current network namespace: %v", err)
|
||||
}
|
||||
|
||||
f.Close()
|
||||
return nil
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -13,7 +13,6 @@ var (
|
|||
var strategies = map[string]NetworkStrategy{
|
||||
"veth": &Veth{},
|
||||
"loopback": &Loopback{},
|
||||
"netns": &NetNS{},
|
||||
}
|
||||
|
||||
// NetworkStrategy represents a specific network configuration for
|
||||
|
|
|
@ -8,9 +8,6 @@ type Network struct {
|
|||
// Type sets the networks type, commonly veth and loopback
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
// Path to network namespace
|
||||
NsPath string `json:"ns_path,omitempty"`
|
||||
|
||||
// The bridge to use.
|
||||
Bridge string `json:"bridge,omitempty"`
|
||||
|
||||
|
@ -50,6 +47,4 @@ type NetworkState struct {
|
|||
VethHost string `json:"veth_host,omitempty"`
|
||||
// The name of the veth interface created inside the container for the child.
|
||||
VethChild string `json:"veth_child,omitempty"`
|
||||
// Net namespace path.
|
||||
NsPath string `json:"ns_path,omitempty"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -176,13 +176,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"type":"NEWIPC"},
|
||||
{"type": "NEWNET"},
|
||||
{"type": "NEWNS"},
|
||||
{"type": "NEWPID"},
|
||||
{"type": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
|
@ -175,13 +175,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"type": "NEWIPC"},
|
||||
{"type": "NEWNET"},
|
||||
{"type": "NEWNS"},
|
||||
{"type": "NEWPID"},
|
||||
{"type": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
|
@ -181,13 +181,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"type": "NEWIPC"},
|
||||
{"type": "NEWNET"},
|
||||
{"type": "NEWNS"},
|
||||
{"type": "NEWPID"},
|
||||
{"type": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
|
@ -175,13 +175,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"type": "NEWIPC"},
|
||||
{"type": "NEWNET"},
|
||||
{"type": "NEWNS"},
|
||||
{"type": "NEWPID"},
|
||||
{"type": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
|
@ -177,13 +177,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"type": "NEWIPC"},
|
||||
{"type": "NEWNET"},
|
||||
{"type": "NEWNS"},
|
||||
{"type": "NEWPID"},
|
||||
{"type": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue