From fa782f19f9408729033e066506dc04f089523629 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Tue, 5 May 2015 13:59:47 -0700 Subject: [PATCH 1/2] Update libcontainer godep Fixes #668 --- Godeps/Godeps.json | 4 +- .../github.com/docker/libcontainer/.gitignore | 1 + .../github.com/docker/libcontainer/Makefile | 2 + .../github.com/docker/libcontainer/README.md | 3 + .../docker/libcontainer/apparmor/apparmor.go | 6 +- .../docker/libcontainer/apparmor/gen.go | 8 +- .../docker/libcontainer/capabilities_linux.go | 1 + .../docker/libcontainer/cgroups/cgroups.go | 3 - .../libcontainer/cgroups/fs/apply_raw.go | 63 ++- .../docker/libcontainer/cgroups/fs/blkio.go | 26 + .../libcontainer/cgroups/fs/blkio_test.go | 124 +++++ .../docker/libcontainer/cgroups/fs/devices.go | 11 + .../libcontainer/cgroups/fs/devices_test.go | 38 +- .../docker/libcontainer/cgroups/fs/hugetlb.go | 29 ++ .../docker/libcontainer/cgroups/fs/memory.go | 1 + .../libcontainer/cgroups/fs/memory_test.go | 2 +- .../cgroups/fs/stats_util_test.go | 2 +- .../docker/libcontainer/cgroups/stats.go | 2 + .../cgroups/systemd/apply_nosystemd.go | 8 - .../cgroups/systemd/apply_systemd.go | 136 +++-- .../docker/libcontainer/configs/cgroup.go | 17 + .../docker/libcontainer/configs/config.go | 7 + .../docker/libcontainer/configs/mount.go | 13 + .../docker/libcontainer/configs/namespaces.go | 42 +- .../configs/namespaces_syscall.go | 31 ++ .../configs/namespaces_syscall_unsupported.go | 15 + .../docker/libcontainer/configs/network.go | 4 +- .../docker/libcontainer/console_linux.go | 2 +- .../docker/libcontainer/container.go | 10 +- .../docker/libcontainer/container_linux.go | 36 +- .../libcontainer/container_linux_test.go | 7 +- .../docker/libcontainer/devices/devices.go | 2 +- .../github.com/docker/libcontainer/factory.go | 12 +- .../docker/libcontainer/factory_linux.go | 7 +- .../docker/libcontainer/init_linux.go | 24 +- .../libcontainer/integration/exec_test.go | 480 ++++++++++++------ .../libcontainer/integration/execin_test.go | 310 +++++------ .../libcontainer/integration/init_test.go | 37 +- .../libcontainer/integration/utils_test.go | 24 +- .../libcontainer/label/label_selinux.go | 14 +- .../libcontainer/label/label_selinux_test.go | 28 + .../docker/libcontainer/nsenter/README.md | 27 +- .../libcontainer/nsenter/nsenter_test.go | 2 +- .../docker/libcontainer/nsenter/nsexec.c | 23 +- .../docker/libcontainer/nsinit/README.md | 45 ++ .../docker/libcontainer/nsinit/config.go | 7 + .../docker/libcontainer/nsinit/exec.go | 1 + .../docker/libcontainer/nsinit/init.go | 2 +- .../docker/libcontainer/nsinit/main.go | 2 +- .../docker/libcontainer/nsinit/oom.go | 3 +- .../docker/libcontainer/nsinit/pause.go | 3 +- .../docker/libcontainer/nsinit/utils.go | 12 +- .../github.com/docker/libcontainer/process.go | 7 +- .../docker/libcontainer/process_linux.go | 3 + .../docker/libcontainer/rootfs_linux.go | 80 ++- .../libcontainer/standard_init_linux.go | 17 +- .../docker/libcontainer/system/setns_linux.go | 4 +- .../libcontainer/system/syscall_linux_64.go | 2 +- .../docker/libcontainer/update-vendor.sh | 2 +- .../github.com/Sirupsen/logrus/CHANGELOG.md | 7 + .../src/github.com/Sirupsen/logrus/README.md | 102 ++-- .../Sirupsen/logrus/examples/hook/hook.go | 7 +- .../github.com/Sirupsen/logrus/formatter.go | 4 + .../logrus/formatters/logstash/logstash.go | 56 ++ .../formatters/logstash/logstash_test.go | 52 ++ .../logrus/hooks/airbrake/airbrake.go | 58 +-- .../logrus/hooks/airbrake/airbrake_test.go | 133 +++++ .../Sirupsen/logrus/hooks/bugsnag/bugsnag.go | 68 +++ .../logrus/hooks/bugsnag/bugsnag_test.go | 64 +++ .../Sirupsen/logrus/json_formatter.go | 24 +- .../src/github.com/Sirupsen/logrus/logger.go | 84 ++- .../Sirupsen/logrus/terminal_openbsd.go | 1 - .../Sirupsen/logrus/text_formatter.go | 12 +- .../Sirupsen/logrus/text_formatter_test.go | 26 +- .../src/github.com/Sirupsen/logrus/writer.go | 2 +- 75 files changed, 1879 insertions(+), 655 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces_syscall.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake_test.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 556ba1de..fb082c55 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -97,8 +97,8 @@ }, { "ImportPath": "github.com/docker/libcontainer", - "Comment": "v1.4.0-412-g4ea9039", - "Rev": "4ea9039ff269fc8675409df7f1a192c94eab2f52" + "Comment": "v1.4.0-501-ga1fe3f1", + "Rev": "a1fe3f1c7ad2e8eebe6d59e573f04d2b10961cf6" }, { "ImportPath": "github.com/fsouza/go-dockerclient", diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/.gitignore b/Godeps/_workspace/src/github.com/docker/libcontainer/.gitignore index 4c2914fc..bf6a664d 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/.gitignore +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/.gitignore @@ -1 +1,2 @@ +bundles nsinit/nsinit diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile b/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile index c2c9a98d..1a2e23e0 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile @@ -29,3 +29,5 @@ local: validate: hack/validate.sh +binary: all + docker run --rm --privileged -v $(CURDIR)/bundles:/go/bin dockercore/libcontainer make direct-install diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/README.md b/Godeps/_workspace/src/github.com/docker/libcontainer/README.md index 984f2c52..6257f9c7 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/README.md +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/README.md @@ -141,6 +141,9 @@ container.Resume() It is able to spawn new containers or join existing containers. A root filesystem must be provided for use along with a container configuration file. +To build `nsinit`, run `make binary`. It will save the binary into +`bundles/nsinit`. + To use `nsinit`, cd into a Linux rootfs and copy a `container.json` file into the directory with your specified configuration. Environment, networking, and different capabilities for the container are specified in this file. diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/apparmor/apparmor.go b/Godeps/_workspace/src/github.com/docker/libcontainer/apparmor/apparmor.go index 3be3294d..18cedf6a 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/apparmor/apparmor.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/apparmor/apparmor.go @@ -14,8 +14,10 @@ import ( func IsEnabled() bool { if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" { - buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") - return err == nil && len(buf) > 1 && buf[0] == 'Y' + if _, err = os.Stat("/sbin/apparmor_parser"); err == nil { + buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") + return err == nil && len(buf) > 1 && buf[0] == 'Y' + } } return false } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/apparmor/gen.go b/Godeps/_workspace/src/github.com/docker/libcontainer/apparmor/gen.go index 825e646d..4565f6df 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/apparmor/gen.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/apparmor/gen.go @@ -67,12 +67,12 @@ func generateProfile(out io.Writer) error { data := &data{ Name: "docker-default", } - if tuntablesExists() { + if tunablesExists() { data.Imports = append(data.Imports, "#include ") } else { data.Imports = append(data.Imports, "@{PROC}=/proc/") } - if abstrctionsEsists() { + if abstractionsExists() { data.InnerImports = append(data.InnerImports, "#include ") } if err := compiled.Execute(out, data); err != nil { @@ -82,13 +82,13 @@ func generateProfile(out io.Writer) error { } // check if the tunables/global exist -func tuntablesExists() bool { +func tunablesExists() bool { _, err := os.Stat("/etc/apparmor.d/tunables/global") return err == nil } // check if abstractions/base exist -func abstrctionsEsists() bool { +func abstractionsExists() bool { _, err := os.Stat("/etc/apparmor.d/abstractions/base") return err == nil } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/capabilities_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/capabilities_linux.go index b1c5c176..6b8b465c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/capabilities_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/capabilities_linux.go @@ -49,6 +49,7 @@ var capabilityList = map[string]capability.Cap{ "SETFCAP": capability.CAP_SETFCAP, "WAKE_ALARM": capability.CAP_WAKE_ALARM, "BLOCK_SUSPEND": capability.CAP_BLOCK_SUSPEND, + "AUDIT_READ": capability.CAP_AUDIT_READ, } func newCapWhitelist(caps []string) (*whitelist, error) { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgroups.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgroups.go index d0da88bf..df7bfb3c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgroups.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgroups.go @@ -34,9 +34,6 @@ type Manager interface { // Set the cgroup as configured. Set(container *configs.Config) error - - // Enters the specified process into these cgroups. - EnterProcess(pid int) error } type NotFoundError struct { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go index 6bc8850f..fa6478b5 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go @@ -1,6 +1,8 @@ package fs import ( + "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -19,6 +21,7 @@ var ( "cpuset": &CpusetGroup{}, "cpuacct": &CpuacctGroup{}, "blkio": &BlkioGroup{}, + "hugetlb": &HugetlbGroup{}, "perf_event": &PerfEventGroup{}, "freezer": &FreezerGroup{}, } @@ -75,10 +78,13 @@ type data struct { } func (m *Manager) Apply(pid int) error { + if m.Cgroups == nil { return nil } + var c = m.Cgroups + d, err := getCgroupData(m.Cgroups, pid) if err != nil { return err @@ -108,6 +114,12 @@ func (m *Manager) Apply(pid int) error { } m.Paths = paths + if paths["cpu"] != "" { + if err := CheckCpushares(paths["cpu"], c.CpuShares); err != nil { + return err + } + } + return nil } @@ -119,19 +131,6 @@ func (m *Manager) GetPaths() map[string]string { return m.Paths } -// Symmetrical public function to update device based cgroups. Also available -// in the systemd implementation. -func ApplyDevices(c *configs.Cgroup, pid int) error { - d, err := getCgroupData(c, pid) - if err != nil { - return err - } - - devices := subsystems["devices"] - - return devices.Apply(d) -} - func (m *Manager) GetStats() (*cgroups.Stats, error) { stats := cgroups.NewStats() for name, path := range m.Paths { @@ -161,20 +160,6 @@ func (m *Manager) Set(container *configs.Config) error { return nil } -func (m *Manager) EnterProcess(pid int) error { - for name, path := range m.Paths { - _, ok := subsystems[name] - if !ok || !cgroups.PathExists(path) { - continue - } - if err := writeFile(path, CgroupProcesses, strconv.Itoa(pid)); err != nil { - return err - } - } - - return nil -} - // Freeze toggles the container's freezer cgroup depending on the state // provided func (m *Manager) Freeze(state configs.FreezerState) error { @@ -294,3 +279,27 @@ func removePath(p string, err error) error { } return nil } + +func CheckCpushares(path string, c int64) error { + var cpuShares int64 + + fd, err := os.Open(filepath.Join(path, "cpu.shares")) + if err != nil { + return err + } + defer fd.Close() + + _, err = fmt.Fscanf(fd, "%d", &cpuShares) + if err != nil && err != io.EOF { + return err + } + if c != 0 { + if c > cpuShares { + return fmt.Errorf("The maximum allowed cpu-shares is %d", cpuShares) + } else if c < cpuShares { + return fmt.Errorf("The minimum allowed cpu-shares is %d", cpuShares) + } + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/blkio.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/blkio.go index 8e132643..06f0a3b2 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/blkio.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/blkio.go @@ -35,6 +35,32 @@ func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error { } } + if cgroup.BlkioWeightDevice != "" { + if err := writeFile(path, "blkio.weight_device", cgroup.BlkioWeightDevice); err != nil { + return err + } + } + if cgroup.BlkioThrottleReadBpsDevice != "" { + if err := writeFile(path, "blkio.throttle.read_bps_device", cgroup.BlkioThrottleReadBpsDevice); err != nil { + return err + } + } + if cgroup.BlkioThrottleWriteBpsDevice != "" { + if err := writeFile(path, "blkio.throttle.write_bps_device", cgroup.BlkioThrottleWriteBpsDevice); err != nil { + return err + } + } + if cgroup.BlkioThrottleReadIOpsDevice != "" { + if err := writeFile(path, "blkio.throttle.read_iops_device", cgroup.BlkioThrottleReadIOpsDevice); err != nil { + return err + } + } + if cgroup.BlkioThrottleWriteIOpsDevice != "" { + if err := writeFile(path, "blkio.throttle.write_iops_device", cgroup.BlkioThrottleWriteIOpsDevice); err != nil { + return err + } + } + return nil } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/blkio_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/blkio_test.go index 9ef93fcf..9d0915da 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/blkio_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/blkio_test.go @@ -67,6 +67,8 @@ Total 22061056` 252:0 Async 164 252:0 Total 164 Total 328` + throttleBefore = `8:0 1024` + throttleAfter = `8:0 2048` ) func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) { @@ -102,6 +104,35 @@ func TestBlkioSetWeight(t *testing.T) { } } +func TestBlkioSetWeightDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + const ( + weightDeviceBefore = "8:0 400" + weightDeviceAfter = "8:0 500" + ) + + helper.writeFileContents(map[string]string{ + "blkio.weight_device": weightDeviceBefore, + }) + + helper.CgroupData.c.BlkioWeightDevice = weightDeviceAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.weight_device") + if err != nil { + t.Fatalf("Failed to parse blkio.weight_device - %s", err) + } + + if value != weightDeviceAfter { + t.Fatal("Got the wrong value, set blkio.weight_device failed.") + } +} + func TestBlkioStats(t *testing.T) { helper := NewCgroupTestUtil("blkio", t) defer helper.cleanup() @@ -442,3 +473,96 @@ func TestNonCFQBlkioStats(t *testing.T) { expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats) } + +func TestBlkioSetThrottleReadBpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.read_bps_device": throttleBefore, + }) + + helper.CgroupData.c.BlkioThrottleReadBpsDevice = throttleAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_bps_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.read_bps_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.read_bps_device failed.") + } +} +func TestBlkioSetThrottleWriteBpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.write_bps_device": throttleBefore, + }) + + helper.CgroupData.c.BlkioThrottleWriteBpsDevice = throttleAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_bps_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.write_bps_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.write_bps_device failed.") + } +} +func TestBlkioSetThrottleReadIOpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.read_iops_device": throttleBefore, + }) + + helper.CgroupData.c.BlkioThrottleReadIOpsDevice = throttleAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_iops_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.read_iops_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.read_iops_device failed.") + } +} +func TestBlkioSetThrottleWriteIOpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.write_iops_device": throttleBefore, + }) + + helper.CgroupData.c.BlkioThrottleWriteIOpsDevice = throttleAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_iops_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.write_iops_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.write_iops_device failed.") + } +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/devices.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/devices.go index 16e00b1c..be588d67 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/devices.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/devices.go @@ -32,6 +32,17 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { return err } } + return nil + } + + if err := writeFile(path, "devices.allow", "a"); err != nil { + return err + } + + for _, dev := range cgroup.DeniedDevices { + if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil { + return err + } } return nil diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/devices_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/devices_test.go index 18bb1274..f950c1b9 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/devices_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/devices_test.go @@ -17,7 +17,18 @@ var ( FileMode: 0666, }, } - allowedList = "c 1:5 rwm" + allowedList = "c 1:5 rwm" + deniedDevices = []*configs.Device{ + { + Path: "/dev/null", + Type: 'c', + Major: 1, + Minor: 3, + Permissions: "rwm", + FileMode: 0666, + }, + } + deniedList = "c 1:3 rwm" ) func TestDevicesSetAllow(t *testing.T) { @@ -44,3 +55,28 @@ func TestDevicesSetAllow(t *testing.T) { t.Fatal("Got the wrong value, set devices.allow failed.") } } + +func TestDevicesSetDeny(t *testing.T) { + helper := NewCgroupTestUtil("devices", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "devices.allow": "a", + }) + + helper.CgroupData.c.AllowAllDevices = true + helper.CgroupData.c.DeniedDevices = deniedDevices + devices := &DevicesGroup{} + if err := devices.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "devices.deny") + if err != nil { + t.Fatalf("Failed to parse devices.deny - %s", err) + } + + if value != deniedList { + t.Fatal("Got the wrong value, set devices.deny failed.") + } +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go new file mode 100644 index 00000000..8defdd1b --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go @@ -0,0 +1,29 @@ +package fs + +import ( + "github.com/docker/libcontainer/cgroups" + "github.com/docker/libcontainer/configs" +) + +type HugetlbGroup struct { +} + +func (s *HugetlbGroup) Apply(d *data) error { + // we just want to join this group even though we don't set anything + if _, err := d.join("hugetlb"); err != nil && !cgroups.IsNotFound(err) { + return err + } + return nil +} + +func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error { + return nil +} + +func (s *HugetlbGroup) Remove(d *data) error { + return removePath(d.path("hugetlb")) +} + +func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error { + return nil +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go index b99f8168..d5dbaf65 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go @@ -95,6 +95,7 @@ func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error { return fmt.Errorf("failed to parse memory.usage_in_bytes - %v", err) } stats.MemoryStats.Usage = value + stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"] value, err = getCgroupParamUint(path, "memory.max_usage_in_bytes") if err != nil { return fmt.Errorf("failed to parse memory.max_usage_in_bytes - %v", err) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory_test.go index 1e939c4e..60edc67a 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory_test.go @@ -128,7 +128,7 @@ func TestMemoryStats(t *testing.T) { if err != nil { t.Fatal(err) } - expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}} + expectedStats := cgroups.MemoryStats{Usage: 2048, Cache: 512, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}} expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go index c55ba938..b94f60f9 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go @@ -2,9 +2,9 @@ package fs import ( "fmt" - "log" "testing" + log "github.com/Sirupsen/logrus" "github.com/docker/libcontainer/cgroups" ) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/stats.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/stats.go index dc5dbb3c..25c8f199 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/stats.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/stats.go @@ -33,6 +33,8 @@ type CpuStats struct { type MemoryStats struct { // current res_counter usage for memory Usage uint64 `json:"usage,omitempty"` + // memory used for cache + Cache uint64 `json:"cache,omitempty"` // maximum usage ever recorded. MaxUsage uint64 `json:"max_usage,omitempty"` // TODO(vishh): Export these as stronger types. diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_nosystemd.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_nosystemd.go index f10fa95c..9b605b3c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_nosystemd.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_nosystemd.go @@ -42,18 +42,10 @@ func (m *Manager) Set(container *configs.Config) error { return nil, fmt.Errorf("Systemd not supported") } -func (m *Manager) EnterProcess(pid int) error { - return nil, fmt.Errorf("Systemd not supported") -} - func (m *Manager) Freeze(state configs.FreezerState) error { return fmt.Errorf("Systemd not supported") } -func ApplyDevices(c *configs.Cgroup, pid int) error { - return fmt.Errorf("Systemd not supported") -} - func Freeze(c *configs.Cgroup, state configs.FreezerState) error { return fmt.Errorf("Systemd not supported") } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go index 9a750f23..2ba10cbb 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go @@ -3,7 +3,6 @@ package systemd import ( - "bytes" "fmt" "io/ioutil" "os" @@ -39,6 +38,7 @@ var subsystems = map[string]subsystem{ "cpuset": &fs.CpusetGroup{}, "cpuacct": &fs.CpuacctGroup{}, "blkio": &fs.BlkioGroup{}, + "hugetlb": &fs.HugetlbGroup{}, "perf_event": &fs.PerfEventGroup{}, "freezer": &fs.FreezerGroup{}, } @@ -217,6 +217,13 @@ func (m *Manager) Apply(pid int) error { return err } + // FIXME: Systemd does have `BlockIODeviceWeight` property, but we got problem + // using that (at least on systemd 208, see https://github.com/docker/libcontainer/pull/354), + // so use fs work around for now. + if err := joinBlkio(c, pid); err != nil { + return err + } + paths := make(map[string]string) for sysname := range subsystems { subsystemPath, err := getSubsystemPath(m.Cgroups, sysname) @@ -229,9 +236,14 @@ func (m *Manager) Apply(pid int) error { } paths[sysname] = subsystemPath } - m.Paths = paths + if paths["cpu"] != "" { + if err := fs.CheckCpushares(paths["cpu"], c.CpuShares); err != nil { + return err + } + } + return nil } @@ -247,6 +259,21 @@ func writeFile(dir, file, data string) error { return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) } +func join(c *configs.Cgroup, subsystem string, pid int) (string, error) { + path, err := getSubsystemPath(c, subsystem) + if err != nil { + return "", err + } + if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { + return "", err + } + if err := writeFile(path, "cgroup.procs", strconv.Itoa(pid)); err != nil { + return "", err + } + + return path, nil +} + func joinCpu(c *configs.Cgroup, pid int) error { path, err := getSubsystemPath(c, "cpu") if err != nil { @@ -266,16 +293,11 @@ func joinCpu(c *configs.Cgroup, pid int) error { } func joinFreezer(c *configs.Cgroup, pid int) error { - path, err := getSubsystemPath(c, "freezer") - if err != nil { + if _, err := join(c, "freezer", pid); err != nil { return err } - if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { - return err - } - - return ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700) + return nil } func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) { @@ -303,21 +325,15 @@ func (m *Manager) Freeze(state configs.FreezerState) error { return err } - if err := ioutil.WriteFile(filepath.Join(path, "freezer.state"), []byte(state), 0); err != nil { + prevState := m.Cgroups.Freezer + m.Cgroups.Freezer = state + + freezer := subsystems["freezer"] + err = freezer.Set(path, m.Cgroups) + if err != nil { + m.Cgroups.Freezer = prevState return err } - for { - state_, err := ioutil.ReadFile(filepath.Join(path, "freezer.state")) - if err != nil { - return err - } - if string(state) == string(bytes.TrimSpace(state_)) { - break - } - time.Sleep(1 * time.Millisecond) - } - - m.Cgroups.Freezer = state return nil } @@ -347,16 +363,12 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) { } func (m *Manager) Set(container *configs.Config) error { - panic("not implemented") -} - -func (m *Manager) EnterProcess(pid int) error { for name, path := range m.Paths { - _, ok := subsystems[name] + sys, ok := subsystems[name] if !ok || !cgroups.PathExists(path) { continue } - if err := writeFile(path, fs.CgroupProcesses, strconv.Itoa(pid)); err != nil { + if err := sys.Set(path, container.Cgroups); err != nil { return err } } @@ -373,43 +385,20 @@ func getUnitName(c *configs.Cgroup) string { // * Support for wildcards to allow /dev/pts support // // The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is -// in wide use. When both these are availalable we will be able to switch, but need to keep the old +// in wide use. When both these are available we will be able to switch, but need to keep the old // implementation for backwards compat. // // Note: we can't use systemd to set up the initial limits, and then change the cgroup // because systemd will re-write the device settings if it needs to re-apply the cgroup context. // This happens at least for v208 when any sibling unit is started. func joinDevices(c *configs.Cgroup, pid int) error { - path, err := getSubsystemPath(c, "devices") + path, err := join(c, "devices", pid) if err != nil { return err } - if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { - return err - } - - if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { - return err - } - - if !c.AllowAllDevices { - if err := writeFile(path, "devices.deny", "a"); err != nil { - return err - } - } - for _, dev := range c.AllowedDevices { - if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil { - return err - } - } - return nil -} - -// Symmetrical public function to update device based cgroups. Also available -// in the fs implementation. -func ApplyDevices(c *configs.Cgroup, pid int) error { - return joinDevices(c, pid) + devices := subsystems["devices"] + return devices.Set(path, c) } func joinMemory(c *configs.Cgroup, pid int) error { @@ -441,3 +430,40 @@ func joinCpuset(c *configs.Cgroup, pid int) error { return s.ApplyDir(path, c, pid) } + +// `BlockIODeviceWeight` property of systemd does not work properly, and systemd +// expects device path instead of major minor numbers, which is also confusing +// for users. So we use fs work around for now. +func joinBlkio(c *configs.Cgroup, pid int) error { + path, err := getSubsystemPath(c, "blkio") + if err != nil { + return err + } + if c.BlkioWeightDevice != "" { + if err := writeFile(path, "blkio.weight_device", c.BlkioWeightDevice); err != nil { + return err + } + } + if c.BlkioThrottleReadBpsDevice != "" { + if err := writeFile(path, "blkio.throttle.read_bps_device", c.BlkioThrottleReadBpsDevice); err != nil { + return err + } + } + if c.BlkioThrottleWriteBpsDevice != "" { + if err := writeFile(path, "blkio.throttle.write_bps_device", c.BlkioThrottleWriteBpsDevice); err != nil { + return err + } + } + if c.BlkioThrottleReadIOpsDevice != "" { + if err := writeFile(path, "blkio.throttle.read_iops_device", c.BlkioThrottleReadIOpsDevice); err != nil { + return err + } + } + if c.BlkioThrottleWriteIOpsDevice != "" { + if err := writeFile(path, "blkio.throttle.write_iops_device", c.BlkioThrottleWriteIOpsDevice); err != nil { + return err + } + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/cgroup.go b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/cgroup.go index 8bf174c1..8a161fcf 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/cgroup.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/cgroup.go @@ -19,6 +19,8 @@ type Cgroup struct { AllowedDevices []*Device `json:"allowed_devices"` + DeniedDevices []*Device `json:"denied_devices"` + // Memory limit (in bytes) Memory int64 `json:"memory"` @@ -43,9 +45,24 @@ type Cgroup struct { // MEM to use CpusetMems string `json:"cpuset_mems"` + // IO read rate limit per cgroup per device, bytes per second. + BlkioThrottleReadBpsDevice string `json:"blkio_throttle_read_bps_device"` + + // IO write rate limit per cgroup per divice, bytes per second. + BlkioThrottleWriteBpsDevice string `json:"blkio_throttle_write_bps_device"` + + // IO read rate limit per cgroup per device, IO per second. + BlkioThrottleReadIOpsDevice string `json:"blkio_throttle_read_iops_device"` + + // IO write rate limit per cgroup per device, IO per second. + BlkioThrottleWriteIOpsDevice string `json:"blkio_throttle_write_iops_device"` + // Specifies per cgroup weight, range is from 10 to 1000. BlkioWeight int64 `json:"blkio_weight"` + // Weight per cgroup per device, can override BlkioWeight. + BlkioWeightDevice string `json:"blkio_weight_device"` + // set the freeze value for the process Freezer FreezerState `json:"freezer"` diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/config.go b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/config.go index b07f252b..2c311a0c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/config.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/config.go @@ -37,6 +37,9 @@ type Config struct { // bind mounts are writtable. Readonlyfs bool `json:"readonlyfs"` + // Privatefs will mount the container's rootfs as private where mount points from the parent will not propogate + Privatefs bool `json:"privatefs"` + // Mounts specify additional source and destination paths that will be mounted inside the container's // rootfs and mount namespace if specified Mounts []*Mount `json:"mounts"` @@ -96,6 +99,10 @@ type Config struct { // ReadonlyPaths specifies paths within the container's rootfs to remount as read-only // so that these files prevent any writes. ReadonlyPaths []string `json:"readonly_paths"` + + // SystemProperties is a map of properties and their values. It is the equivalent of using + // sysctl -w my.property.name value in Linux. + SystemProperties map[string]string `json:"system_properties"` } // Gets the root uid for the process on host which could be non-zero diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/mount.go b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/mount.go index 7b3dea33..5a69f815 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/mount.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/mount.go @@ -18,4 +18,17 @@ type Mount struct { // Relabel source if set, "z" indicates shared, "Z" indicates unshared. Relabel string `json:"relabel"` + + // Optional Command to be run before Source is mounted. + PremountCmds []Command `json:"premount_cmds"` + + // Optional Command to be run after Source is mounted. + PostmountCmds []Command `json:"postmount_cmds"` +} + +type Command struct { + Path string `json:"path"` + Args []string `json:"args"` + Env []string `json:"env"` + Dir string `json:"dir"` } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces.go b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces.go index 9078e6ab..2c2a9fd2 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces.go @@ -1,9 +1,6 @@ package configs -import ( - "fmt" - "syscall" -) +import "fmt" type NamespaceType string @@ -16,6 +13,17 @@ const ( NEWUSER NamespaceType = "NEWUSER" ) +func NamespaceTypes() []NamespaceType { + return []NamespaceType{ + NEWNET, + NEWPID, + NEWNS, + NEWUTS, + NEWIPC, + NEWUSER, + } +} + // Namespace defines configuration for each namespace. It specifies an // alternate path that is able to be joined via setns. type Namespace struct { @@ -23,10 +31,6 @@ type Namespace struct { Path string `json:"path"` } -func (n *Namespace) Syscall() int { - return namespaceInfo[n.Type] -} - func (n *Namespace) GetPath(pid int) string { if n.Path != "" { return n.Path @@ -85,25 +89,3 @@ func (n *Namespaces) index(t NamespaceType) int { func (n *Namespaces) Contains(t NamespaceType) bool { return n.index(t) != -1 } - -var namespaceInfo = map[NamespaceType]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, -} - -// CloneFlags parses the container's Namespaces options to set the correct -// flags on clone, unshare. This functions returns flags only for new namespaces. -func (n *Namespaces) CloneFlags() uintptr { - var flag int - for _, v := range *n { - if v.Path != "" { - continue - } - flag |= namespaceInfo[v.Type] - } - return uintptr(flag) -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces_syscall.go b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces_syscall.go new file mode 100644 index 00000000..c962999e --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces_syscall.go @@ -0,0 +1,31 @@ +// +build linux + +package configs + +import "syscall" + +func (n *Namespace) Syscall() int { + return namespaceInfo[n.Type] +} + +var namespaceInfo = map[NamespaceType]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, +} + +// CloneFlags parses the container's Namespaces options to set the correct +// flags on clone, unshare. This functions returns flags only for new namespaces. +func (n *Namespaces) CloneFlags() uintptr { + var flag int + for _, v := range *n { + if v.Path != "" { + continue + } + flag |= namespaceInfo[v.Type] + } + return uintptr(flag) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go new file mode 100644 index 00000000..1bd26bd6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go @@ -0,0 +1,15 @@ +// +build !linux + +package configs + +func (n *Namespace) Syscall() int { + panic("No namespace syscall support") + return 0 +} + +// CloneFlags parses the container's Namespaces options to set the correct +// flags on clone, unshare. This functions returns flags only for new namespaces. +func (n *Namespaces) CloneFlags() uintptr { + panic("No namespace syscall support") + return uintptr(0) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/network.go b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/network.go index 9d5ed7a6..ccdb228e 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/configs/network.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/configs/network.go @@ -2,7 +2,7 @@ package configs // Network defines configuration for a container's networking stack // -// The network configuration can be omited from a container causing the +// The network configuration can be omitted from a container causing the // container to be setup with the host's networking stack type Network struct { // Type sets the networks type, commonly veth and loopback @@ -53,7 +53,7 @@ type Network struct { // Routes can be specified to create entries in the route table as the container is started // // All of destination, source, and gateway should be either IPv4 or IPv6. -// One of the three options must be present, and ommitted entries will use their +// One of the three options must be present, and omitted entries will use their // IP family default for the route table. For IPv4 for example, setting the // gateway to 1.2.3.4 and the interface to eth0 will set up a standard // destination of 0.0.0.0(or *) when viewed in the route table. diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/console_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/console_linux.go index afdc2976..a3a0551c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/console_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/console_linux.go @@ -38,7 +38,7 @@ func newConsole(uid, gid int) (Console, error) { }, nil } -// newConsoleFromPath is an internal fucntion returning an initialzied console for use inside +// newConsoleFromPath is an internal function returning an initialized console for use inside // a container's MNT namespace. func newConsoleFromPath(slavePath string) *linuxConsole { return &linuxConsole{ diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/container.go b/Godeps/_workspace/src/github.com/docker/libcontainer/container.go index b85dcb24..a38df826 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/container.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/container.go @@ -67,7 +67,7 @@ type Container interface { // State returns the current container's state information. // // errors: - // Systemerror - System erroor. + // Systemerror - System error. State() (*State, error) // Returns the current config of the container. @@ -108,14 +108,6 @@ type Container interface { // Systemerror - System error. Start(process *Process) (err error) - // Charge the resource usage of the specified process to this container. - // Does not join the namespaces of the container. - // - // errors: - // ContainerDestroyed - Container no longer exists, - // Systemerror - System error. - ChargeProcess(pid int) error - // Destroys the container after killing all running processes. // // Any event registrations are removed before the container is destroyed. diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/container_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/container_linux.go index ddd4b61b..1ffd7d9c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/container_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/container_linux.go @@ -16,6 +16,8 @@ import ( "github.com/docker/libcontainer/configs" ) +const stdioFdCount = 3 + type linuxContainer struct { id string root string @@ -112,10 +114,6 @@ func (c *linuxContainer) Start(process *Process) error { return nil } -func (c *linuxContainer) ChargeProcess(pid int) error { - return c.cgroupManager.EnterProcess(pid) -} - func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) { parentPipe, childPipe, err := newPipe() if err != nil { @@ -143,8 +141,11 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } - cmd.ExtraFiles = []*os.File{childPipe} - cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL + cmd.ExtraFiles = append(p.ExtraFiles, childPipe) + cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1)) + // NOTE: when running a container with no PID namespace and the parent process spawning the container is + // PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason + // even with the parent still running. if c.config.ParentDeathSignal > 0 { cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal) } @@ -180,11 +181,9 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, fmt.Sprintf("_LIBCONTAINER_INITPID=%d", c.initProcess.pid()), "_LIBCONTAINER_INITTYPE=setns", ) - if p.consolePath != "" { cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath) } - // TODO: set on container for process management return &setnsProcess{ cmd: cmd, @@ -197,13 +196,14 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, func (c *linuxContainer) newInitConfig(process *Process) *initConfig { return &initConfig{ - Config: c.config, - Args: process.Args, - Env: process.Env, - User: process.User, - Cwd: process.Cwd, - Console: process.consolePath, - Capabilities: process.Capabilities, + Config: c.config, + Args: process.Args, + Env: process.Env, + User: process.User, + Cwd: process.Cwd, + Console: process.consolePath, + Capabilities: process.Capabilities, + PassedFilesCount: len(process.ExtraFiles), } } @@ -308,5 +308,11 @@ func (c *linuxContainer) currentState() (*State, error) { for _, ns := range c.config.Namespaces { state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid()) } + for _, nsType := range configs.NamespaceTypes() { + if _, ok := state.NamespacePaths[nsType]; !ok { + ns := configs.Namespace{Type: nsType} + state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid()) + } + } return state, nil } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/container_linux_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/container_linux_test.go index 64382a8e..b05733e5 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/container_linux_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/container_linux_test.go @@ -33,10 +33,6 @@ func (m *mockCgroupManager) Set(container *configs.Config) error { return nil } -func (m *mockCgroupManager) ChargeProcess(pid int) error { - return nil -} - func (m *mockCgroupManager) Destroy() error { return nil } @@ -134,7 +130,8 @@ func TestGetContainerState(t *testing.T) { {Type: configs.NEWNS}, {Type: configs.NEWNET, Path: expectedNetworkPath}, {Type: configs.NEWUTS}, - {Type: configs.NEWIPC}, + // emulate host for IPC + //{Type: configs.NEWIPC}, }, }, initProcess: &mockProcess{ diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/devices/devices.go b/Godeps/_workspace/src/github.com/docker/libcontainer/devices/devices.go index 537f71af..7a11eaf1 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/devices/devices.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/devices/devices.go @@ -21,7 +21,7 @@ var ( ioutilReadDir = ioutil.ReadDir ) -// Given the path to a device and it's cgroup_permissions(which cannot be easilly queried) look up the information about a linux device and return that information as a Device struct. +// Given the path to a device and it's cgroup_permissions(which cannot be easily queried) look up the information about a linux device and return that information as a Device struct. func DeviceFromPath(path, permissions string) (*configs.Device, error) { fileInfo, err := osLstat(path) if err != nil { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/factory.go b/Godeps/_workspace/src/github.com/docker/libcontainer/factory.go index 0c9fa63a..2b3ff85d 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/factory.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/factory.go @@ -32,15 +32,13 @@ type Factory interface { // System error Load(id string) (Container, error) - // StartInitialization is an internal API to libcontainer used during the rexec of the - // container. pipefd is the fd to the child end of the pipe used to syncronize the - // parent and child process providing state and configuration to the child process and - // returning any errors during the init of the container + // StartInitialization is an internal API to libcontainer used during the reexec of the + // container. // // Errors: - // pipe connection error - // system error - StartInitialization(pipefd uintptr) error + // Pipe connection error + // System error + StartInitialization() error // Type returns info string about factory type (e.g. lxc, libcontainer...) Type() string diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/factory_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/factory_linux.go index a2d3bec7..3cf1c3d2 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/factory_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/factory_linux.go @@ -10,6 +10,7 @@ import ( "os/exec" "path/filepath" "regexp" + "strconv" "syscall" "github.com/docker/docker/pkg/mount" @@ -194,7 +195,11 @@ func (l *LinuxFactory) Type() string { // StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state // This is a low level implementation detail of the reexec and should not be consumed externally -func (l *LinuxFactory) StartInitialization(pipefd uintptr) (err error) { +func (l *LinuxFactory) StartInitialization() (err error) { + pipefd, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_INITPIPE")) + if err != nil { + return err + } var ( pipe = os.NewFile(uintptr(pipefd), "pipe") it = initType(os.Getenv("_LIBCONTAINER_INITTYPE")) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/init_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/init_linux.go index 0468b2e9..4bbb713d 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/init_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/init_linux.go @@ -40,14 +40,15 @@ type network struct { // initConfig is used for transferring parameters from Exec() to Init() type initConfig struct { - Args []string `json:"args"` - Env []string `json:"env"` - Cwd string `json:"cwd"` - Capabilities []string `json:"capabilities"` - User string `json:"user"` - Config *configs.Config `json:"config"` - Console string `json:"console"` - Networks []*network `json:"network"` + Args []string `json:"args"` + Env []string `json:"env"` + Cwd string `json:"cwd"` + Capabilities []string `json:"capabilities"` + User string `json:"user"` + Config *configs.Config `json:"config"` + Console string `json:"console"` + Networks []*network `json:"network"` + PassedFilesCount int `json:"passed_files_count"` } type initer interface { @@ -69,7 +70,8 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) { }, nil case initStandard: return &linuxStandardInit{ - config: config, + parentPid: syscall.Getppid(), + config: config, }, nil } return nil, fmt.Errorf("unknown init type %q", t) @@ -94,10 +96,10 @@ func populateProcessEnvironment(env []string) error { // and working dir, and closes any leaked file descriptors // before executing the command inside the namespace func finalizeNamespace(config *initConfig) error { - // Ensure that all non-standard fds we may have accidentally + // Ensure that all unwanted fds we may have accidentally // inherited are marked close-on-exec so they stay out of the // container - if err := utils.CloseExecFrom(3); err != nil { + if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil { return err } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/exec_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/exec_test.go index 4afff77d..c8b16687 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/exec_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/exec_test.go @@ -4,11 +4,14 @@ import ( "bytes" "io/ioutil" "os" + "path/filepath" "strconv" "strings" + "syscall" "testing" "github.com/docker/libcontainer" + "github.com/docker/libcontainer/cgroups/systemd" "github.com/docker/libcontainer/configs" ) @@ -28,9 +31,7 @@ func testExecPS(t *testing.T, userns bool) { return } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) if userns { @@ -63,21 +64,15 @@ func TestIPCPrivate(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) l, err := os.Readlink("/proc/1/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) config := newTemplateConfig(rootfs) buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) if exitCode != 0 { t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) @@ -94,22 +89,16 @@ func TestIPCHost(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) l, err := os.Readlink("/proc/1/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) config := newTemplateConfig(rootfs) config.Namespaces.Remove(configs.NEWIPC) buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) if exitCode != 0 { t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) @@ -126,23 +115,17 @@ func TestIPCJoinPath(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) l, err := os.Readlink("/proc/1/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) config := newTemplateConfig(rootfs) config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipc") buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) if exitCode != 0 { t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) @@ -159,9 +142,7 @@ func TestIPCBadPath(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) @@ -179,16 +160,12 @@ func TestRlimit(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) out, _, err := runContainer(config, "", "/bin/sh", "-c", "ulimit -n") - if err != nil { - t.Fatal(err) - } + ok(t, err) if limit := strings.TrimSpace(out.Stdout.String()); limit != "1025" { t.Fatalf("expected rlimit to be 1025, got %s", limit) } @@ -207,9 +184,7 @@ func newTestRoot() (string, error) { func waitProcess(p *libcontainer.Process, t *testing.T) { status, err := p.Wait() - if err != nil { - t.Fatal(err) - } + ok(t, err) if !status.Success() { t.Fatal(status) } @@ -220,35 +195,22 @@ func TestEnter(t *testing.T) { return } root, err := newTestRoot() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer os.RemoveAll(root) rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) - factory, err := libcontainer.New(root, libcontainer.Cgroupfs) - if err != nil { - t.Fatal(err) - } - container, err := factory.Create("test", config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() // Execute a first process in the container stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) var stdout, stdout2 bytes.Buffer @@ -261,19 +223,13 @@ func TestEnter(t *testing.T) { err = container.Start(&pconfig) stdinR.Close() defer stdinW.Close() - if err != nil { - t.Fatal(err) - } + ok(t, err) pid, err := pconfig.Pid() - if err != nil { - t.Fatal(err) - } + ok(t, err) // Execute another process in the container stdinR2, stdinW2, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) pconfig2 := libcontainer.Process{ Env: standardEnvironment, } @@ -284,19 +240,13 @@ func TestEnter(t *testing.T) { err = container.Start(&pconfig2) stdinR2.Close() defer stdinW2.Close() - if err != nil { - t.Fatal(err) - } + ok(t, err) pid2, err := pconfig2.Pid() - if err != nil { - t.Fatal(err) - } + ok(t, err) processes, err := container.Processes() - if err != nil { - t.Fatal(err) - } + ok(t, err) n := 0 for i := range processes { @@ -317,14 +267,10 @@ func TestEnter(t *testing.T) { // Check that both processes live in the same pidns pidns := string(stdout.Bytes()) - if err != nil { - t.Fatal(err) - } + ok(t, err) pidns2 := string(stdout2.Bytes()) - if err != nil { - t.Fatal(err) - } + ok(t, err) if pidns != pidns2 { t.Fatal("The second process isn't in the required pid namespace", pidns, pidns2) @@ -336,28 +282,17 @@ func TestProcessEnv(t *testing.T) { return } root, err := newTestRoot() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer os.RemoveAll(root) rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) - factory, err := libcontainer.New(root, libcontainer.Cgroupfs) - if err != nil { - t.Fatal(err) - } - container, err := factory.Create("test", config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() var stdout bytes.Buffer @@ -373,17 +308,12 @@ func TestProcessEnv(t *testing.T) { Stdout: &stdout, } err = container.Start(&pconfig) - if err != nil { - t.Fatal(err) - } + ok(t, err) // Wait for process waitProcess(&pconfig, t) outputEnv := string(stdout.Bytes()) - if err != nil { - t.Fatal(err) - } // Check that the environment has the key/value pair we added if !strings.Contains(outputEnv, "FOO=BAR") { @@ -401,28 +331,17 @@ func TestProcessCaps(t *testing.T) { return } root, err := newTestRoot() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer os.RemoveAll(root) rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) - factory, err := libcontainer.New(root, libcontainer.Cgroupfs) - if err != nil { - t.Fatal(err) - } - container, err := factory.Create("test", config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() processCaps := append(config.Capabilities, "NET_ADMIN") @@ -436,17 +355,12 @@ func TestProcessCaps(t *testing.T) { Stdout: &stdout, } err = container.Start(&pconfig) - if err != nil { - t.Fatal(err) - } + ok(t, err) // Wait for process waitProcess(&pconfig, t) outputStatus := string(stdout.Bytes()) - if err != nil { - t.Fatal(err) - } lines := strings.Split(outputStatus, "\n") @@ -481,6 +395,108 @@ func TestProcessCaps(t *testing.T) { } func TestFreeze(t *testing.T) { + testFreeze(t, false) +} + +func TestSystemdFreeze(t *testing.T) { + if !systemd.UseSystemd() { + t.Skip("Systemd is unsupported") + } + testFreeze(t, true) +} + +func testFreeze(t *testing.T, systemd bool) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + f := factory + if systemd { + f = systemdFactory + } + + container, err := f.Create("test", config) + ok(t, err) + defer container.Destroy() + + stdinR, stdinW, err := os.Pipe() + ok(t, err) + + pconfig := libcontainer.Process{ + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(&pconfig) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + pid, err := pconfig.Pid() + ok(t, err) + + process, err := os.FindProcess(pid) + ok(t, err) + + err = container.Pause() + ok(t, err) + state, err := container.Status() + ok(t, err) + err = container.Resume() + ok(t, err) + if state != libcontainer.Paused { + t.Fatal("Unexpected state: ", state) + } + + stdinW.Close() + s, err := process.Wait() + ok(t, err) + + if !s.Success() { + t.Fatal(s.String()) + } +} + +func TestCpuShares(t *testing.T) { + testCpuShares(t, false) +} + +func TestSystemdCpuShares(t *testing.T) { + if !systemd.UseSystemd() { + t.Skip("Systemd is unsupported") + } + testCpuShares(t, true) +} + +func testCpuShares(t *testing.T, systemd bool) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + if systemd { + config.Cgroups.Slice = "system.slice" + } + config.Cgroups.CpuShares = 1 + + _, _, err = runContainer(config, "", "ps") + if err == nil { + t.Fatalf("runContainer should failed with invalid CpuShares") + } +} + +func TestContainerState(t *testing.T) { if testing.Short() { return } @@ -496,13 +512,21 @@ func TestFreeze(t *testing.T) { } defer remove(rootfs) - config := newTemplateConfig(rootfs) - - factory, err := libcontainer.New(root, libcontainer.Cgroupfs) + l, err := os.Readlink("/proc/1/ns/ipc") if err != nil { t.Fatal(err) } + config := newTemplateConfig(rootfs) + config.Namespaces = configs.Namespaces([]configs.Namespace{ + {Type: configs.NEWNS}, + {Type: configs.NEWUTS}, + // host for IPC + //{Type: configs.NEWIPC}, + {Type: configs.NEWPID}, + {Type: configs.NEWNET}, + }) + container, err := factory.Create("test", config) if err != nil { t.Fatal(err) @@ -513,49 +537,199 @@ func TestFreeze(t *testing.T) { if err != nil { t.Fatal(err) } - - pconfig := libcontainer.Process{ + p := &libcontainer.Process{ Args: []string{"cat"}, Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(&pconfig) + err = container.Start(p) + if err != nil { + t.Fatal(err) + } stdinR.Close() - defer stdinW.Close() + defer p.Signal(os.Kill) + + st, err := container.State() if err != nil { t.Fatal(err) } - pid, err := pconfig.Pid() + l1, err := os.Readlink(st.NamespacePaths[configs.NEWIPC]) if err != nil { t.Fatal(err) } - - process, err := os.FindProcess(pid) - if err != nil { - t.Fatal(err) + if l1 != l { + t.Fatal("Container using non-host ipc namespace") } - - if err := container.Pause(); err != nil { - t.Fatal(err) - } - state, err := container.Status() - if err != nil { - t.Fatal(err) - } - if err := container.Resume(); err != nil { - t.Fatal(err) - } - if state != libcontainer.Paused { - t.Fatal("Unexpected state: ", state) - } - stdinW.Close() - s, err := process.Wait() + p.Wait() +} + +func TestPassExtraFiles(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() if err != nil { t.Fatal(err) } - if !s.Success() { - t.Fatal(s.String()) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + var stdout bytes.Buffer + pipeout1, pipein1, err := os.Pipe() + pipeout2, pipein2, err := os.Pipe() + process := libcontainer.Process{ + Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + ExtraFiles: []*os.File{pipein1, pipein2}, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&process) + if err != nil { + t.Fatal(err) + } + + waitProcess(&process, t) + + out := string(stdout.Bytes()) + // fd 5 is the directory handle for /proc/$$/fd + if out != "0 1 2 3 4 5" { + t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to init, got '%s'", out) + } + var buf = []byte{0} + _, err = pipeout1.Read(buf) + if err != nil { + t.Fatal(err) + } + out1 := string(buf) + if out1 != "1" { + t.Fatalf("expected first pipe to receive '1', got '%s'", out1) + } + + _, err = pipeout2.Read(buf) + if err != nil { + t.Fatal(err) + } + out2 := string(buf) + if out2 != "2" { + t.Fatalf("expected second pipe to receive '2', got '%s'", out2) + } +} + +func TestMountCmds(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + tmpDir, err := ioutil.TempDir("", "tmpdir") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + config := newTemplateConfig(rootfs) + config.Mounts = append(config.Mounts, &configs.Mount{ + Source: tmpDir, + Destination: filepath.Join(rootfs, "tmp"), + Device: "bind", + Flags: syscall.MS_BIND | syscall.MS_REC, + PremountCmds: []configs.Command{ + {Path: "touch", Args: []string{filepath.Join(tmpDir, "hello")}}, + {Path: "touch", Args: []string{filepath.Join(tmpDir, "world")}}, + }, + PostmountCmds: []configs.Command{ + {Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "hello"), filepath.Join(rootfs, "tmp", "hello-backup")}}, + {Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "world"), filepath.Join(rootfs, "tmp", "world-backup")}}, + }, + }) + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + pconfig := libcontainer.Process{ + Args: []string{"sh", "-c", "env"}, + Env: standardEnvironment, + } + err = container.Start(&pconfig) + if err != nil { + t.Fatal(err) + } + + // Wait for process + waitProcess(&pconfig, t) + + entries, err := ioutil.ReadDir(tmpDir) + if err != nil { + t.Fatal(err) + } + expected := []string{"hello", "hello-backup", "world", "world-backup"} + for i, e := range entries { + if e.Name() != expected[i] { + t.Errorf("Got(%s), expect %s", e.Name(), expected[i]) + } + } +} + +func TestSystemProperties(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.SystemProperties = map[string]string{ + "kernel.shmmni": "8192", + } + + container, err := factory.Create("test", config) + ok(t, err) + defer container.Destroy() + + var stdout bytes.Buffer + pconfig := libcontainer.Process{ + Args: []string{"sh", "-c", "cat /proc/sys/kernel/shmmni"}, + Env: standardEnvironment, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&pconfig) + ok(t, err) + + // Wait for process + waitProcess(&pconfig, t) + + shmmniOutput := strings.TrimSpace(string(stdout.Bytes())) + if shmmniOutput != "8192" { + t.Fatalf("kernel.shmmni property expected to be 8192, but is %s", shmmniOutput) } } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/execin_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/execin_test.go index 252e6e41..f81faf01 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/execin_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/execin_test.go @@ -16,22 +16,16 @@ func TestExecIn(t *testing.T) { return } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) container, err := newContainer(config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() // Execute a first process in the container stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) process := &libcontainer.Process{ Args: []string{"cat"}, Env: standardEnvironment, @@ -40,9 +34,7 @@ func TestExecIn(t *testing.T) { err = container.Start(process) stdinR.Close() defer stdinW.Close() - if err != nil { - t.Fatal(err) - } + ok(t, err) buffers := newStdBuffers() ps := &libcontainer.Process{ @@ -53,12 +45,9 @@ func TestExecIn(t *testing.T) { Stderr: buffers.Stderr, } err = container.Start(ps) - if err != nil { - t.Fatal(err) - } - if _, err := ps.Wait(); err != nil { - t.Fatal(err) - } + ok(t, err) + _, err = ps.Wait() + ok(t, err) stdinW.Close() if _, err := process.Wait(); err != nil { t.Log(err) @@ -74,21 +63,15 @@ func TestExecInRlimit(t *testing.T) { return } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) container, err := newContainer(config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) process := &libcontainer.Process{ Args: []string{"cat"}, Env: standardEnvironment, @@ -97,9 +80,7 @@ func TestExecInRlimit(t *testing.T) { err = container.Start(process) stdinR.Close() defer stdinW.Close() - if err != nil { - t.Fatal(err) - } + ok(t, err) buffers := newStdBuffers() ps := &libcontainer.Process{ @@ -110,12 +91,9 @@ func TestExecInRlimit(t *testing.T) { Stderr: buffers.Stderr, } err = container.Start(ps) - if err != nil { - t.Fatal(err) - } - if _, err := ps.Wait(); err != nil { - t.Fatal(err) - } + ok(t, err) + _, err = ps.Wait() + ok(t, err) stdinW.Close() if _, err := process.Wait(); err != nil { t.Log(err) @@ -131,22 +109,16 @@ func TestExecInError(t *testing.T) { return } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) container, err := newContainer(config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() // Execute a first process in the container stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) process := &libcontainer.Process{ Args: []string{"cat"}, Env: standardEnvironment, @@ -160,9 +132,7 @@ func TestExecInError(t *testing.T) { t.Log(err) } }() - if err != nil { - t.Fatal(err) - } + ok(t, err) unexistent := &libcontainer.Process{ Args: []string{"unexistent"}, @@ -178,6 +148,121 @@ func TestExecInError(t *testing.T) { } func TestExecInTTY(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + var stdout bytes.Buffer + ps := &libcontainer.Process{ + Args: []string{"ps"}, + Env: standardEnvironment, + } + console, err := ps.NewConsole(0) + copy := make(chan struct{}) + go func() { + io.Copy(&stdout, console) + close(copy) + }() + ok(t, err) + err = container.Start(ps) + ok(t, err) + select { + case <-time.After(5 * time.Second): + t.Fatal("Waiting for copy timed out") + case <-copy: + } + _, err = ps.Wait() + ok(t, err) + stdinW.Close() + if _, err := process.Wait(); err != nil { + t.Log(err) + } + out := stdout.String() + if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") { + t.Fatalf("unexpected running process, output %q", out) + } +} + +func TestExecInEnvironment(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + buffers := newStdBuffers() + process2 := &libcontainer.Process{ + Args: []string{"env"}, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "DEBUG=true", + "DEBUG=false", + "ENV=test", + }, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + err = container.Start(process2) + ok(t, err) + if _, err := process2.Wait(); err != nil { + out := buffers.Stdout.String() + t.Fatal(err, out) + } + stdinW.Close() + if _, err := process.Wait(); err != nil { + t.Log(err) + } + out := buffers.Stdout.String() + // check execin's process environment + if !strings.Contains(out, "DEBUG=false") || + !strings.Contains(out, "ENV=test") || + !strings.Contains(out, "HOME=/root") || + !strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") || + strings.Contains(out, "DEBUG=true") { + t.Fatalf("unexpected running process, output %q", out) + } +} + +func TestExecinPassExtraFiles(t *testing.T) { if testing.Short() { return } @@ -211,106 +296,45 @@ func TestExecInTTY(t *testing.T) { } var stdout bytes.Buffer - ps := &libcontainer.Process{ - Args: []string{"ps"}, - Env: standardEnvironment, + pipeout1, pipein1, err := os.Pipe() + pipeout2, pipein2, err := os.Pipe() + inprocess := &libcontainer.Process{ + Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + ExtraFiles: []*os.File{pipein1, pipein2}, + Stdin: nil, + Stdout: &stdout, } - console, err := ps.NewConsole(0) - copy := make(chan struct{}) - go func() { - io.Copy(&stdout, console) - close(copy) - }() + err = container.Start(inprocess) if err != nil { t.Fatal(err) } - err = container.Start(ps) - if err != nil { - t.Fatal(err) - } - select { - case <-time.After(5 * time.Second): - t.Fatal("Waiting for copy timed out") - case <-copy: - } - if _, err := ps.Wait(); err != nil { - t.Fatal(err) - } + + waitProcess(inprocess, t) stdinW.Close() - if _, err := process.Wait(); err != nil { - t.Log(err) + waitProcess(process, t) + + out := string(stdout.Bytes()) + // fd 5 is the directory handle for /proc/$$/fd + if out != "0 1 2 3 4 5" { + t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out) } - out := stdout.String() - if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") { - t.Fatalf("unexpected running process, output %q", out) - } -} - -func TestExecInEnvironment(t *testing.T) { - if testing.Short() { - return - } - rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } - defer remove(rootfs) - config := newTemplateConfig(rootfs) - container, err := newContainer(config) - if err != nil { - t.Fatal(err) - } - defer container.Destroy() - - // Execute a first process in the container - stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - process := &libcontainer.Process{ - Args: []string{"cat"}, - Env: standardEnvironment, - Stdin: stdinR, - } - err = container.Start(process) - stdinR.Close() - defer stdinW.Close() - if err != nil { - t.Fatal(err) - } - - buffers := newStdBuffers() - process2 := &libcontainer.Process{ - Args: []string{"env"}, - Env: []string{ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "DEBUG=true", - "DEBUG=false", - "ENV=test", - }, - Stdin: buffers.Stdin, - Stdout: buffers.Stdout, - Stderr: buffers.Stderr, - } - err = container.Start(process2) - if err != nil { - t.Fatal(err) - } - if _, err := process2.Wait(); err != nil { - out := buffers.Stdout.String() - t.Fatal(err, out) - } - stdinW.Close() - if _, err := process.Wait(); err != nil { - t.Log(err) - } - out := buffers.Stdout.String() - // check execin's process environment - if !strings.Contains(out, "DEBUG=false") || - !strings.Contains(out, "ENV=test") || - !strings.Contains(out, "HOME=/root") || - !strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") || - strings.Contains(out, "DEBUG=true") { - t.Fatalf("unexpected running process, output %q", out) + var buf = []byte{0} + _, err = pipeout1.Read(buf) + if err != nil { + t.Fatal(err) + } + out1 := string(buf) + if out1 != "1" { + t.Fatalf("expected first pipe to receive '1', got '%s'", out1) + } + + _, err = pipeout2.Read(buf) + if err != nil { + t.Fatal(err) + } + out2 := string(buf) + if out2 != "2" { + t.Fatalf("expected second pipe to receive '2', got '%s'", out2) } } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/init_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/init_test.go index f11834de..b1ee20da 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/init_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/init_test.go @@ -1,11 +1,13 @@ package integration import ( - "log" "os" "runtime" + "testing" + log "github.com/Sirupsen/logrus" "github.com/docker/libcontainer" + "github.com/docker/libcontainer/cgroups/systemd" _ "github.com/docker/libcontainer/nsenter" ) @@ -21,7 +23,38 @@ func init() { if err != nil { log.Fatalf("unable to initialize for container: %s", err) } - if err := factory.StartInitialization(3); err != nil { + if err := factory.StartInitialization(); err != nil { log.Fatal(err) } } + +var ( + factory libcontainer.Factory + systemdFactory libcontainer.Factory +) + +func TestMain(m *testing.M) { + var ( + err error + ret int = 0 + ) + + log.SetOutput(os.Stderr) + log.SetLevel(log.InfoLevel) + + factory, err = libcontainer.New(".", libcontainer.Cgroupfs) + if err != nil { + log.Error(err) + os.Exit(1) + } + if systemd.UseSystemd() { + systemdFactory, err = libcontainer.New(".", libcontainer.SystemdCgroups) + if err != nil { + log.Error(err) + os.Exit(1) + } + } + + ret = m.Run() + os.Exit(ret) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/utils_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/utils_test.go index c444eecf..ffd7130b 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/integration/utils_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/integration/utils_test.go @@ -6,8 +6,11 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" + "runtime" "strings" "syscall" + "testing" "github.com/docker/libcontainer" "github.com/docker/libcontainer/configs" @@ -38,6 +41,14 @@ func (b *stdBuffers) String() string { return strings.Join(s, "|") } +// ok fails the test if an err is not nil. +func ok(t testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + t.Fatalf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error()) + } +} + // newRootfs creates a new tmp directory and copies the busybox root filesystem func newRootfs() (string, error) { dir, err := ioutil.TempDir("", "") @@ -68,14 +79,13 @@ func copyBusybox(dest string) error { } func newContainer(config *configs.Config) (libcontainer.Container, error) { - factory, err := libcontainer.New(".", - libcontainer.InitArgs(os.Args[0], "init", "--"), - libcontainer.Cgroupfs, - ) - if err != nil { - return nil, err + f := factory + + if config.Cgroups != nil && config.Cgroups.Slice == "system.slice" { + f = systemdFactory } - return factory.Create("testCT", config) + + return f.Create("testCT", config) } // runContainer runs the container with the specific config and arguments diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux.go index 5983031a..7bc40ddd 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux.go @@ -101,10 +101,22 @@ func SetFileCreateLabel(fileLabel string) error { // the MCS label should continue to be used. SELinux will use this field // to make sure the content can not be shared by other containes. func Relabel(path string, fileLabel string, relabel string) error { + exclude_path := []string{"/", "/usr", "/etc"} if fileLabel == "" { return nil } - if relabel == "z" { + for _, p := range exclude_path { + if path == p { + return fmt.Errorf("Relabeling of %s is not allowed", path) + } + } + if !strings.ContainsAny(relabel, "zZ") { + return nil + } + if strings.Contains(relabel, "z") && strings.Contains(relabel, "Z") { + return fmt.Errorf("Bad SELinux option z and Z can not be used together") + } + if strings.Contains(relabel, "z") { c := selinux.NewContext(fileLabel) c["level"] = "s0" fileLabel = c.Get() diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux_test.go index 8629353f..6ab0c67c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux_test.go @@ -87,3 +87,31 @@ func TestDuplicateLabel(t *testing.T) { t.Errorf("DisableSecOpt Failed level incorrect") } } +func TestRelabel(t *testing.T) { + testdir := "/tmp/test" + label := "system_u:system_r:svirt_sandbox_file_t:s0:c1,c2" + if err := Relabel(testdir, "", "z"); err != nil { + t.Fatal("Relabel with no label failed: %v", err) + } + if err := Relabel(testdir, label, ""); err != nil { + t.Fatal("Relabel with no relabel field failed: %v", err) + } + if err := Relabel(testdir, label, "z"); err != nil { + t.Fatal("Relabel shared failed: %v", err) + } + if err := Relabel(testdir, label, "Z"); err != nil { + t.Fatal("Relabel unshared failed: %v", err) + } + if err := Relabel(testdir, label, "zZ"); err == nil { + t.Fatal("Relabel with shared and unshared succeeded") + } + if err := Relabel("/etc", label, "zZ"); err == nil { + t.Fatal("Relabel /etc succeeded") + } + if err := Relabel("/", label, ""); err == nil { + t.Fatal("Relabel / succeeded") + } + if err := Relabel("/usr", label, "Z"); err == nil { + t.Fatal("Relabel /usr succeeded") + } +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/README.md b/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/README.md index ac94cba0..d1a60ef9 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/README.md +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/README.md @@ -1,6 +1,25 @@ ## nsenter -The `nsenter` package registers a special init constructor that is called before the Go runtime has -a chance to boot. This provides us the ability to `setns` on existing namespaces and avoid the issues -that the Go runtime has with multiple threads. This constructor is only called if this package is -registered, imported, in your go application and the argv 0 is `nsenter`. +The `nsenter` package registers a special init constructor that is called before +the Go runtime has a chance to boot. This provides us the ability to `setns` on +existing namespaces and avoid the issues that the Go runtime has with multiple +threads. This constructor will be called if this package is registered, +imported, in your go application. + +The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd/cgo/) +package. In cgo, if the import of "C" is immediately preceded by a comment, that comment, +called the preamble, is used as a header when compiling the C parts of the package. +So every time we import package `nsenter`, the C code function `nsexec()` would be +called. And package `nsenter` is now only imported in Docker execdriver, so every time +before we call `execdriver.Exec()`, that C code would run. + +`nsexec()` will first check the environment variable `_LIBCONTAINER_INITPID` +which will give the process of the container that should be joined. Namespaces fd will +be found from `/proc/[pid]/ns` and set by `setns` syscall. + +And then get the pipe number from `_LIBCONTAINER_INITPIPE`, error message could +be transfered through it. If tty is added, `_LIBCONTAINER_CONSOLE_PATH` will +have value and start a console for output. + +Finally, `nsexec()` will clone a child process , exit the parent process and let +the Go runtime take over. diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/nsenter_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/nsenter_test.go index 34e1f521..db27b8a4 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/nsenter_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/nsenter_test.go @@ -24,7 +24,7 @@ func TestNsenterAlivePid(t *testing.T) { Path: os.Args[0], Args: args, ExtraFiles: []*os.File{w}, - Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid())}, + Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid()), "_LIBCONTAINER_INITPIPE=3"}, } if err := cmd.Start(); err != nil { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/nsexec.c b/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/nsexec.c index e7658f38..d8e45f3c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/nsexec.c +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsenter/nsexec.c @@ -66,7 +66,7 @@ void nsexec() const int num = sizeof(namespaces) / sizeof(char *); jmp_buf env; char buf[PATH_MAX], *val; - int i, tfd, child, len, consolefd = -1; + int i, tfd, child, len, pipenum, consolefd = -1; pid_t pid; char *console; @@ -81,6 +81,19 @@ void nsexec() exit(1); } + val = getenv("_LIBCONTAINER_INITPIPE"); + if (val == NULL) { + pr_perror("Child pipe not found"); + exit(1); + } + + pipenum = atoi(val); + snprintf(buf, sizeof(buf), "%d", pipenum); + if (strcmp(val, buf)) { + pr_perror("Unable to parse _LIBCONTAINER_INITPIPE"); + exit(1); + } + console = getenv("_LIBCONTAINER_CONSOLE_PATH"); if (console != NULL) { consolefd = open(console, O_RDWR); @@ -124,6 +137,8 @@ void nsexec() } if (setjmp(env) == 1) { + // Child + if (setsid() == -1) { pr_perror("setsid failed"); exit(1); @@ -149,7 +164,11 @@ void nsexec() // Finish executing, let the Go runtime take over. return; } + // Parent + // We must fork to actually enter the PID namespace, use CLONE_PARENT + // so the child can have the right parent, and we don't need to forward + // the child's exit code or resend its death signal. child = clone_parent(&env); if (child < 0) { pr_perror("Unable to fork"); @@ -158,7 +177,7 @@ void nsexec() len = snprintf(buf, sizeof(buf), "{ \"pid\" : %d }\n", child); - if (write(3, buf, len) != len) { + if (write(pipenum, buf, len) != len) { pr_perror("Unable to send a child pid"); kill(child, SIGKILL); exit(1); diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/README.md b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/README.md index f2e66a86..98bed0e8 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/README.md +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/README.md @@ -65,3 +65,48 @@ You can identify if a process is running in a container by looking to see if You may also specify an alternate root directory from where the `container.json` file is read and where the `state.json` file will be saved. + +### How to use? + +Currently nsinit has 9 commands. Type `nsinit -h` to list all of them. +And for every alternative command, you can also use `--help` to get more +detailed help documents. For example, `nsinit config --help`. + +`nsinit` cli application is implemented using [cli.go](https://github.com/codegangsta/cli). +Lots of details are handled in cli.go, so the implementation of `nsinit` itself +is very clean and clear. + +* **config** +It will generate a standard configuration file for a container. By default, it +will generate as the template file in [config.go](https://github.com/docker/libcontainer/blob/master/nsinit/config.go#L192). +It will modify the template if you have specified some configuration by options. +* **exec** +Starts a container and execute a new command inside it. Besides common options, it +has some special options as below. + - `--tty,-t`: allocate a TTY to the container. + - `--config`: you can specify a configuration file. By default, it will use + template configuration. + - `--id`: specify the ID for a container. By default, the id is "nsinit". + - `--user,-u`: set the user, uid, and/or gid for the process. By default the + value is "root". + - `--cwd`: set the current working dir. + - `--env`: set environment variables for the process. +* **init** +It's an internal command that is called inside the container's namespaces to +initialize the namespace and exec the user's process. It should not be called +externally. +* **oom** +Display oom notifications for a container, you should specify container id. +* **pause** +Pause the container's processes, you should specify container id. It will use +cgroup freeze subsystem to help. +* **unpause** +Unpause the container's processes. Same with `pause`. +* **stats** +Display statistics for the container, it will mainly show cgroup and network +statistics. +* **state** +Get the container's current state. You can also read the state from `state.json` + in your container_id folder. +* **help, h** +Shows a list of commands or help for one command. diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/config.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/config.go index e50bb3c1..1eee9dd9 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/config.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/config.go @@ -43,6 +43,7 @@ var createFlags = []cli.Flag{ cli.StringFlag{Name: "veth-address", Usage: "veth ip address"}, cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"}, cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"}, + cli.BoolFlag{Name: "cgroup", Usage: "mount the cgroup data for the container"}, } var configCommand = cli.Command{ @@ -187,6 +188,12 @@ func modify(config *configs.Config, context *cli.Context) { } config.Networks = append(config.Networks, network) } + if context.Bool("cgroup") { + config.Mounts = append(config.Mounts, &configs.Mount{ + Destination: "/sys/fs/cgroup", + Device: "cgroup", + }) + } } func getTemplate() *configs.Config { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/exec.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/exec.go index 9d302aa3..cf40a595 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/exec.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/exec.go @@ -23,6 +23,7 @@ var execCommand = cli.Command{ Action: execAction, Flags: append([]cli.Flag{ cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"}, + cli.BoolFlag{Name: "systemd", Usage: "Use systemd for managing cgroups, if available"}, cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, cli.StringFlag{Name: "config", Value: "", Usage: "path to the configuration file"}, cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"}, diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/init.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/init.go index 7b2cf193..c7506a0e 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/init.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/init.go @@ -20,7 +20,7 @@ var initCommand = cli.Command{ if err != nil { fatal(err) } - if err := factory.StartInitialization(3); err != nil { + if err := factory.StartInitialization(); err != nil { fatal(err) } panic("This line should never been executed") diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main.go index 922d74cc..eec064c2 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main.go @@ -13,7 +13,7 @@ func main() { app.Version = "2" app.Author = "libcontainer maintainers" app.Flags = []cli.Flag{ - cli.StringFlag{Name: "root", Value: ".", Usage: "root directory for containers"}, + cli.StringFlag{Name: "root", Value: "/var/run/nsinit", Usage: "root directory for containers"}, cli.StringFlag{Name: "log-file", Value: "", Usage: "set the log file to output logs to"}, cli.BoolFlag{Name: "debug", Usage: "enable debug output in the logs"}, } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/oom.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/oom.go index a59b7533..412534bc 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/oom.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/oom.go @@ -1,8 +1,7 @@ package main import ( - "log" - + log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" ) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/pause.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/pause.go index 89af0b6f..40aace44 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/pause.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/pause.go @@ -1,8 +1,7 @@ package main import ( - "log" - + log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" ) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/utils.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/utils.go index 4deca766..92f0a9d9 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/utils.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/utils.go @@ -3,10 +3,12 @@ package main import ( "encoding/json" "fmt" + log "github.com/Sirupsen/logrus" "os" "github.com/codegangsta/cli" "github.com/docker/libcontainer" + "github.com/docker/libcontainer/cgroups/systemd" "github.com/docker/libcontainer/configs" ) @@ -29,7 +31,15 @@ func loadConfig(context *cli.Context) (*configs.Config, error) { } func loadFactory(context *cli.Context) (libcontainer.Factory, error) { - return libcontainer.New(context.GlobalString("root"), libcontainer.Cgroupfs) + cgm := libcontainer.Cgroupfs + if context.Bool("systemd") { + if systemd.UseSystemd() { + cgm = libcontainer.SystemdCgroups + } else { + log.Warn("systemd cgroup flag passed, but systemd support for managing cgroups is not available.") + } + } + return libcontainer.New(context.GlobalString("root"), cgm) } func getContainer(context *cli.Context) (libcontainer.Container, error) { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/process.go b/Godeps/_workspace/src/github.com/docker/libcontainer/process.go index 82fcff8c..7902d08c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/process.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/process.go @@ -23,7 +23,7 @@ type Process struct { Env []string // User will set the uid and gid of the executing process running inside the container - // local to the contaienr's user and group configuration. + // local to the container's user and group configuration. User string // Cwd will change the processes current working directory inside the container's rootfs. @@ -38,11 +38,14 @@ type Process struct { // Stderr is a pointer to a writer which receives the standard error stream. Stderr io.Writer + // ExtraFiles specifies additional open files to be inherited by the container + ExtraFiles []*os.File + // consolePath is the path to the console allocated to the container. consolePath string // 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 capabilities not specified will be dropped from the processes capability mask Capabilities []string ops processOperations diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/process_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/process_linux.go index 1c74b654..66411a8a 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/process_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/process_linux.go @@ -119,6 +119,9 @@ func (p *setnsProcess) execSetns() error { // terminate sends a SIGKILL to the forked process for the setns routine then waits to // avoid the process becomming a zombie. func (p *setnsProcess) terminate() error { + if p.cmd.Process == nil { + return nil + } err := p.cmd.Process.Kill() if _, werr := p.wait(); err == nil { err = werr diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/rootfs_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/rootfs_linux.go index ab1a9a5f..d8c61e97 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/rootfs_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/rootfs_linux.go @@ -6,11 +6,14 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" + "path" "path/filepath" "strings" "syscall" "time" + "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/label" ) @@ -24,9 +27,20 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) { return newSystemError(err) } for _, m := range config.Mounts { + for _, precmd := range m.PremountCmds { + if err := mountCmd(precmd); err != nil { + return newSystemError(err) + } + } if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil { return newSystemError(err) } + + for _, postcmd := range m.PostmountCmds { + if err := mountCmd(postcmd); err != nil { + return newSystemError(err) + } + } } if err := createDevices(config); err != nil { return newSystemError(err) @@ -62,6 +76,18 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) { return nil } +func mountCmd(cmd configs.Command) error { + + command := exec.Command(cmd.Path, cmd.Args[:]...) + command.Env = cmd.Env + command.Dir = cmd.Dir + if out, err := command.CombinedOutput(); err != nil { + return fmt.Errorf("%#v failed: %s: %v", cmd, string(out), err) + } + + return nil +} + func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { var ( dest = m.Destination @@ -72,11 +98,19 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { } switch m.Device { - case "proc", "mqueue", "sysfs": + case "proc", "sysfs": if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) { return err } return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), "") + case "mqueue": + if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) { + return err + } + if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), ""); err != nil { + return err + } + return label.SetFileLabel(dest, mountLabel) case "tmpfs": stat, err := os.Stat(dest) if err != nil { @@ -126,6 +160,37 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { return err } } + case "cgroup": + mounts, err := cgroups.GetCgroupMounts() + if err != nil { + return err + } + var binds []*configs.Mount + for _, mm := range mounts { + dir, err := mm.GetThisCgroupDir() + if err != nil { + return err + } + binds = append(binds, &configs.Mount{ + Device: "bind", + Source: filepath.Join(mm.Mountpoint, dir), + Destination: filepath.Join(m.Destination, strings.Join(mm.Subsystems, ",")), + Flags: syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY, + }) + } + tmpfs := &configs.Mount{ + Device: "tmpfs", + Destination: m.Destination, + Flags: syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV, + } + if err := mountToRootfs(tmpfs, rootfs, mountLabel); err != nil { + return err + } + for _, b := range binds { + if err := mountToRootfs(b, rootfs, mountLabel); err != nil { + return err + } + } default: return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination) } @@ -240,9 +305,9 @@ func mknodDevice(dest string, node *configs.Device) error { } func prepareRoot(config *configs.Config) error { - flag := syscall.MS_PRIVATE | syscall.MS_REC - if config.NoPivotRoot { - flag = syscall.MS_SLAVE | syscall.MS_REC + flag := syscall.MS_SLAVE | syscall.MS_REC + if config.Privatefs { + flag = syscall.MS_PRIVATE | syscall.MS_REC } if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil { return err @@ -355,3 +420,10 @@ func maskFile(path string) error { } return nil } + +// writeSystemProperty writes the value to a path under /proc/sys as determined from the key. +// For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward. +func writeSystemProperty(key, value string) error { + keyPath := strings.Replace(key, ".", "/", -1) + return ioutil.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0644) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/standard_init_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/standard_init_linux.go index 29619d3c..251c09f6 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/standard_init_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/standard_init_linux.go @@ -13,7 +13,8 @@ import ( ) type linuxStandardInit struct { - config *initConfig + parentPid int + config *initConfig } func (l *linuxStandardInit) Init() error { @@ -63,6 +64,13 @@ func (l *linuxStandardInit) Init() error { if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil { return err } + + for key, value := range l.config.Config.SystemProperties { + if err := writeSystemProperty(key, value); err != nil { + return err + } + } + for _, path := range l.config.Config.ReadonlyPaths { if err := remountReadonly(path); err != nil { return err @@ -85,9 +93,10 @@ func (l *linuxStandardInit) Init() error { if err := pdeath.Restore(); err != nil { return err } - // Signal self if parent is already dead. Does nothing if running in a new - // PID namespace, as Getppid will always return 0. - if syscall.Getppid() == 1 { + // compare the parent from the inital start of the init process and make sure that it did not change. + // if the parent changes that means it died and we were reparened to something else so we should + // just kill ourself and not cause problems for someone else. + if syscall.Getppid() != l.parentPid { return syscall.Kill(syscall.Getpid(), syscall.SIGKILL) } return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/system/setns_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/system/setns_linux.go index 228e6ccd..a3c4cbb2 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/system/setns_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/system/setns_linux.go @@ -12,8 +12,10 @@ import ( // We are declaring the macro here because the SETNS syscall does not exist in th stdlib var setNsMap = map[string]uintptr{ "linux/386": 346, + "linux/arm64": 268, "linux/amd64": 308, - "linux/arm": 374, + "linux/arm": 375, + "linux/ppc": 350, "linux/ppc64": 350, "linux/ppc64le": 350, "linux/s390x": 339, diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/system/syscall_linux_64.go b/Godeps/_workspace/src/github.com/docker/libcontainer/system/syscall_linux_64.go index 6840c377..0816bf82 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/system/syscall_linux_64.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/system/syscall_linux_64.go @@ -1,4 +1,4 @@ -// +build linux,amd64 linux,ppc64 linux,ppc64le linux,s390x +// +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x package system diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/update-vendor.sh b/Godeps/_workspace/src/github.com/docker/libcontainer/update-vendor.sh index b68f5d46..ab471872 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/update-vendor.sh +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/update-vendor.sh @@ -43,7 +43,7 @@ clone() { clone git github.com/codegangsta/cli 1.1.0 clone git github.com/coreos/go-systemd v2 clone git github.com/godbus/dbus v2 -clone git github.com/Sirupsen/logrus v0.6.6 +clone git github.com/Sirupsen/logrus v0.7.3 clone git github.com/syndtr/gocapability 8e4cdcb # intentionally not vendoring Docker itself... that'd be a circle :) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md new file mode 100644 index 00000000..eb72bff9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md @@ -0,0 +1,7 @@ +# 0.7.3 + +formatter/\*: allow configuration of timestamp layout + +# 0.7.2 + +formatter/text: Add configuration option for time format (#158) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/README.md b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/README.md index e755e7c1..d55f9092 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/README.md +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/README.md @@ -37,11 +37,13 @@ attached, the output is compatible with the [logfmt](http://godoc.org/github.com/kr/logfmt) format: ```text -time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10 -time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122 -time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10 -time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9 -time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100 +time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 +time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10 +time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true +time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4 +time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009 +time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true +exit status 1 ``` #### Example @@ -82,7 +84,7 @@ func init() { // Use the Airbrake hook to report errors that have Error severity or above to // an exception tracker. You can create custom hooks, see the Hooks section. - log.AddHook(&logrus_airbrake.AirbrakeHook{}) + log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development")) // Output to stderr instead of stdout, could also be a file. log.SetOutput(os.Stderr) @@ -106,6 +108,16 @@ func main() { "omg": true, "number": 100, }).Fatal("The ice breaks!") + + // A common pattern is to re-use fields between logging statements by re-using + // the logrus.Entry returned from WithFields() + contextLogger := log.WithFields(log.Fields{ + "common": "this is a common field", + "other": "I also should be logged always", + }) + + contextLogger.Info("I'll be logged with common and other field") + contextLogger.Info("Me too") } ``` @@ -164,43 +176,8 @@ You can add hooks for logging levels. For example to send errors to an exception tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to multiple places simultaneously, e.g. syslog. -```go -// Not the real implementation of the Airbrake hook. Just a simple sample. -import ( - log "github.com/Sirupsen/logrus" -) - -func init() { - log.AddHook(new(AirbrakeHook)) -} - -type AirbrakeHook struct{} - -// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains -// the fields for the entry. See the Fields section of the README. -func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { - err := airbrake.Notify(entry.Data["error"].(error)) - if err != nil { - log.WithFields(log.Fields{ - "source": "airbrake", - "endpoint": airbrake.Endpoint, - }).Info("Failed to send error to Airbrake") - } - - return nil -} - -// `Levels()` returns a slice of `Levels` the hook is fired for. -func (hook *AirbrakeHook) Levels() []log.Level { - return []log.Level{ - log.ErrorLevel, - log.FatalLevel, - log.PanicLevel, - } -} -``` - -Logrus comes with built-in hooks. Add those, or your custom hook, in `init`: +Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in +`init`: ```go import ( @@ -211,7 +188,7 @@ import ( ) func init() { - log.AddHook(new(logrus_airbrake.AirbrakeHook)) + log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development")) hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") if err != nil { @@ -222,28 +199,18 @@ func init() { } ``` -* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) - Send errors to an exception tracking service compatible with the Airbrake API. - Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. -* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) - Send errors to the Papertrail hosted logging service via UDP. - -* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) - Send errors to remote syslog server. - Uses standard library `log/syslog` behind the scenes. - -* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus) - Send errors to a channel in hipchat. - -* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly) - Send logs to Loggly (https://www.loggly.com/) - -* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus) - Hook for Slack chat. - -* [`github.com/wercker/journalhook`](https://github.com/wercker/journalhook). - Hook for logging to `systemd-journald`. +| Hook | Description | +| ----- | ----------- | +| [Airbrake](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Send errors to an exception tracking service compatible with the Airbrake API. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | +| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. | +| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. | +| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | +| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. | +| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) | +| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. | +| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | +| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) | #### Level logging @@ -321,6 +288,11 @@ The built-in logging formatters are: field to `true`. To force no colored output even if there is a TTY set the `DisableColors` field to `true` * `logrus.JSONFormatter`. Logs fields as JSON. +* `logrus_logstash.LogstashFormatter`. Logs fields as Logstash Events (http://logstash.net). + + ```go + logrus.SetFormatter(&logrus_logstash.LogstashFormatter{Type: “application_name"}) + ``` Third party logging formatters: diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/examples/hook/hook.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/examples/hook/hook.go index 42e7a4c9..cb5759a3 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/examples/hook/hook.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/examples/hook/hook.go @@ -3,21 +3,16 @@ package main import ( "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus/hooks/airbrake" - "github.com/tobi/airbrake-go" ) var log = logrus.New() func init() { log.Formatter = new(logrus.TextFormatter) // default - log.Hooks.Add(new(logrus_airbrake.AirbrakeHook)) + log.Hooks.Add(airbrake.NewHook("https://example.com", "xyz", "development")) } func main() { - airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml" - airbrake.ApiKey = "whatever" - airbrake.Environment = "production" - log.WithFields(logrus.Fields{ "animal": "walrus", "size": 10, diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatter.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatter.go index 038ce9fd..104d689f 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatter.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatter.go @@ -1,5 +1,9 @@ package logrus +import "time" + +const DefaultTimestampFormat = time.RFC3339 + // The Formatter interface is used to implement a custom Formatter. It takes an // `Entry`. It exposes all the fields, including the default ones: // diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go new file mode 100644 index 00000000..8ea93ddf --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go @@ -0,0 +1,56 @@ +package logstash + +import ( + "encoding/json" + "fmt" + + "github.com/Sirupsen/logrus" +) + +// Formatter generates json in logstash format. +// Logstash site: http://logstash.net/ +type LogstashFormatter struct { + Type string // if not empty use for logstash type field. + + // TimestampFormat sets the format used for timestamps. + TimestampFormat string +} + +func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) { + entry.Data["@version"] = 1 + + if f.TimestampFormat == "" { + f.TimestampFormat = logrus.DefaultTimestampFormat + } + + entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat) + + // set message field + v, ok := entry.Data["message"] + if ok { + entry.Data["fields.message"] = v + } + entry.Data["message"] = entry.Message + + // set level field + v, ok = entry.Data["level"] + if ok { + entry.Data["fields.level"] = v + } + entry.Data["level"] = entry.Level.String() + + // set type field + if f.Type != "" { + v, ok = entry.Data["type"] + if ok { + entry.Data["fields.type"] = v + } + entry.Data["type"] = f.Type + } + + serialized, err := json.Marshal(entry.Data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go new file mode 100644 index 00000000..d8814a0e --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go @@ -0,0 +1,52 @@ +package logstash + +import ( + "bytes" + "encoding/json" + "github.com/Sirupsen/logrus" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLogstashFormatter(t *testing.T) { + assert := assert.New(t) + + lf := LogstashFormatter{Type: "abc"} + + fields := logrus.Fields{ + "message": "def", + "level": "ijk", + "type": "lmn", + "one": 1, + "pi": 3.14, + "bool": true, + } + + entry := logrus.WithFields(fields) + entry.Message = "msg" + entry.Level = logrus.InfoLevel + + b, _ := lf.Format(entry) + + var data map[string]interface{} + dec := json.NewDecoder(bytes.NewReader(b)) + dec.UseNumber() + dec.Decode(&data) + + // base fields + assert.Equal(json.Number("1"), data["@version"]) + assert.NotEmpty(data["@timestamp"]) + assert.Equal("abc", data["type"]) + assert.Equal("msg", data["message"]) + assert.Equal("info", data["level"]) + + // substituted fields + assert.Equal("def", data["fields.message"]) + assert.Equal("ijk", data["fields.level"]) + assert.Equal("lmn", data["fields.type"]) + + // formats + assert.Equal(json.Number("1"), data["one"]) + assert.Equal(json.Number("3.14"), data["pi"]) + assert.Equal(true, data["bool"]) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go index 75f4db15..b0502c33 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go @@ -1,51 +1,51 @@ -package logrus_airbrake +package airbrake import ( + "errors" + "fmt" + "github.com/Sirupsen/logrus" "github.com/tobi/airbrake-go" ) // AirbrakeHook to send exceptions to an exception-tracking service compatible -// with the Airbrake API. You must set: -// * airbrake.Endpoint -// * airbrake.ApiKey -// * airbrake.Environment -// -// Before using this hook, to send an error. Entries that trigger an Error, -// Fatal or Panic should now include an "error" field to send to Airbrake. -type AirbrakeHook struct{} +// with the Airbrake API. +type airbrakeHook struct { + APIKey string + Endpoint string + Environment string +} -func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { - if entry.Data["error"] == nil { - entry.Logger.WithFields(logrus.Fields{ - "source": "airbrake", - "endpoint": airbrake.Endpoint, - }).Warn("Exceptions sent to Airbrake must have an 'error' key with the error") - return nil +func NewHook(endpoint, apiKey, env string) *airbrakeHook { + return &airbrakeHook{ + APIKey: apiKey, + Endpoint: endpoint, + Environment: env, } +} +func (hook *airbrakeHook) Fire(entry *logrus.Entry) error { + airbrake.ApiKey = hook.APIKey + airbrake.Endpoint = hook.Endpoint + airbrake.Environment = hook.Environment + + var notifyErr error err, ok := entry.Data["error"].(error) - if !ok { - entry.Logger.WithFields(logrus.Fields{ - "source": "airbrake", - "endpoint": airbrake.Endpoint, - }).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`") - return nil + if ok { + notifyErr = err + } else { + notifyErr = errors.New(entry.Message) } - airErr := airbrake.Notify(err) + airErr := airbrake.Notify(notifyErr) if airErr != nil { - entry.Logger.WithFields(logrus.Fields{ - "source": "airbrake", - "endpoint": airbrake.Endpoint, - "error": airErr, - }).Warn("Failed to send error to Airbrake") + return fmt.Errorf("Failed to send error to Airbrake: %s", airErr) } return nil } -func (hook *AirbrakeHook) Levels() []logrus.Level { +func (hook *airbrakeHook) Levels() []logrus.Level { return []logrus.Level{ logrus.ErrorLevel, logrus.FatalLevel, diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake_test.go new file mode 100644 index 00000000..058a91e3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake_test.go @@ -0,0 +1,133 @@ +package airbrake + +import ( + "encoding/xml" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Sirupsen/logrus" +) + +type notice struct { + Error NoticeError `xml:"error"` +} +type NoticeError struct { + Class string `xml:"class"` + Message string `xml:"message"` +} + +type customErr struct { + msg string +} + +func (e *customErr) Error() string { + return e.msg +} + +const ( + testAPIKey = "abcxyz" + testEnv = "development" + expectedClass = "*airbrake.customErr" + expectedMsg = "foo" + unintendedMsg = "Airbrake will not see this string" +) + +var ( + noticeError = make(chan NoticeError, 1) +) + +// TestLogEntryMessageReceived checks if invoking Logrus' log.Error +// method causes an XML payload containing the log entry message is received +// by a HTTP server emulating an Airbrake-compatible endpoint. +func TestLogEntryMessageReceived(t *testing.T) { + log := logrus.New() + ts := startAirbrakeServer(t) + defer ts.Close() + + hook := NewHook(ts.URL, testAPIKey, "production") + log.Hooks.Add(hook) + + log.Error(expectedMsg) + + select { + case received := <-noticeError: + if received.Message != expectedMsg { + t.Errorf("Unexpected message received: %s", received.Message) + } + case <-time.After(time.Second): + t.Error("Timed out; no notice received by Airbrake API") + } +} + +// TestLogEntryMessageReceived confirms that, when passing an error type using +// logrus.Fields, a HTTP server emulating an Airbrake endpoint receives the +// error message returned by the Error() method on the error interface +// rather than the logrus.Entry.Message string. +func TestLogEntryWithErrorReceived(t *testing.T) { + log := logrus.New() + ts := startAirbrakeServer(t) + defer ts.Close() + + hook := NewHook(ts.URL, testAPIKey, "production") + log.Hooks.Add(hook) + + log.WithFields(logrus.Fields{ + "error": &customErr{expectedMsg}, + }).Error(unintendedMsg) + + select { + case received := <-noticeError: + if received.Message != expectedMsg { + t.Errorf("Unexpected message received: %s", received.Message) + } + if received.Class != expectedClass { + t.Errorf("Unexpected error class: %s", received.Class) + } + case <-time.After(time.Second): + t.Error("Timed out; no notice received by Airbrake API") + } +} + +// TestLogEntryWithNonErrorTypeNotReceived confirms that, when passing a +// non-error type using logrus.Fields, a HTTP server emulating an Airbrake +// endpoint receives the logrus.Entry.Message string. +// +// Only error types are supported when setting the 'error' field using +// logrus.WithFields(). +func TestLogEntryWithNonErrorTypeNotReceived(t *testing.T) { + log := logrus.New() + ts := startAirbrakeServer(t) + defer ts.Close() + + hook := NewHook(ts.URL, testAPIKey, "production") + log.Hooks.Add(hook) + + log.WithFields(logrus.Fields{ + "error": expectedMsg, + }).Error(unintendedMsg) + + select { + case received := <-noticeError: + if received.Message != unintendedMsg { + t.Errorf("Unexpected message received: %s", received.Message) + } + case <-time.After(time.Second): + t.Error("Timed out; no notice received by Airbrake API") + } +} + +func startAirbrakeServer(t *testing.T) *httptest.Server { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var notice notice + if err := xml.NewDecoder(r.Body).Decode(¬ice); err != nil { + t.Error(err) + } + r.Body.Close() + + noticeError <- notice.Error + })) + + return ts +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go new file mode 100644 index 00000000..d20a0f54 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go @@ -0,0 +1,68 @@ +package logrus_bugsnag + +import ( + "errors" + + "github.com/Sirupsen/logrus" + "github.com/bugsnag/bugsnag-go" +) + +type bugsnagHook struct{} + +// ErrBugsnagUnconfigured is returned if NewBugsnagHook is called before +// bugsnag.Configure. Bugsnag must be configured before the hook. +var ErrBugsnagUnconfigured = errors.New("bugsnag must be configured before installing this logrus hook") + +// ErrBugsnagSendFailed indicates that the hook failed to submit an error to +// bugsnag. The error was successfully generated, but `bugsnag.Notify()` +// failed. +type ErrBugsnagSendFailed struct { + err error +} + +func (e ErrBugsnagSendFailed) Error() string { + return "failed to send error to Bugsnag: " + e.err.Error() +} + +// NewBugsnagHook initializes a logrus hook which sends exceptions to an +// exception-tracking service compatible with the Bugsnag API. Before using +// this hook, you must call bugsnag.Configure(). The returned object should be +// registered with a log via `AddHook()` +// +// Entries that trigger an Error, Fatal or Panic should now include an "error" +// field to send to Bugsnag. +func NewBugsnagHook() (*bugsnagHook, error) { + if bugsnag.Config.APIKey == "" { + return nil, ErrBugsnagUnconfigured + } + return &bugsnagHook{}, nil +} + +// Fire forwards an error to Bugsnag. Given a logrus.Entry, it extracts the +// "error" field (or the Message if the error isn't present) and sends it off. +func (hook *bugsnagHook) Fire(entry *logrus.Entry) error { + var notifyErr error + err, ok := entry.Data["error"].(error) + if ok { + notifyErr = err + } else { + notifyErr = errors.New(entry.Message) + } + + bugsnagErr := bugsnag.Notify(notifyErr) + if bugsnagErr != nil { + return ErrBugsnagSendFailed{bugsnagErr} + } + + return nil +} + +// Levels enumerates the log levels on which the error should be forwarded to +// bugsnag: everything at or above the "Error" level. +func (hook *bugsnagHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, + } +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go new file mode 100644 index 00000000..e9ea298d --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go @@ -0,0 +1,64 @@ +package logrus_bugsnag + +import ( + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Sirupsen/logrus" + "github.com/bugsnag/bugsnag-go" +) + +type notice struct { + Events []struct { + Exceptions []struct { + Message string `json:"message"` + } `json:"exceptions"` + } `json:"events"` +} + +func TestNoticeReceived(t *testing.T) { + msg := make(chan string, 1) + expectedMsg := "foo" + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var notice notice + data, _ := ioutil.ReadAll(r.Body) + if err := json.Unmarshal(data, ¬ice); err != nil { + t.Error(err) + } + _ = r.Body.Close() + + msg <- notice.Events[0].Exceptions[0].Message + })) + defer ts.Close() + + hook := &bugsnagHook{} + + bugsnag.Configure(bugsnag.Configuration{ + Endpoint: ts.URL, + ReleaseStage: "production", + APIKey: "12345678901234567890123456789012", + Synchronous: true, + }) + + log := logrus.New() + log.Hooks.Add(hook) + + log.WithFields(logrus.Fields{ + "error": errors.New(expectedMsg), + }).Error("Bugsnag will not see this string") + + select { + case received := <-msg: + if received != expectedMsg { + t.Errorf("Unexpected message received: %s", received) + } + case <-time.After(time.Second): + t.Error("Timed out; no notice received by Bugsnag API") + } +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/json_formatter.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/json_formatter.go index 0e38a619..dcc4f1d9 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/json_formatter.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/json_formatter.go @@ -3,24 +3,32 @@ package logrus import ( "encoding/json" "fmt" - "time" ) -type JSONFormatter struct{} +type JSONFormatter struct { + // TimestampFormat sets the format used for marshaling timestamps. + TimestampFormat string +} func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data := make(Fields, len(entry.Data)+3) for k, v := range entry.Data { - // Otherwise errors are ignored by `encoding/json` - // https://github.com/Sirupsen/logrus/issues/137 - if err, ok := v.(error); ok { - data[k] = err.Error() - } else { + switch v := v.(type) { + case error: + // Otherwise errors are ignored by `encoding/json` + // https://github.com/Sirupsen/logrus/issues/137 + data[k] = v.Error() + default: data[k] = v } } prefixFieldClashes(data) - data["time"] = entry.Time.Format(time.RFC3339) + + if f.TimestampFormat == "" { + f.TimestampFormat = DefaultTimestampFormat + } + + data["time"] = entry.Time.Format(f.TimestampFormat) data["msg"] = entry.Message data["level"] = entry.Level.String() diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/logger.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/logger.go index b392e547..da928a37 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/logger.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/logger.go @@ -65,11 +65,15 @@ func (logger *Logger) WithFields(fields Fields) *Entry { } func (logger *Logger) Debugf(format string, args ...interface{}) { - NewEntry(logger).Debugf(format, args...) + if logger.Level >= DebugLevel { + NewEntry(logger).Debugf(format, args...) + } } func (logger *Logger) Infof(format string, args ...interface{}) { - NewEntry(logger).Infof(format, args...) + if logger.Level >= InfoLevel { + NewEntry(logger).Infof(format, args...) + } } func (logger *Logger) Printf(format string, args ...interface{}) { @@ -77,31 +81,45 @@ func (logger *Logger) Printf(format string, args ...interface{}) { } func (logger *Logger) Warnf(format string, args ...interface{}) { - NewEntry(logger).Warnf(format, args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warnf(format, args...) + } } func (logger *Logger) Warningf(format string, args ...interface{}) { - NewEntry(logger).Warnf(format, args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warnf(format, args...) + } } func (logger *Logger) Errorf(format string, args ...interface{}) { - NewEntry(logger).Errorf(format, args...) + if logger.Level >= ErrorLevel { + NewEntry(logger).Errorf(format, args...) + } } func (logger *Logger) Fatalf(format string, args ...interface{}) { - NewEntry(logger).Fatalf(format, args...) + if logger.Level >= FatalLevel { + NewEntry(logger).Fatalf(format, args...) + } } func (logger *Logger) Panicf(format string, args ...interface{}) { - NewEntry(logger).Panicf(format, args...) + if logger.Level >= PanicLevel { + NewEntry(logger).Panicf(format, args...) + } } func (logger *Logger) Debug(args ...interface{}) { - NewEntry(logger).Debug(args...) + if logger.Level >= DebugLevel { + NewEntry(logger).Debug(args...) + } } func (logger *Logger) Info(args ...interface{}) { - NewEntry(logger).Info(args...) + if logger.Level >= InfoLevel { + NewEntry(logger).Info(args...) + } } func (logger *Logger) Print(args ...interface{}) { @@ -109,31 +127,45 @@ func (logger *Logger) Print(args ...interface{}) { } func (logger *Logger) Warn(args ...interface{}) { - NewEntry(logger).Warn(args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warn(args...) + } } func (logger *Logger) Warning(args ...interface{}) { - NewEntry(logger).Warn(args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warn(args...) + } } func (logger *Logger) Error(args ...interface{}) { - NewEntry(logger).Error(args...) + if logger.Level >= ErrorLevel { + NewEntry(logger).Error(args...) + } } func (logger *Logger) Fatal(args ...interface{}) { - NewEntry(logger).Fatal(args...) + if logger.Level >= FatalLevel { + NewEntry(logger).Fatal(args...) + } } func (logger *Logger) Panic(args ...interface{}) { - NewEntry(logger).Panic(args...) + if logger.Level >= PanicLevel { + NewEntry(logger).Panic(args...) + } } func (logger *Logger) Debugln(args ...interface{}) { - NewEntry(logger).Debugln(args...) + if logger.Level >= DebugLevel { + NewEntry(logger).Debugln(args...) + } } func (logger *Logger) Infoln(args ...interface{}) { - NewEntry(logger).Infoln(args...) + if logger.Level >= InfoLevel { + NewEntry(logger).Infoln(args...) + } } func (logger *Logger) Println(args ...interface{}) { @@ -141,21 +173,31 @@ func (logger *Logger) Println(args ...interface{}) { } func (logger *Logger) Warnln(args ...interface{}) { - NewEntry(logger).Warnln(args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warnln(args...) + } } func (logger *Logger) Warningln(args ...interface{}) { - NewEntry(logger).Warnln(args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warnln(args...) + } } func (logger *Logger) Errorln(args ...interface{}) { - NewEntry(logger).Errorln(args...) + if logger.Level >= ErrorLevel { + NewEntry(logger).Errorln(args...) + } } func (logger *Logger) Fatalln(args ...interface{}) { - NewEntry(logger).Fatalln(args...) + if logger.Level >= FatalLevel { + NewEntry(logger).Fatalln(args...) + } } func (logger *Logger) Panicln(args ...interface{}) { - NewEntry(logger).Panicln(args...) + if logger.Level >= PanicLevel { + NewEntry(logger).Panicln(args...) + } } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go index d238bfa0..af609a53 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go @@ -1,4 +1,3 @@ - package logrus import "syscall" diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/text_formatter.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/text_formatter.go index 71dcb661..612417ff 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/text_formatter.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/text_formatter.go @@ -3,7 +3,6 @@ package logrus import ( "bytes" "fmt" - "regexp" "sort" "strings" "time" @@ -21,7 +20,6 @@ const ( var ( baseTimestamp time.Time isTerminal bool - noQuoteNeeded *regexp.Regexp ) func init() { @@ -48,6 +46,9 @@ type TextFormatter struct { // the time passed since beginning of execution. FullTimestamp bool + // TimestampFormat to use for display when a full timestamp is printed + TimestampFormat string + // The fields are sorted by default for a consistent output. For applications // that log extremely frequently and don't use the JSON formatter this may not // be desired. @@ -70,11 +71,14 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { isColored := (f.ForceColors || isTerminal) && !f.DisableColors + if f.TimestampFormat == "" { + f.TimestampFormat = DefaultTimestampFormat + } if isColored { f.printColored(b, entry, keys) } else { if !f.DisableTimestamp { - f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339)) + f.appendKeyValue(b, "time", entry.Time.Format(f.TimestampFormat)) } f.appendKeyValue(b, "level", entry.Level.String()) f.appendKeyValue(b, "msg", entry.Message) @@ -105,7 +109,7 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin if !f.FullTimestamp { fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) } else { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(time.RFC3339), entry.Message) + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(f.TimestampFormat), entry.Message) } for _, k := range keys { v := entry.Data[k] diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go index 28a94990..e25a44f6 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go @@ -3,8 +3,8 @@ package logrus import ( "bytes" "errors" - "testing" + "time" ) func TestQuoting(t *testing.T) { @@ -33,5 +33,29 @@ func TestQuoting(t *testing.T) { checkQuoting(true, errors.New("invalid argument")) } +func TestTimestampFormat(t *testing.T) { + checkTimeStr := func(format string) { + customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format} + customStr, _ := customFormatter.Format(WithField("test", "test")) + timeStart := bytes.Index(customStr, ([]byte)("time=")) + timeEnd := bytes.Index(customStr, ([]byte)("level=")) + timeStr := customStr[timeStart+5 : timeEnd-1] + if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' { + timeStr = timeStr[1 : len(timeStr)-1] + } + if format == "" { + format = time.RFC3339 + } + _, e := time.Parse(format, (string)(timeStr)) + if e != nil { + t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e) + } + } + + checkTimeStr("2006-01-02T15:04:05.000000000Z07:00") + checkTimeStr("Mon Jan _2 15:04:05 2006") + checkTimeStr("") +} + // TODO add tests for sorting etc., this requires a parser for the text // formatter output. diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/writer.go b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/writer.go index 90d3e01b..1e30b1c7 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/writer.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/vendor/src/github.com/Sirupsen/logrus/writer.go @@ -6,7 +6,7 @@ import ( "runtime" ) -func (logger *Logger) Writer() (*io.PipeWriter) { +func (logger *Logger) Writer() *io.PipeWriter { reader, writer := io.Pipe() go logger.writerScanner(reader) From 96c025a60aa7718ffc70946819cc37fd41aae9e2 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Tue, 5 May 2015 14:31:25 -0700 Subject: [PATCH 2/2] Update gocapability --- Godeps/Godeps.json | 2 +- .../gocapability/capability/capability.go | 3 +- .../capability/capability_linux.go | 46 +++++++++- .../syndtr/gocapability/capability/enum.go | 89 ++++++++++--------- 4 files changed, 95 insertions(+), 45 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index fb082c55..7446650c 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -178,7 +178,7 @@ }, { "ImportPath": "github.com/syndtr/gocapability/capability", - "Rev": "3c85049eaeb429febe7788d9c7aac42322a377fe" + "Rev": "8e4cdcb3c22b40d5e330ade0b68cb2e2a3cf6f98" }, { "ImportPath": "golang.org/x/exp/inotify", diff --git a/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/capability.go b/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/capability.go index 9df3b415..c13f4e52 100644 --- a/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/capability.go +++ b/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/capability.go @@ -60,7 +60,8 @@ type Capabilities interface { Apply(kind CapType) error } -// NewPid create new initialized Capabilities object for given pid. +// NewPid create new initialized Capabilities object for given pid when it +// is nonzero, or for the current pid if pid is 0 func NewPid(pid int) (Capabilities, error) { return newPid(pid) } diff --git a/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/capability_linux.go b/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/capability_linux.go index c5f335f7..3dfcd398 100644 --- a/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/capability_linux.go +++ b/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/capability_linux.go @@ -24,12 +24,46 @@ const ( linuxCapVer3 = 0x20080522 ) -var capVers uint32 +var ( + capVers uint32 + capLastCap Cap +) func init() { var hdr capHeader capget(&hdr, nil) capVers = hdr.version + + if initLastCap() == nil { + CAP_LAST_CAP = capLastCap + if capLastCap > 31 { + capUpperMask = (uint32(1) << (uint(capLastCap) - 31)) - 1 + } else { + capUpperMask = 0 + } + } +} + +func initLastCap() error { + if capLastCap != 0 { + return nil + } + + f, err := os.Open("/proc/sys/kernel/cap_last_cap") + if err != nil { + return err + } + defer f.Close() + + var b []byte = make([]byte, 11) + _, err = f.Read(b) + if err != nil { + return err + } + + fmt.Sscanf(string(b), "%d", &capLastCap) + + return nil } func mkStringCap(c Capabilities, which CapType) (ret string) { @@ -351,7 +385,15 @@ func (c *capsV3) Load() (err error) { return } - f, err := os.Open(fmt.Sprintf("/proc/%d/status", c.hdr.pid)) + var status_path string + + if c.hdr.pid == 0 { + status_path = fmt.Sprintf("/proc/self/status") + } else { + status_path = fmt.Sprintf("/proc/%d/status", c.hdr.pid) + } + + f, err := os.Open(status_path) if err != nil { return } diff --git a/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/enum.go b/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/enum.go index e2900a4e..bff756a5 100644 --- a/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/enum.go +++ b/Godeps/_workspace/src/github.com/syndtr/gocapability/capability/enum.go @@ -112,32 +112,33 @@ func (c Cap) String() string { return "wake_alarm" case CAP_BLOCK_SUSPEND: return "block_suspend" + case CAP_AUDIT_READ: + return "audit_read" } return "unknown" } +// POSIX-draft defined capabilities. const ( - // POSIX-draft defined capabilities. - // In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this // overrides the restriction of changing file ownership and group // ownership. - CAP_CHOWN Cap = 0 + CAP_CHOWN = Cap(0) // Override all DAC access, including ACL execute access if // [_POSIX_ACL] is defined. Excluding DAC access covered by // CAP_LINUX_IMMUTABLE. - CAP_DAC_OVERRIDE Cap = 1 + CAP_DAC_OVERRIDE = Cap(1) // Overrides all DAC restrictions regarding read and search on files // and directories, including ACL restrictions if [_POSIX_ACL] is // defined. Excluding DAC access covered by CAP_LINUX_IMMUTABLE. - CAP_DAC_READ_SEARCH Cap = 2 + CAP_DAC_READ_SEARCH = Cap(2) // Overrides all restrictions about allowed operations on files, where // file owner ID must be equal to the user ID, except where CAP_FSETID // is applicable. It doesn't override MAC and DAC restrictions. - CAP_FOWNER Cap = 3 + CAP_FOWNER = Cap(3) // Overrides the following restrictions that the effective user ID // shall match the file owner ID when setting the S_ISUID and S_ISGID @@ -145,21 +146,21 @@ const ( // supplementary group IDs) shall match the file owner ID when setting // the S_ISGID bit on that file; that the S_ISUID and S_ISGID bits are // cleared on successful return from chown(2) (not implemented). - CAP_FSETID Cap = 4 + CAP_FSETID = Cap(4) // Overrides the restriction that the real or effective user ID of a // process sending a signal must match the real or effective user ID // of the process receiving the signal. - CAP_KILL Cap = 5 + CAP_KILL = Cap(5) // Allows setgid(2) manipulation // Allows setgroups(2) // Allows forged gids on socket credentials passing. - CAP_SETGID Cap = 6 + CAP_SETGID = Cap(6) // Allows set*uid(2) manipulation (including fsuid). // Allows forged pids on socket credentials passing. - CAP_SETUID Cap = 7 + CAP_SETUID = Cap(7) // Linux-specific capabilities @@ -171,17 +172,17 @@ const ( // to the current process' inheritable set // Allow taking bits out of capability bounding set // Allow modification of the securebits for a process - CAP_SETPCAP Cap = 8 + CAP_SETPCAP = Cap(8) // Allow modification of S_IMMUTABLE and S_APPEND file attributes - CAP_LINUX_IMMUTABLE Cap = 9 + CAP_LINUX_IMMUTABLE = Cap(9) // Allows binding to TCP/UDP sockets below 1024 // Allows binding to ATM VCIs below 32 - CAP_NET_BIND_SERVICE Cap = 10 + CAP_NET_BIND_SERVICE = Cap(10) // Allow broadcasting, listen to multicast - CAP_NET_BROADCAST Cap = 11 + CAP_NET_BROADCAST = Cap(11) // Allow interface configuration // Allow administration of IP firewall, masquerading and accounting @@ -196,36 +197,36 @@ const ( // Allow multicasting // Allow read/write of device-specific registers // Allow activation of ATM control sockets - CAP_NET_ADMIN Cap = 12 + CAP_NET_ADMIN = Cap(12) // Allow use of RAW sockets // Allow use of PACKET sockets // Allow binding to any address for transparent proxying (also via NET_ADMIN) - CAP_NET_RAW Cap = 13 + CAP_NET_RAW = Cap(13) // Allow locking of shared memory segments // Allow mlock and mlockall (which doesn't really have anything to do // with IPC) - CAP_IPC_LOCK Cap = 14 + CAP_IPC_LOCK = Cap(14) // Override IPC ownership checks - CAP_IPC_OWNER Cap = 15 + CAP_IPC_OWNER = Cap(15) // Insert and remove kernel modules - modify kernel without limit - CAP_SYS_MODULE Cap = 16 + CAP_SYS_MODULE = Cap(16) // Allow ioperm/iopl access // Allow sending USB messages to any device via /proc/bus/usb - CAP_SYS_RAWIO Cap = 17 + CAP_SYS_RAWIO = Cap(17) // Allow use of chroot() - CAP_SYS_CHROOT Cap = 18 + CAP_SYS_CHROOT = Cap(18) // Allow ptrace() of any process - CAP_SYS_PTRACE Cap = 19 + CAP_SYS_PTRACE = Cap(19) // Allow configuration of process accounting - CAP_SYS_PACCT Cap = 20 + CAP_SYS_PACCT = Cap(20) // Allow configuration of the secure attention key // Allow administration of the random device @@ -263,10 +264,10 @@ const ( // arbitrary SCSI commands // Allow setting encryption key on loopback filesystem // Allow setting zone reclaim policy - CAP_SYS_ADMIN Cap = 21 + CAP_SYS_ADMIN = Cap(21) // Allow use of reboot() - CAP_SYS_BOOT Cap = 22 + CAP_SYS_BOOT = Cap(22) // Allow raising priority and setting priority on other (different // UID) processes @@ -274,7 +275,7 @@ const ( // processes and setting the scheduling algorithm used by another // process. // Allow setting cpu affinity on other processes - CAP_SYS_NICE Cap = 23 + CAP_SYS_NICE = Cap(23) // Override resource limits. Set resource limits. // Override quota limits. @@ -287,33 +288,33 @@ const ( // Allow more than 64hz interrupts from the real-time clock // Override max number of consoles on console allocation // Override max number of keymaps - CAP_SYS_RESOURCE Cap = 24 + CAP_SYS_RESOURCE = Cap(24) // Allow manipulation of system clock // Allow irix_stime on mips // Allow setting the real-time clock - CAP_SYS_TIME Cap = 25 + CAP_SYS_TIME = Cap(25) // Allow configuration of tty devices // Allow vhangup() of tty - CAP_SYS_TTY_CONFIG Cap = 26 + CAP_SYS_TTY_CONFIG = Cap(26) // Allow the privileged aspects of mknod() - CAP_MKNOD Cap = 27 + CAP_MKNOD = Cap(27) // Allow taking of leases on files - CAP_LEASE Cap = 28 + CAP_LEASE = Cap(28) - CAP_AUDIT_WRITE Cap = 29 - CAP_AUDIT_CONTROL Cap = 30 - CAP_SETFCAP Cap = 31 + CAP_AUDIT_WRITE = Cap(29) + CAP_AUDIT_CONTROL = Cap(30) + CAP_SETFCAP = Cap(31) // Override MAC access. // The base kernel enforces no MAC policy. // An LSM may enforce a MAC policy, and if it does and it chooses // to implement capability based overrides of that policy, this is // the capability it should use to do so. - CAP_MAC_OVERRIDE Cap = 32 + CAP_MAC_OVERRIDE = Cap(32) // Allow MAC configuration or state changes. // The base kernel requires no MAC configuration. @@ -321,18 +322,24 @@ const ( // to implement capability based checks on modifications to that // policy or the data required to maintain it, this is the // capability it should use to do so. - CAP_MAC_ADMIN Cap = 33 + CAP_MAC_ADMIN = Cap(33) // Allow configuring the kernel's syslog (printk behaviour) - CAP_SYSLOG Cap = 34 + CAP_SYSLOG = Cap(34) // Allow triggering something that will wake the system - CAP_WAKE_ALARM Cap = 35 + CAP_WAKE_ALARM = Cap(35) // Allow preventing system suspends - CAP_BLOCK_SUSPEND Cap = 36 + CAP_BLOCK_SUSPEND = Cap(36) - CAP_LAST_CAP = CAP_BLOCK_SUSPEND + // Allow reading audit messages from the kernel + CAP_AUDIT_READ = Cap(37) ) -const capUpperMask = (uint32(1) << (uint(CAP_LAST_CAP) - 31)) - 1 +var ( + // Highest valid capability of the running kernel. + CAP_LAST_CAP = Cap(63) + + capUpperMask = ^uint32(0) +)