代码拉取完成,页面将自动刷新
同步操作将从 src-openEuler/docker 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
From 962e669ab1b1d3545faca5b668ca9d1be0d3b786 Mon Sep 17 00:00:00 2001
From: jingrui <jingrui@huawei.com>
Date: Mon, 21 Jan 2019 21:25:33 +0800
Subject: [PATCH 067/111] pause: fix build missing dep packages
reason: update vendor for libcontainer/cgroup dependency.
checkout from runc-1.0.0:
eff62015 runc: support specify umask
- coreos/go-systemd/util
- opencontainers/runc/libcontainer/cgroups/fs
- opencontainers/runc/libcontainer/cgroups/systemd
- opencontainers/runc/libcontainer/utils
files modified support FilesLimit:
- opencontainers/runc/libcontainer/cgroups/stats.go
- opencontainers/runc/libcontainer/configs/cgroup_linux.go
Change-Id: I794f0bf9be87c6068788f866555c346ca2372c02
Signed-off-by: jingrui <jingrui@huawei.com>
---
.../github.com/coreos/go-systemd/Checklist | 2 +
.../github.com/coreos/go-systemd/util/util.go | 33 +
.../runc/libcontainer/Checklist | 4 +
.../runc/libcontainer/cgroups/fs/apply_raw.go | 361 ++++++++++
.../libcontainer/cgroups/fs/apply_raw_test.go | 272 ++++++++
.../runc/libcontainer/cgroups/fs/blkio.go | 237 +++++++
.../libcontainer/cgroups/fs/blkio_test.go | 636 ++++++++++++++++++
.../runc/libcontainer/cgroups/fs/cpu.go | 125 ++++
.../runc/libcontainer/cgroups/fs/cpu_test.go | 209 ++++++
.../runc/libcontainer/cgroups/fs/cpuacct.go | 121 ++++
.../runc/libcontainer/cgroups/fs/cpuset.go | 183 +++++
.../libcontainer/cgroups/fs/cpuset_test.go | 65 ++
.../runc/libcontainer/cgroups/fs/devices.go | 80 +++
.../libcontainer/cgroups/fs/devices_test.go | 98 +++
.../runc/libcontainer/cgroups/fs/files.go | 72 ++
.../runc/libcontainer/cgroups/fs/freezer.go | 61 ++
.../libcontainer/cgroups/fs/freezer_test.go | 47 ++
.../libcontainer/cgroups/fs/fs_unsupported.go | 3 +
.../runc/libcontainer/cgroups/fs/hugetlb.go | 71 ++
.../libcontainer/cgroups/fs/hugetlb_test.go | 154 +++++
.../runc/libcontainer/cgroups/fs/memory.go | 301 +++++++++
.../libcontainer/cgroups/fs/memory_test.go | 453 +++++++++++++
.../runc/libcontainer/cgroups/fs/name.go | 40 ++
.../runc/libcontainer/cgroups/fs/net_cls.go | 43 ++
.../libcontainer/cgroups/fs/net_cls_test.go | 39 ++
.../runc/libcontainer/cgroups/fs/net_prio.go | 41 ++
.../libcontainer/cgroups/fs/net_prio_test.go | 38 ++
.../libcontainer/cgroups/fs/perf_event.go | 35 +
.../runc/libcontainer/cgroups/fs/pids.go | 73 ++
.../runc/libcontainer/cgroups/fs/pids_test.go | 111 +++
.../cgroups/fs/stats_util_test.go | 117 ++++
.../runc/libcontainer/cgroups/fs/util_test.go | 67 ++
.../runc/libcontainer/cgroups/fs/utils.go | 78 +++
.../libcontainer/cgroups/fs/utils_test.go | 97 +++
.../libcontainer/cgroups/rootless/rootless.go | 128 ++++
.../runc/libcontainer/cgroups/stats.go | 8 +
.../cgroups/systemd/apply_nosystemd.go | 55 ++
.../cgroups/systemd/apply_systemd.go | 556 +++++++++++++++
.../runc/libcontainer/configs/cgroup_linux.go | 3 +
.../runc/libcontainer/utils/cmsg.go | 95 +++
.../runc/libcontainer/utils/utils.go | 126 ++++
.../runc/libcontainer/utils/utils_unix.go | 43 ++
42 files changed, 5381 insertions(+)
create mode 100644 components/engine/vendor/github.com/coreos/go-systemd/Checklist
create mode 100644 components/engine/vendor/github.com/coreos/go-systemd/util/util.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/Checklist
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/files.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs_unsupported.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/rootless.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go
diff --git a/components/engine/vendor/github.com/coreos/go-systemd/Checklist b/components/engine/vendor/github.com/coreos/go-systemd/Checklist
new file mode 100644
index 0000000000..c231fc1636
--- /dev/null
+++ b/components/engine/vendor/github.com/coreos/go-systemd/Checklist
@@ -0,0 +1,2 @@
+Add these packages from go-systemd v4 for moving Pause handling from runc to dockerd
+- github.com/coreos/go-systemd/util
\ No newline at end of file
diff --git a/components/engine/vendor/github.com/coreos/go-systemd/util/util.go b/components/engine/vendor/github.com/coreos/go-systemd/util/util.go
new file mode 100644
index 0000000000..33832a1ed4
--- /dev/null
+++ b/components/engine/vendor/github.com/coreos/go-systemd/util/util.go
@@ -0,0 +1,33 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package util contains utility functions related to systemd that applications
+// can use to check things like whether systemd is running.
+package util
+
+import (
+ "os"
+)
+
+// IsRunningSystemd checks whether the host was booted with systemd as its init
+// system. This functions similar to systemd's `sd_booted(3)`: internally, it
+// checks whether /run/systemd/system/ exists and is a directory.
+// http://www.freedesktop.org/software/systemd/man/sd_booted.html
+func IsRunningSystemd() bool {
+ fi, err := os.Lstat("/run/systemd/system")
+ if err != nil {
+ return false
+ }
+ return fi.IsDir()
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/Checklist b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/Checklist
new file mode 100644
index 0000000000..d0900fc618
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/Checklist
@@ -0,0 +1,4 @@
+Add these packages for moving Pause handling from runc to dockerd
+- github.com/opencontainers/runc/libcontainer/cgroups/fs
+- github.com/opencontainers/runc/libcontainer/cgroups/systemd
+- github.com/opencontainers/runc/libcontainer/utils
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go
new file mode 100644
index 0000000000..1bf59a47be
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go
@@ -0,0 +1,361 @@
+// +build linux
+
+package fs
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
+)
+
+var (
+ subsystems = subsystemSet{
+ &CpusetGroup{},
+ &DevicesGroup{},
+ &MemoryGroup{},
+ &CpuGroup{},
+ &CpuacctGroup{},
+ &PidsGroup{},
+ &FilesGroup{},
+ &BlkioGroup{},
+ &HugetlbGroup{},
+ &NetClsGroup{},
+ &NetPrioGroup{},
+ &PerfEventGroup{},
+ &FreezerGroup{},
+ &NameGroup{GroupName: "name=systemd", Join: true},
+ }
+ HugePageSizes, _ = cgroups.GetHugePageSize()
+)
+
+var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
+
+type subsystemSet []subsystem
+
+func (s subsystemSet) Get(name string) (subsystem, error) {
+ for _, ss := range s {
+ if ss.Name() == name {
+ return ss, nil
+ }
+ }
+ return nil, errSubsystemDoesNotExist
+}
+
+type subsystem interface {
+ // Name returns the name of the subsystem.
+ Name() string
+ // Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
+ GetStats(path string, stats *cgroups.Stats) error
+ // Removes the cgroup represented by 'cgroupData'.
+ Remove(*cgroupData) error
+ // Creates and joins the cgroup represented by 'cgroupData'.
+ Apply(*cgroupData) error
+ // Set the cgroup represented by cgroup.
+ Set(path string, cgroup *configs.Cgroup) error
+}
+
+type Manager struct {
+ mu sync.Mutex
+ Cgroups *configs.Cgroup
+ Paths map[string]string
+}
+
+// The absolute path to the root of the cgroup hierarchies.
+var cgroupRootLock sync.Mutex
+var cgroupRoot string
+
+// Gets the cgroupRoot.
+func getCgroupRoot() (string, error) {
+ cgroupRootLock.Lock()
+ defer cgroupRootLock.Unlock()
+
+ if cgroupRoot != "" {
+ return cgroupRoot, nil
+ }
+
+ root, err := cgroups.FindCgroupMountpointDir()
+ if err != nil {
+ return "", err
+ }
+
+ if _, err := os.Stat(root); err != nil {
+ return "", err
+ }
+
+ cgroupRoot = root
+ return cgroupRoot, nil
+}
+
+type cgroupData struct {
+ root string
+ innerPath string
+ config *configs.Cgroup
+ pid int
+}
+
+func (m *Manager) Apply(pid int) (err error) {
+ if m.Cgroups == nil {
+ return nil
+ }
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ var c = m.Cgroups
+
+ d, err := getCgroupData(m.Cgroups, pid)
+ if err != nil {
+ return err
+ }
+
+ m.Paths = make(map[string]string)
+ if c.Paths != nil {
+ for name, path := range c.Paths {
+ _, err := d.path(name)
+ if err != nil {
+ if cgroups.IsNotFound(err) {
+ continue
+ }
+ return err
+ }
+ m.Paths[name] = path
+ }
+ return cgroups.EnterPid(m.Paths, pid)
+ }
+
+ for _, sys := range subsystems {
+ // TODO: Apply should, ideally, be reentrant or be broken up into a separate
+ // create and join phase so that the cgroup hierarchy for a container can be
+ // created then join consists of writing the process pids to cgroup.procs
+ p, err := d.path(sys.Name())
+ if err != nil {
+ // The non-presence of the devices subsystem is
+ // considered fatal for security reasons.
+ if cgroups.IsNotFound(err) && sys.Name() != "devices" {
+ continue
+ }
+ return err
+ }
+ m.Paths[sys.Name()] = p
+
+ if err := sys.Apply(d); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (m *Manager) Destroy() error {
+ if m.Cgroups == nil || m.Cgroups.Paths != nil {
+ return nil
+ }
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ if err := cgroups.RemovePaths(m.Paths); err != nil {
+ return err
+ }
+ m.Paths = make(map[string]string)
+ return nil
+}
+
+func (m *Manager) GetPaths() map[string]string {
+ m.mu.Lock()
+ paths := m.Paths
+ m.mu.Unlock()
+ return paths
+}
+
+func (m *Manager) GetStats() (*cgroups.Stats, error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ stats := cgroups.NewStats()
+ for name, path := range m.Paths {
+ sys, err := subsystems.Get(name)
+ if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
+ continue
+ }
+ if err := sys.GetStats(path, stats); err != nil {
+ return nil, err
+ }
+ }
+ return stats, nil
+}
+
+func (m *Manager) Set(container *configs.Config) error {
+ // If Paths are set, then we are just joining cgroups paths
+ // and there is no need to set any values.
+ if m.Cgroups.Paths != nil {
+ return nil
+ }
+
+ paths := m.GetPaths()
+ for _, sys := range subsystems {
+ path := paths[sys.Name()]
+ if err := sys.Set(path, container.Cgroups); err != nil {
+ return err
+ }
+ }
+
+ if m.Paths["cpu"] != "" {
+ if err := CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); 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 {
+ paths := m.GetPaths()
+ dir := paths["freezer"]
+ prevState := m.Cgroups.Resources.Freezer
+ m.Cgroups.Resources.Freezer = state
+ freezer, err := subsystems.Get("freezer")
+ if err != nil {
+ return err
+ }
+ err = freezer.Set(dir, m.Cgroups)
+ if err != nil {
+ m.Cgroups.Resources.Freezer = prevState
+ return err
+ }
+ return nil
+}
+
+func (m *Manager) GetPids() ([]int, error) {
+ paths := m.GetPaths()
+ return cgroups.GetPids(paths["devices"])
+}
+
+func (m *Manager) GetAllPids() ([]int, error) {
+ paths := m.GetPaths()
+ return cgroups.GetAllPids(paths["devices"])
+}
+
+func getCgroupData(c *configs.Cgroup, pid int) (*cgroupData, error) {
+ root, err := getCgroupRoot()
+ if err != nil {
+ return nil, err
+ }
+
+ if (c.Name != "" || c.Parent != "") && c.Path != "" {
+ return nil, fmt.Errorf("cgroup: either Path or Name and Parent should be used")
+ }
+
+ // XXX: Do not remove this code. Path safety is important! -- cyphar
+ cgPath := libcontainerUtils.CleanPath(c.Path)
+ cgParent := libcontainerUtils.CleanPath(c.Parent)
+ cgName := libcontainerUtils.CleanPath(c.Name)
+
+ innerPath := cgPath
+ if innerPath == "" {
+ innerPath = filepath.Join(cgParent, cgName)
+ }
+
+ return &cgroupData{
+ root: root,
+ innerPath: innerPath,
+ config: c,
+ pid: pid,
+ }, nil
+}
+
+func (raw *cgroupData) path(subsystem string) (string, error) {
+ mnt, err := cgroups.FindCgroupMountpoint(subsystem)
+ // If we didn't mount the subsystem, there is no point we make the path.
+ if err != nil {
+ return "", err
+ }
+
+ // If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
+ if filepath.IsAbs(raw.innerPath) {
+ // Sometimes subsystems can be mounted together as 'cpu,cpuacct'.
+ return filepath.Join(raw.root, filepath.Base(mnt), raw.innerPath), nil
+ }
+
+ // Use GetOwnCgroupPath instead of GetInitCgroupPath, because the creating
+ // process could in container and shared pid namespace with host, and
+ // /proc/1/cgroup could point to whole other world of cgroups.
+ parentPath, err := cgroups.GetOwnCgroupPath(subsystem)
+ if err != nil {
+ return "", err
+ }
+
+ return filepath.Join(parentPath, raw.innerPath), nil
+}
+
+func (raw *cgroupData) join(subsystem string) (string, error) {
+ path, err := raw.path(subsystem)
+ if err != nil {
+ return "", err
+ }
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return "", err
+ }
+ if err := cgroups.WriteCgroupProc(path, raw.pid); err != nil {
+ return "", err
+ }
+ return path, nil
+}
+
+func writeFile(dir, file, data string) error {
+ // Normally dir should not be empty, one case is that cgroup subsystem
+ // is not mounted, we will get empty dir, and we want it fail here.
+ if dir == "" {
+ return fmt.Errorf("no such directory for %s", file)
+ }
+ if err := ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700); err != nil {
+ return fmt.Errorf("failed to write %v to %v: %v", data, file, err)
+ }
+ return nil
+}
+
+func readFile(dir, file string) (string, error) {
+ data, err := ioutil.ReadFile(filepath.Join(dir, file))
+ return string(data), err
+}
+
+func removePath(p string, err error) error {
+ if err != nil {
+ return err
+ }
+ if p != "" {
+ return os.RemoveAll(p)
+ }
+ return nil
+}
+
+func CheckCpushares(path string, c uint64) error {
+ var cpuShares uint64
+
+ if c == 0 {
+ return nil
+ }
+
+ 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 > 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/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw_test.go
new file mode 100644
index 0000000000..ba4e9e543c
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw_test.go
@@ -0,0 +1,272 @@
+// +build linux
+
+package fs
+
+import (
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func TestInvalidCgroupPath(t *testing.T) {
+ root, err := getCgroupRoot()
+ if err != nil {
+ t.Errorf("couldn't get cgroup root: %v", err)
+ }
+
+ config := &configs.Cgroup{
+ Path: "../../../../../../../../../../some/path",
+ }
+
+ data, err := getCgroupData(config, 0)
+ if err != nil {
+ t.Errorf("couldn't get cgroup data: %v", err)
+ }
+
+ // Make sure the final innerPath doesn't go outside the cgroup mountpoint.
+ if strings.HasPrefix(data.innerPath, "..") {
+ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
+ }
+
+ // Double-check, using an actual cgroup.
+ deviceRoot := filepath.Join(root, "devices")
+ devicePath, err := data.path("devices")
+ if err != nil {
+ t.Errorf("couldn't get cgroup path: %v", err)
+ }
+ if !strings.HasPrefix(devicePath, deviceRoot) {
+ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
+ }
+}
+
+func TestInvalidAbsoluteCgroupPath(t *testing.T) {
+ root, err := getCgroupRoot()
+ if err != nil {
+ t.Errorf("couldn't get cgroup root: %v", err)
+ }
+
+ config := &configs.Cgroup{
+ Path: "/../../../../../../../../../../some/path",
+ }
+
+ data, err := getCgroupData(config, 0)
+ if err != nil {
+ t.Errorf("couldn't get cgroup data: %v", err)
+ }
+
+ // Make sure the final innerPath doesn't go outside the cgroup mountpoint.
+ if strings.HasPrefix(data.innerPath, "..") {
+ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
+ }
+
+ // Double-check, using an actual cgroup.
+ deviceRoot := filepath.Join(root, "devices")
+ devicePath, err := data.path("devices")
+ if err != nil {
+ t.Errorf("couldn't get cgroup path: %v", err)
+ }
+ if !strings.HasPrefix(devicePath, deviceRoot) {
+ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
+ }
+}
+
+// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
+func TestInvalidCgroupParent(t *testing.T) {
+ root, err := getCgroupRoot()
+ if err != nil {
+ t.Errorf("couldn't get cgroup root: %v", err)
+ }
+
+ config := &configs.Cgroup{
+ Parent: "../../../../../../../../../../some/path",
+ Name: "name",
+ }
+
+ data, err := getCgroupData(config, 0)
+ if err != nil {
+ t.Errorf("couldn't get cgroup data: %v", err)
+ }
+
+ // Make sure the final innerPath doesn't go outside the cgroup mountpoint.
+ if strings.HasPrefix(data.innerPath, "..") {
+ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
+ }
+
+ // Double-check, using an actual cgroup.
+ deviceRoot := filepath.Join(root, "devices")
+ devicePath, err := data.path("devices")
+ if err != nil {
+ t.Errorf("couldn't get cgroup path: %v", err)
+ }
+ if !strings.HasPrefix(devicePath, deviceRoot) {
+ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
+ }
+}
+
+// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
+func TestInvalidAbsoluteCgroupParent(t *testing.T) {
+ root, err := getCgroupRoot()
+ if err != nil {
+ t.Errorf("couldn't get cgroup root: %v", err)
+ }
+
+ config := &configs.Cgroup{
+ Parent: "/../../../../../../../../../../some/path",
+ Name: "name",
+ }
+
+ data, err := getCgroupData(config, 0)
+ if err != nil {
+ t.Errorf("couldn't get cgroup data: %v", err)
+ }
+
+ // Make sure the final innerPath doesn't go outside the cgroup mountpoint.
+ if strings.HasPrefix(data.innerPath, "..") {
+ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
+ }
+
+ // Double-check, using an actual cgroup.
+ deviceRoot := filepath.Join(root, "devices")
+ devicePath, err := data.path("devices")
+ if err != nil {
+ t.Errorf("couldn't get cgroup path: %v", err)
+ }
+ if !strings.HasPrefix(devicePath, deviceRoot) {
+ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
+ }
+}
+
+// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
+func TestInvalidCgroupName(t *testing.T) {
+ root, err := getCgroupRoot()
+ if err != nil {
+ t.Errorf("couldn't get cgroup root: %v", err)
+ }
+
+ config := &configs.Cgroup{
+ Parent: "parent",
+ Name: "../../../../../../../../../../some/path",
+ }
+
+ data, err := getCgroupData(config, 0)
+ if err != nil {
+ t.Errorf("couldn't get cgroup data: %v", err)
+ }
+
+ // Make sure the final innerPath doesn't go outside the cgroup mountpoint.
+ if strings.HasPrefix(data.innerPath, "..") {
+ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
+ }
+
+ // Double-check, using an actual cgroup.
+ deviceRoot := filepath.Join(root, "devices")
+ devicePath, err := data.path("devices")
+ if err != nil {
+ t.Errorf("couldn't get cgroup path: %v", err)
+ }
+ if !strings.HasPrefix(devicePath, deviceRoot) {
+ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
+ }
+
+}
+
+// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
+func TestInvalidAbsoluteCgroupName(t *testing.T) {
+ root, err := getCgroupRoot()
+ if err != nil {
+ t.Errorf("couldn't get cgroup root: %v", err)
+ }
+
+ config := &configs.Cgroup{
+ Parent: "parent",
+ Name: "/../../../../../../../../../../some/path",
+ }
+
+ data, err := getCgroupData(config, 0)
+ if err != nil {
+ t.Errorf("couldn't get cgroup data: %v", err)
+ }
+
+ // Make sure the final innerPath doesn't go outside the cgroup mountpoint.
+ if strings.HasPrefix(data.innerPath, "..") {
+ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
+ }
+
+ // Double-check, using an actual cgroup.
+ deviceRoot := filepath.Join(root, "devices")
+ devicePath, err := data.path("devices")
+ if err != nil {
+ t.Errorf("couldn't get cgroup path: %v", err)
+ }
+ if !strings.HasPrefix(devicePath, deviceRoot) {
+ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
+ }
+}
+
+// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
+func TestInvalidCgroupNameAndParent(t *testing.T) {
+ root, err := getCgroupRoot()
+ if err != nil {
+ t.Errorf("couldn't get cgroup root: %v", err)
+ }
+
+ config := &configs.Cgroup{
+ Parent: "../../../../../../../../../../some/path",
+ Name: "../../../../../../../../../../some/path",
+ }
+
+ data, err := getCgroupData(config, 0)
+ if err != nil {
+ t.Errorf("couldn't get cgroup data: %v", err)
+ }
+
+ // Make sure the final innerPath doesn't go outside the cgroup mountpoint.
+ if strings.HasPrefix(data.innerPath, "..") {
+ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
+ }
+
+ // Double-check, using an actual cgroup.
+ deviceRoot := filepath.Join(root, "devices")
+ devicePath, err := data.path("devices")
+ if err != nil {
+ t.Errorf("couldn't get cgroup path: %v", err)
+ }
+ if !strings.HasPrefix(devicePath, deviceRoot) {
+ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
+ }
+}
+
+// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
+func TestInvalidAbsoluteCgroupNameAndParent(t *testing.T) {
+ root, err := getCgroupRoot()
+ if err != nil {
+ t.Errorf("couldn't get cgroup root: %v", err)
+ }
+
+ config := &configs.Cgroup{
+ Parent: "/../../../../../../../../../../some/path",
+ Name: "/../../../../../../../../../../some/path",
+ }
+
+ data, err := getCgroupData(config, 0)
+ if err != nil {
+ t.Errorf("couldn't get cgroup data: %v", err)
+ }
+
+ // Make sure the final innerPath doesn't go outside the cgroup mountpoint.
+ if strings.HasPrefix(data.innerPath, "..") {
+ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
+ }
+
+ // Double-check, using an actual cgroup.
+ deviceRoot := filepath.Join(root, "devices")
+ devicePath, err := data.path("devices")
+ if err != nil {
+ t.Errorf("couldn't get cgroup path: %v", err)
+ }
+ if !strings.HasPrefix(devicePath, deviceRoot) {
+ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go
new file mode 100644
index 0000000000..a142cb991d
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go
@@ -0,0 +1,237 @@
+// +build linux
+
+package fs
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type BlkioGroup struct {
+}
+
+func (s *BlkioGroup) Name() string {
+ return "blkio"
+}
+
+func (s *BlkioGroup) Apply(d *cgroupData) error {
+ _, err := d.join("blkio")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
+
+func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
+ if cgroup.Resources.BlkioWeight != 0 {
+ if err := writeFile(path, "blkio.weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioWeight), 10)); err != nil {
+ return err
+ }
+ }
+
+ if cgroup.Resources.BlkioLeafWeight != 0 {
+ if err := writeFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioLeafWeight), 10)); err != nil {
+ return err
+ }
+ }
+ for _, wd := range cgroup.Resources.BlkioWeightDevice {
+ if err := writeFile(path, "blkio.weight_device", wd.WeightString()); err != nil {
+ return err
+ }
+ if err := writeFile(path, "blkio.leaf_weight_device", wd.LeafWeightString()); err != nil {
+ return err
+ }
+ }
+ for _, td := range cgroup.Resources.BlkioThrottleReadBpsDevice {
+ if err := writeFile(path, "blkio.throttle.read_bps_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range cgroup.Resources.BlkioThrottleWriteBpsDevice {
+ if err := writeFile(path, "blkio.throttle.write_bps_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range cgroup.Resources.BlkioThrottleReadIOPSDevice {
+ if err := writeFile(path, "blkio.throttle.read_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range cgroup.Resources.BlkioThrottleWriteIOPSDevice {
+ if err := writeFile(path, "blkio.throttle.write_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *BlkioGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("blkio"))
+}
+
+/*
+examples:
+
+ blkio.sectors
+ 8:0 6792
+
+ blkio.io_service_bytes
+ 8:0 Read 1282048
+ 8:0 Write 2195456
+ 8:0 Sync 2195456
+ 8:0 Async 1282048
+ 8:0 Total 3477504
+ Total 3477504
+
+ blkio.io_serviced
+ 8:0 Read 124
+ 8:0 Write 104
+ 8:0 Sync 104
+ 8:0 Async 124
+ 8:0 Total 228
+ Total 228
+
+ blkio.io_queued
+ 8:0 Read 0
+ 8:0 Write 0
+ 8:0 Sync 0
+ 8:0 Async 0
+ 8:0 Total 0
+ Total 0
+*/
+
+func splitBlkioStatLine(r rune) bool {
+ return r == ' ' || r == ':'
+}
+
+func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
+ var blkioStats []cgroups.BlkioStatEntry
+ f, err := os.Open(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return blkioStats, nil
+ }
+ return nil, err
+ }
+ defer f.Close()
+
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ // format: dev type amount
+ fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine)
+ if len(fields) < 3 {
+ if len(fields) == 2 && fields[0] == "Total" {
+ // skip total line
+ continue
+ } else {
+ return nil, fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text())
+ }
+ }
+
+ v, err := strconv.ParseUint(fields[0], 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ major := v
+
+ v, err = strconv.ParseUint(fields[1], 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ minor := v
+
+ op := ""
+ valueField := 2
+ if len(fields) == 4 {
+ op = fields[2]
+ valueField = 3
+ }
+ v, err = strconv.ParseUint(fields[valueField], 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v})
+ }
+
+ return blkioStats, nil
+}
+
+func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error {
+ // Try to read CFQ stats available on all CFQ enabled kernels first
+ if blkioStats, err := getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err == nil && blkioStats != nil {
+ return getCFQStats(path, stats)
+ }
+ return getStats(path, stats) // Use generic stats as fallback
+}
+
+func getCFQStats(path string, stats *cgroups.Stats) error {
+ var blkioStats []cgroups.BlkioStatEntry
+ var err error
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil {
+ return err
+ }
+ stats.BlkioStats.SectorsRecursive = blkioStats
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_bytes_recursive")); err != nil {
+ return err
+ }
+ stats.BlkioStats.IoServiceBytesRecursive = blkioStats
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err != nil {
+ return err
+ }
+ stats.BlkioStats.IoServicedRecursive = blkioStats
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_queued_recursive")); err != nil {
+ return err
+ }
+ stats.BlkioStats.IoQueuedRecursive = blkioStats
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_time_recursive")); err != nil {
+ return err
+ }
+ stats.BlkioStats.IoServiceTimeRecursive = blkioStats
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_wait_time_recursive")); err != nil {
+ return err
+ }
+ stats.BlkioStats.IoWaitTimeRecursive = blkioStats
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_merged_recursive")); err != nil {
+ return err
+ }
+ stats.BlkioStats.IoMergedRecursive = blkioStats
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.time_recursive")); err != nil {
+ return err
+ }
+ stats.BlkioStats.IoTimeRecursive = blkioStats
+
+ return nil
+}
+
+func getStats(path string, stats *cgroups.Stats) error {
+ var blkioStats []cgroups.BlkioStatEntry
+ var err error
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_service_bytes")); err != nil {
+ return err
+ }
+ stats.BlkioStats.IoServiceBytesRecursive = blkioStats
+
+ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_serviced")); err != nil {
+ return err
+ }
+ stats.BlkioStats.IoServicedRecursive = blkioStats
+
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go
new file mode 100644
index 0000000000..6957392048
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go
@@ -0,0 +1,636 @@
+// +build linux
+
+package fs
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+const (
+ sectorsRecursiveContents = `8:0 1024`
+ serviceBytesRecursiveContents = `8:0 Read 100
+8:0 Write 200
+8:0 Sync 300
+8:0 Async 500
+8:0 Total 500
+Total 500`
+ servicedRecursiveContents = `8:0 Read 10
+8:0 Write 40
+8:0 Sync 20
+8:0 Async 30
+8:0 Total 50
+Total 50`
+ queuedRecursiveContents = `8:0 Read 1
+8:0 Write 4
+8:0 Sync 2
+8:0 Async 3
+8:0 Total 5
+Total 5`
+ serviceTimeRecursiveContents = `8:0 Read 173959
+8:0 Write 0
+8:0 Sync 0
+8:0 Async 173959
+8:0 Total 17395
+Total 17395`
+ waitTimeRecursiveContents = `8:0 Read 15571
+8:0 Write 0
+8:0 Sync 0
+8:0 Async 15571
+8:0 Total 15571`
+ mergedRecursiveContents = `8:0 Read 5
+8:0 Write 10
+8:0 Sync 0
+8:0 Async 0
+8:0 Total 15
+Total 15`
+ timeRecursiveContents = `8:0 8`
+ throttleServiceBytes = `8:0 Read 11030528
+8:0 Write 23
+8:0 Sync 42
+8:0 Async 11030528
+8:0 Total 11030528
+252:0 Read 11030528
+252:0 Write 23
+252:0 Sync 42
+252:0 Async 11030528
+252:0 Total 11030528
+Total 22061056`
+ throttleServiced = `8:0 Read 164
+8:0 Write 23
+8:0 Sync 42
+8:0 Async 164
+8:0 Total 164
+252:0 Read 164
+252:0 Write 23
+252:0 Sync 42
+252:0 Async 164
+252:0 Total 164
+Total 328`
+)
+
+func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) {
+ *blkioStatEntries = append(*blkioStatEntries, cgroups.BlkioStatEntry{Major: major, Minor: minor, Value: value, Op: op})
+}
+
+func TestBlkioSetWeight(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+
+ const (
+ weightBefore = 100
+ weightAfter = 200
+ )
+
+ helper.writeFileContents(map[string]string{
+ "blkio.weight": strconv.Itoa(weightBefore),
+ })
+
+ helper.CgroupData.config.Resources.BlkioWeight = weightAfter
+ blkio := &BlkioGroup{}
+ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "blkio.weight")
+ if err != nil {
+ t.Fatalf("Failed to parse blkio.weight - %s", err)
+ }
+
+ if value != weightAfter {
+ t.Fatal("Got the wrong value, set blkio.weight failed.")
+ }
+}
+
+func TestBlkioSetWeightDevice(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+
+ const (
+ weightDeviceBefore = "8:0 400"
+ )
+
+ wd := configs.NewWeightDevice(8, 0, 500, 0)
+ weightDeviceAfter := wd.WeightString()
+
+ helper.writeFileContents(map[string]string{
+ "blkio.weight_device": weightDeviceBefore,
+ })
+
+ helper.CgroupData.config.Resources.BlkioWeightDevice = []*configs.WeightDevice{wd}
+ blkio := &BlkioGroup{}
+ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); 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.")
+ }
+}
+
+// regression #274
+func TestBlkioSetMultipleWeightDevice(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+
+ const (
+ weightDeviceBefore = "8:0 400"
+ )
+
+ wd1 := configs.NewWeightDevice(8, 0, 500, 0)
+ wd2 := configs.NewWeightDevice(8, 16, 500, 0)
+ // we cannot actually set and check both because normal ioutil.WriteFile
+ // when writing to cgroup file will overwrite the whole file content instead
+ // of updating it as the kernel is doing. Just check the second device
+ // is present will suffice for the test to ensure multiple writes are done.
+ weightDeviceAfter := wd2.WeightString()
+
+ helper.writeFileContents(map[string]string{
+ "blkio.weight_device": weightDeviceBefore,
+ })
+
+ helper.CgroupData.config.Resources.BlkioWeightDevice = []*configs.WeightDevice{wd1, wd2}
+ blkio := &BlkioGroup{}
+ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); 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()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Verify expected stats.
+ expectedStats := cgroups.BlkioStats{}
+ appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 1024, "")
+
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 100, "Read")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 200, "Write")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 300, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Async")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Total")
+
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 10, "Read")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 40, "Write")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 20, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 30, "Async")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 50, "Total")
+
+ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 1, "Read")
+ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Write")
+ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Async")
+ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Total")
+
+ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Read")
+ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Write")
+ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Async")
+ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 17395, "Total")
+
+ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Read")
+ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Write")
+ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Async")
+ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Total")
+
+ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 5, "Read")
+ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 10, "Write")
+ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Async")
+ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 15, "Total")
+
+ appendBlkioStatEntry(&expectedStats.IoTimeRecursive, 8, 0, 8, "")
+
+ expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
+}
+
+func TestBlkioStatsNoSectorsFile(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatalf("Failed unexpectedly: %s", err)
+ }
+}
+
+func TestBlkioStatsNoServiceBytesFile(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatalf("Failed unexpectedly: %s", err)
+ }
+}
+
+func TestBlkioStatsNoServicedFile(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatalf("Failed unexpectedly: %s", err)
+ }
+}
+
+func TestBlkioStatsNoQueuedFile(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatalf("Failed unexpectedly: %s", err)
+ }
+}
+
+func TestBlkioStatsNoServiceTimeFile(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping test in short mode.")
+ }
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatalf("Failed unexpectedly: %s", err)
+ }
+}
+
+func TestBlkioStatsNoWaitTimeFile(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping test in short mode.")
+ }
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatalf("Failed unexpectedly: %s", err)
+ }
+}
+
+func TestBlkioStatsNoMergedFile(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping test in short mode.")
+ }
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatalf("Failed unexpectedly: %s", err)
+ }
+}
+
+func TestBlkioStatsNoTimeFile(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping test in short mode.")
+ }
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatalf("Failed unexpectedly: %s", err)
+ }
+}
+
+func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": "8:0 Read 100 100",
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected to fail, but did not")
+ }
+}
+
+func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": "8:0 Read Write",
+ "blkio.io_serviced_recursive": servicedRecursiveContents,
+ "blkio.io_queued_recursive": queuedRecursiveContents,
+ "blkio.sectors_recursive": sectorsRecursiveContents,
+ "blkio.io_service_time_recursive": serviceTimeRecursiveContents,
+ "blkio.io_wait_time_recursive": waitTimeRecursiveContents,
+ "blkio.io_merged_recursive": mergedRecursiveContents,
+ "blkio.time_recursive": timeRecursiveContents,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected to fail, but did not")
+ }
+}
+
+func TestNonCFQBlkioStats(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "blkio.io_service_bytes_recursive": "",
+ "blkio.io_serviced_recursive": "",
+ "blkio.io_queued_recursive": "",
+ "blkio.sectors_recursive": "",
+ "blkio.io_service_time_recursive": "",
+ "blkio.io_wait_time_recursive": "",
+ "blkio.io_merged_recursive": "",
+ "blkio.time_recursive": "",
+ "blkio.throttle.io_service_bytes": throttleServiceBytes,
+ "blkio.throttle.io_serviced": throttleServiced,
+ })
+
+ blkio := &BlkioGroup{}
+ actualStats := *cgroups.NewStats()
+ err := blkio.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Verify expected stats.
+ expectedStats := cgroups.BlkioStats{}
+
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Read")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 23, "Write")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 42, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Async")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Total")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Read")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 23, "Write")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 42, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Async")
+ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Total")
+
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Read")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 23, "Write")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 42, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Async")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Total")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Read")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 23, "Write")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 42, "Sync")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Async")
+ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Total")
+
+ expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
+}
+
+func TestBlkioSetThrottleReadBpsDevice(t *testing.T) {
+ helper := NewCgroupTestUtil("blkio", t)
+ defer helper.cleanup()
+
+ const (
+ throttleBefore = `8:0 1024`
+ )
+
+ td := configs.NewThrottleDevice(8, 0, 2048)
+ throttleAfter := td.String()
+
+ helper.writeFileContents(map[string]string{
+ "blkio.throttle.read_bps_device": throttleBefore,
+ })
+
+ helper.CgroupData.config.Resources.BlkioThrottleReadBpsDevice = []*configs.ThrottleDevice{td}
+ blkio := &BlkioGroup{}
+ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); 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()
+
+ const (
+ throttleBefore = `8:0 1024`
+ )
+
+ td := configs.NewThrottleDevice(8, 0, 2048)
+ throttleAfter := td.String()
+
+ helper.writeFileContents(map[string]string{
+ "blkio.throttle.write_bps_device": throttleBefore,
+ })
+
+ helper.CgroupData.config.Resources.BlkioThrottleWriteBpsDevice = []*configs.ThrottleDevice{td}
+ blkio := &BlkioGroup{}
+ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); 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()
+
+ const (
+ throttleBefore = `8:0 1024`
+ )
+
+ td := configs.NewThrottleDevice(8, 0, 2048)
+ throttleAfter := td.String()
+
+ helper.writeFileContents(map[string]string{
+ "blkio.throttle.read_iops_device": throttleBefore,
+ })
+
+ helper.CgroupData.config.Resources.BlkioThrottleReadIOPSDevice = []*configs.ThrottleDevice{td}
+ blkio := &BlkioGroup{}
+ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); 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()
+
+ const (
+ throttleBefore = `8:0 1024`
+ )
+
+ td := configs.NewThrottleDevice(8, 0, 2048)
+ throttleAfter := td.String()
+
+ helper.writeFileContents(map[string]string{
+ "blkio.throttle.write_iops_device": throttleBefore,
+ })
+
+ helper.CgroupData.config.Resources.BlkioThrottleWriteIOPSDevice = []*configs.ThrottleDevice{td}
+ blkio := &BlkioGroup{}
+ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); 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/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go
new file mode 100644
index 0000000000..b712bd0b1e
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go
@@ -0,0 +1,125 @@
+// +build linux
+
+package fs
+
+import (
+ "bufio"
+ "os"
+ "path/filepath"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type CpuGroup struct {
+}
+
+func (s *CpuGroup) Name() string {
+ return "cpu"
+}
+
+func (s *CpuGroup) Apply(d *cgroupData) error {
+ // We always want to join the cpu group, to allow fair cpu scheduling
+ // on a container basis
+ path, err := d.path("cpu")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return s.ApplyDir(path, d.config, d.pid)
+}
+
+func (s *CpuGroup) ApplyDir(path string, cgroup *configs.Cgroup, pid int) error {
+ // This might happen if we have no cpu cgroup mounted.
+ // Just do nothing and don't fail.
+ if path == "" {
+ return nil
+ }
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return err
+ }
+ // We should set the real-Time group scheduling settings before moving
+ // in the process because if the process is already in SCHED_RR mode
+ // and no RT bandwidth is set, adding it will fail.
+ if err := s.SetRtSched(path, cgroup); err != nil {
+ return err
+ }
+ // because we are not using d.join we need to place the pid into the procs file
+ // unlike the other subsystems
+ if err := cgroups.WriteCgroupProc(path, pid); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *CpuGroup) SetRtSched(path string, cgroup *configs.Cgroup) error {
+ if cgroup.Resources.CpuRtPeriod != 0 {
+ if err := writeFile(path, "cpu.rt_period_us", strconv.FormatUint(cgroup.Resources.CpuRtPeriod, 10)); err != nil {
+ return err
+ }
+ }
+ if cgroup.Resources.CpuRtRuntime != 0 {
+ if err := writeFile(path, "cpu.rt_runtime_us", strconv.FormatInt(cgroup.Resources.CpuRtRuntime, 10)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error {
+ if cgroup.Resources.CpuShares != 0 {
+ if err := writeFile(path, "cpu.shares", strconv.FormatUint(cgroup.Resources.CpuShares, 10)); err != nil {
+ return err
+ }
+ }
+ if cgroup.Resources.CpuPeriod != 0 {
+ if err := writeFile(path, "cpu.cfs_period_us", strconv.FormatUint(cgroup.Resources.CpuPeriod, 10)); err != nil {
+ return err
+ }
+ }
+ if cgroup.Resources.CpuQuota != 0 {
+ if err := writeFile(path, "cpu.cfs_quota_us", strconv.FormatInt(cgroup.Resources.CpuQuota, 10)); err != nil {
+ return err
+ }
+ }
+ if err := s.SetRtSched(path, cgroup); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *CpuGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("cpu"))
+}
+
+func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
+ f, err := os.Open(filepath.Join(path, "cpu.stat"))
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+ defer f.Close()
+
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ t, v, err := getCgroupParamKeyValue(sc.Text())
+ if err != nil {
+ return err
+ }
+ switch t {
+ case "nr_periods":
+ stats.CpuStats.ThrottlingData.Periods = v
+
+ case "nr_throttled":
+ stats.CpuStats.ThrottlingData.ThrottledPeriods = v
+
+ case "throttled_time":
+ stats.CpuStats.ThrottlingData.ThrottledTime = v
+ }
+ }
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go
new file mode 100644
index 0000000000..6369c91ad6
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go
@@ -0,0 +1,209 @@
+// +build linux
+
+package fs
+
+import (
+ "fmt"
+ "strconv"
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+)
+
+func TestCpuSetShares(t *testing.T) {
+ helper := NewCgroupTestUtil("cpu", t)
+ defer helper.cleanup()
+
+ const (
+ sharesBefore = 1024
+ sharesAfter = 512
+ )
+
+ helper.writeFileContents(map[string]string{
+ "cpu.shares": strconv.Itoa(sharesBefore),
+ })
+
+ helper.CgroupData.config.Resources.CpuShares = sharesAfter
+ cpu := &CpuGroup{}
+ if err := cpu.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "cpu.shares")
+ if err != nil {
+ t.Fatalf("Failed to parse cpu.shares - %s", err)
+ }
+
+ if value != sharesAfter {
+ t.Fatal("Got the wrong value, set cpu.shares failed.")
+ }
+}
+
+func TestCpuSetBandWidth(t *testing.T) {
+ helper := NewCgroupTestUtil("cpu", t)
+ defer helper.cleanup()
+
+ const (
+ quotaBefore = 8000
+ quotaAfter = 5000
+ periodBefore = 10000
+ periodAfter = 7000
+ rtRuntimeBefore = 8000
+ rtRuntimeAfter = 5000
+ rtPeriodBefore = 10000
+ rtPeriodAfter = 7000
+ )
+
+ helper.writeFileContents(map[string]string{
+ "cpu.cfs_quota_us": strconv.Itoa(quotaBefore),
+ "cpu.cfs_period_us": strconv.Itoa(periodBefore),
+ "cpu.rt_runtime_us": strconv.Itoa(rtRuntimeBefore),
+ "cpu.rt_period_us": strconv.Itoa(rtPeriodBefore),
+ })
+
+ helper.CgroupData.config.Resources.CpuQuota = quotaAfter
+ helper.CgroupData.config.Resources.CpuPeriod = periodAfter
+ helper.CgroupData.config.Resources.CpuRtRuntime = rtRuntimeAfter
+ helper.CgroupData.config.Resources.CpuRtPeriod = rtPeriodAfter
+ cpu := &CpuGroup{}
+ if err := cpu.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ quota, err := getCgroupParamUint(helper.CgroupPath, "cpu.cfs_quota_us")
+ if err != nil {
+ t.Fatalf("Failed to parse cpu.cfs_quota_us - %s", err)
+ }
+ if quota != quotaAfter {
+ t.Fatal("Got the wrong value, set cpu.cfs_quota_us failed.")
+ }
+
+ period, err := getCgroupParamUint(helper.CgroupPath, "cpu.cfs_period_us")
+ if err != nil {
+ t.Fatalf("Failed to parse cpu.cfs_period_us - %s", err)
+ }
+ if period != periodAfter {
+ t.Fatal("Got the wrong value, set cpu.cfs_period_us failed.")
+ }
+ rtRuntime, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_runtime_us")
+ if err != nil {
+ t.Fatalf("Failed to parse cpu.rt_runtime_us - %s", err)
+ }
+ if rtRuntime != rtRuntimeAfter {
+ t.Fatal("Got the wrong value, set cpu.rt_runtime_us failed.")
+ }
+ rtPeriod, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_period_us")
+ if err != nil {
+ t.Fatalf("Failed to parse cpu.rt_period_us - %s", err)
+ }
+ if rtPeriod != rtPeriodAfter {
+ t.Fatal("Got the wrong value, set cpu.rt_period_us failed.")
+ }
+}
+
+func TestCpuStats(t *testing.T) {
+ helper := NewCgroupTestUtil("cpu", t)
+ defer helper.cleanup()
+
+ const (
+ nrPeriods = 2000
+ nrThrottled = 200
+ throttledTime = uint64(18446744073709551615)
+ )
+
+ cpuStatContent := fmt.Sprintf("nr_periods %d\n nr_throttled %d\n throttled_time %d\n",
+ nrPeriods, nrThrottled, throttledTime)
+ helper.writeFileContents(map[string]string{
+ "cpu.stat": cpuStatContent,
+ })
+
+ cpu := &CpuGroup{}
+ actualStats := *cgroups.NewStats()
+ err := cpu.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expectedStats := cgroups.ThrottlingData{
+ Periods: nrPeriods,
+ ThrottledPeriods: nrThrottled,
+ ThrottledTime: throttledTime}
+
+ expectThrottlingDataEquals(t, expectedStats, actualStats.CpuStats.ThrottlingData)
+}
+
+func TestNoCpuStatFile(t *testing.T) {
+ helper := NewCgroupTestUtil("cpu", t)
+ defer helper.cleanup()
+
+ cpu := &CpuGroup{}
+ actualStats := *cgroups.NewStats()
+ err := cpu.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatal("Expected not to fail, but did")
+ }
+}
+
+func TestInvalidCpuStat(t *testing.T) {
+ helper := NewCgroupTestUtil("cpu", t)
+ defer helper.cleanup()
+ cpuStatContent := `nr_periods 2000
+ nr_throttled 200
+ throttled_time fortytwo`
+ helper.writeFileContents(map[string]string{
+ "cpu.stat": cpuStatContent,
+ })
+
+ cpu := &CpuGroup{}
+ actualStats := *cgroups.NewStats()
+ err := cpu.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failed stat parsing.")
+ }
+}
+
+func TestCpuSetRtSchedAtApply(t *testing.T) {
+ helper := NewCgroupTestUtil("cpu", t)
+ defer helper.cleanup()
+
+ const (
+ rtRuntimeBefore = 0
+ rtRuntimeAfter = 5000
+ rtPeriodBefore = 0
+ rtPeriodAfter = 7000
+ )
+
+ helper.writeFileContents(map[string]string{
+ "cpu.rt_runtime_us": strconv.Itoa(rtRuntimeBefore),
+ "cpu.rt_period_us": strconv.Itoa(rtPeriodBefore),
+ })
+
+ helper.CgroupData.config.Resources.CpuRtRuntime = rtRuntimeAfter
+ helper.CgroupData.config.Resources.CpuRtPeriod = rtPeriodAfter
+ cpu := &CpuGroup{}
+ if err := cpu.ApplyDir(helper.CgroupPath, helper.CgroupData.config, 1234); err != nil {
+ t.Fatal(err)
+ }
+
+ rtRuntime, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_runtime_us")
+ if err != nil {
+ t.Fatalf("Failed to parse cpu.rt_runtime_us - %s", err)
+ }
+ if rtRuntime != rtRuntimeAfter {
+ t.Fatal("Got the wrong value, set cpu.rt_runtime_us failed.")
+ }
+ rtPeriod, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_period_us")
+ if err != nil {
+ t.Fatalf("Failed to parse cpu.rt_period_us - %s", err)
+ }
+ if rtPeriod != rtPeriodAfter {
+ t.Fatal("Got the wrong value, set cpu.rt_period_us failed.")
+ }
+ pid, err := getCgroupParamUint(helper.CgroupPath, "cgroup.procs")
+ if err != nil {
+ t.Fatalf("Failed to parse cgroup.procs - %s", err)
+ }
+ if pid != 1234 {
+ t.Fatal("Got the wrong value, set cgroup.procs failed.")
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go
new file mode 100644
index 0000000000..53afbaddf1
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go
@@ -0,0 +1,121 @@
+// +build linux
+
+package fs
+
+import (
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/opencontainers/runc/libcontainer/system"
+)
+
+const (
+ cgroupCpuacctStat = "cpuacct.stat"
+ nanosecondsInSecond = 1000000000
+)
+
+var clockTicks = uint64(system.GetClockTicks())
+
+type CpuacctGroup struct {
+}
+
+func (s *CpuacctGroup) Name() string {
+ return "cpuacct"
+}
+
+func (s *CpuacctGroup) Apply(d *cgroupData) error {
+ // we just want to join this group even though we don't set anything
+ if _, err := d.join("cpuacct"); err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+
+ return nil
+}
+
+func (s *CpuacctGroup) Set(path string, cgroup *configs.Cgroup) error {
+ return nil
+}
+
+func (s *CpuacctGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("cpuacct"))
+}
+
+func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
+ userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path)
+ if err != nil {
+ return err
+ }
+
+ totalUsage, err := getCgroupParamUint(path, "cpuacct.usage")
+ if err != nil {
+ return err
+ }
+
+ percpuUsage, err := getPercpuUsage(path)
+ if err != nil {
+ return err
+ }
+
+ stats.CpuStats.CpuUsage.TotalUsage = totalUsage
+ stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage
+ stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage
+ stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage
+ return nil
+}
+
+// Returns user and kernel usage breakdown in nanoseconds.
+func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
+ userModeUsage := uint64(0)
+ kernelModeUsage := uint64(0)
+ const (
+ userField = "user"
+ systemField = "system"
+ )
+
+ // Expected format:
+ // user <usage in ticks>
+ // system <usage in ticks>
+ data, err := ioutil.ReadFile(filepath.Join(path, cgroupCpuacctStat))
+ if err != nil {
+ return 0, 0, err
+ }
+ fields := strings.Fields(string(data))
+ if len(fields) != 4 {
+ return 0, 0, fmt.Errorf("failure - %s is expected to have 4 fields", filepath.Join(path, cgroupCpuacctStat))
+ }
+ if fields[0] != userField {
+ return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[0], cgroupCpuacctStat, userField)
+ }
+ if fields[2] != systemField {
+ return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[2], cgroupCpuacctStat, systemField)
+ }
+ if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil {
+ return 0, 0, err
+ }
+ if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil {
+ return 0, 0, err
+ }
+
+ return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil
+}
+
+func getPercpuUsage(path string) ([]uint64, error) {
+ percpuUsage := []uint64{}
+ data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu"))
+ if err != nil {
+ return percpuUsage, err
+ }
+ for _, value := range strings.Fields(string(data)) {
+ value, err := strconv.ParseUint(value, 10, 64)
+ if err != nil {
+ return percpuUsage, fmt.Errorf("Unable to convert param value to uint64: %s", err)
+ }
+ percpuUsage = append(percpuUsage, value)
+ }
+ return percpuUsage, nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go
new file mode 100644
index 0000000000..e61994fc3c
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go
@@ -0,0 +1,183 @@
+// +build linux
+
+package fs
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/sirupsen/logrus"
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
+)
+
+type CpusetGroup struct {
+}
+
+func (s *CpusetGroup) Name() string {
+ return "cpuset"
+}
+
+func (s *CpusetGroup) Apply(d *cgroupData) error {
+ dir, err := d.path("cpuset")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return s.ApplyDir(dir, d.config, d.pid)
+}
+
+func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
+ if cgroup.Resources.CpusetCpus != "" {
+ if err := writeFile(path, "cpuset.cpus", cgroup.Resources.CpusetCpus); err != nil {
+ return err
+ }
+ }
+ if cgroup.Resources.CpusetMems != "" {
+ if err := writeFile(path, "cpuset.mems", cgroup.Resources.CpusetMems); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (s *CpusetGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("cpuset"))
+}
+
+func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
+
+func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) error {
+ // This might happen if we have no cpuset cgroup mounted.
+ // Just do nothing and don't fail.
+ if dir == "" {
+ return nil
+ }
+ root, err := getCgroupRoot()
+ if err != nil {
+ return err
+ }
+ // 'ensureParent' start with parent because we don't want to
+ // explicitly inherit from parent, it could conflict with
+ // 'cpuset.cpu_exclusive'.
+ if err := s.ensureParent(filepath.Dir(dir), root); err != nil {
+ return err
+ }
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return err
+ }
+ // We didn't inherit cpuset configs from parent, but we have
+ // to ensure cpuset configs are set before moving task into the
+ // cgroup.
+ // The logic is, if user specified cpuset configs, use these
+ // specified configs, otherwise, inherit from parent. This makes
+ // cpuset configs work correctly with 'cpuset.cpu_exclusive', and
+ // keep backward compatbility.
+ if err := s.ensureCpusAndMems(dir, cgroup); err != nil {
+ return err
+ }
+
+ // because we are not using d.join we need to place the pid into the procs file
+ // unlike the other subsystems
+ if err := cgroups.WriteCgroupProc(dir, pid); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
+ defer func() {
+ if err != nil {
+ minfo, err1 := ioutil.ReadFile("/proc/self/mountinfo")
+ if err1 != nil {
+ logrus.Errorf("Failed to read mountinfo when getSubsystemSettings get an error")
+ }
+
+ dirInfo := ""
+ fs, err2 := ioutil.ReadDir(parent)
+ if err2 != nil {
+ logrus.Errorf("Failed to read mountinfo when getSubsystemSettings get an error")
+ }
+ for _, f := range fs {
+ dirInfo = dirInfo + " " + f.Name()
+ }
+
+ logrus.Errorf("Read cpuset cgroup failed, print mountinfo and cgroup info here"+
+ "path: %s, mountinfo: [%s], dirinfo: [%s]", parent, string(minfo), dirInfo)
+ }
+ }()
+ if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
+ return
+ }
+ if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil {
+ return
+ }
+ return cpus, mems, nil
+}
+
+// ensureParent makes sure that the parent directory of current is created
+// and populated with the proper cpus and mems files copied from
+// it's parent.
+func (s *CpusetGroup) ensureParent(current, root string) error {
+ parent := filepath.Dir(current)
+ if libcontainerUtils.CleanPath(parent) == root {
+ return nil
+ }
+ // Avoid infinite recursion.
+ if parent == current {
+ return fmt.Errorf("cpuset: cgroup parent path outside cgroup root")
+ }
+ if err := s.ensureParent(parent, root); err != nil {
+ return err
+ }
+ if err := os.MkdirAll(current, 0755); err != nil {
+ return err
+ }
+ return s.copyIfNeeded(current, parent)
+}
+
+// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
+// directory to the current directory if the file's contents are 0
+func (s *CpusetGroup) copyIfNeeded(current, parent string) error {
+ var (
+ err error
+ currentCpus, currentMems []byte
+ parentCpus, parentMems []byte
+ )
+
+ if currentCpus, currentMems, err = s.getSubsystemSettings(current); err != nil {
+ return err
+ }
+ if parentCpus, parentMems, err = s.getSubsystemSettings(parent); err != nil {
+ return err
+ }
+
+ if s.isEmpty(currentCpus) {
+ if err := writeFile(current, "cpuset.cpus", string(parentCpus)); err != nil {
+ return err
+ }
+ }
+ if s.isEmpty(currentMems) {
+ if err := writeFile(current, "cpuset.mems", string(parentMems)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (s *CpusetGroup) isEmpty(b []byte) bool {
+ return len(bytes.Trim(b, "\n")) == 0
+}
+
+func (s *CpusetGroup) ensureCpusAndMems(path string, cgroup *configs.Cgroup) error {
+ if err := s.Set(path, cgroup); err != nil {
+ return err
+ }
+ return s.copyIfNeeded(path, filepath.Dir(path))
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go
new file mode 100644
index 0000000000..0f929151fc
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go
@@ -0,0 +1,65 @@
+// +build linux
+
+package fs
+
+import (
+ "testing"
+)
+
+func TestCpusetSetCpus(t *testing.T) {
+ helper := NewCgroupTestUtil("cpuset", t)
+ defer helper.cleanup()
+
+ const (
+ cpusBefore = "0"
+ cpusAfter = "1-3"
+ )
+
+ helper.writeFileContents(map[string]string{
+ "cpuset.cpus": cpusBefore,
+ })
+
+ helper.CgroupData.config.Resources.CpusetCpus = cpusAfter
+ cpuset := &CpusetGroup{}
+ if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamString(helper.CgroupPath, "cpuset.cpus")
+ if err != nil {
+ t.Fatalf("Failed to parse cpuset.cpus - %s", err)
+ }
+
+ if value != cpusAfter {
+ t.Fatal("Got the wrong value, set cpuset.cpus failed.")
+ }
+}
+
+func TestCpusetSetMems(t *testing.T) {
+ helper := NewCgroupTestUtil("cpuset", t)
+ defer helper.cleanup()
+
+ const (
+ memsBefore = "0"
+ memsAfter = "1"
+ )
+
+ helper.writeFileContents(map[string]string{
+ "cpuset.mems": memsBefore,
+ })
+
+ helper.CgroupData.config.Resources.CpusetMems = memsAfter
+ cpuset := &CpusetGroup{}
+ if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamString(helper.CgroupPath, "cpuset.mems")
+ if err != nil {
+ t.Fatalf("Failed to parse cpuset.mems - %s", err)
+ }
+
+ if value != memsAfter {
+ t.Fatal("Got the wrong value, set cpuset.mems failed.")
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go
new file mode 100644
index 0000000000..0ac5b4ed70
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go
@@ -0,0 +1,80 @@
+// +build linux
+
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/opencontainers/runc/libcontainer/system"
+)
+
+type DevicesGroup struct {
+}
+
+func (s *DevicesGroup) Name() string {
+ return "devices"
+}
+
+func (s *DevicesGroup) Apply(d *cgroupData) error {
+ _, err := d.join("devices")
+ if err != nil {
+ // We will return error even it's `not found` error, devices
+ // cgroup is hard requirement for container's security.
+ return err
+ }
+ return nil
+}
+
+func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
+ if system.RunningInUserNS() {
+ return nil
+ }
+
+ devices := cgroup.Resources.Devices
+ if len(devices) > 0 {
+ for _, dev := range devices {
+ file := "devices.deny"
+ if dev.Allow {
+ file = "devices.allow"
+ }
+ if err := writeFile(path, file, dev.CgroupString()); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ if cgroup.Resources.AllowAllDevices != nil {
+ if *cgroup.Resources.AllowAllDevices == false {
+ if err := writeFile(path, "devices.deny", "a"); err != nil {
+ return err
+ }
+
+ for _, dev := range cgroup.Resources.AllowedDevices {
+ if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ if err := writeFile(path, "devices.allow", "a"); err != nil {
+ return err
+ }
+ }
+
+ for _, dev := range cgroup.Resources.DeniedDevices {
+ if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *DevicesGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("devices"))
+}
+
+func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go
new file mode 100644
index 0000000000..fc635b990f
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go
@@ -0,0 +1,98 @@
+// +build linux
+
+package fs
+
+import (
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+var (
+ allowedDevices = []*configs.Device{
+ {
+ Path: "/dev/zero",
+ Type: 'c',
+ Major: 1,
+ Minor: 5,
+ Permissions: "rwm",
+ FileMode: 0666,
+ },
+ }
+ 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) {
+ helper := NewCgroupTestUtil("devices", t)
+ defer helper.cleanup()
+
+ helper.writeFileContents(map[string]string{
+ "devices.deny": "a",
+ })
+ allowAllDevices := false
+ helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices
+ helper.CgroupData.config.Resources.AllowedDevices = allowedDevices
+ devices := &DevicesGroup{}
+ if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamString(helper.CgroupPath, "devices.allow")
+ if err != nil {
+ t.Fatalf("Failed to parse devices.allow - %s", err)
+ }
+
+ if value != allowedList {
+ t.Fatal("Got the wrong value, set devices.allow failed.")
+ }
+
+ // When AllowAllDevices is nil, devices.allow file should not be modified.
+ helper.CgroupData.config.Resources.AllowAllDevices = nil
+ if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+ value, err = getCgroupParamString(helper.CgroupPath, "devices.allow")
+ if err != nil {
+ t.Fatalf("Failed to parse devices.allow - %s", err)
+ }
+ if value != allowedList {
+ t.Fatal("devices policy shouldn't have changed on AllowedAllDevices=nil.")
+ }
+}
+
+func TestDevicesSetDeny(t *testing.T) {
+ helper := NewCgroupTestUtil("devices", t)
+ defer helper.cleanup()
+
+ helper.writeFileContents(map[string]string{
+ "devices.allow": "a",
+ })
+
+ allowAllDevices := true
+ helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices
+ helper.CgroupData.config.Resources.DeniedDevices = deniedDevices
+ devices := &DevicesGroup{}
+ if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); 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/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/files.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/files.go
new file mode 100644
index 0000000000..70e95240c8
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/files.go
@@ -0,0 +1,72 @@
+// +build linux
+
+package fs
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "path/filepath"
+)
+
+type FilesGroup struct {
+}
+
+func (s *FilesGroup) Name() string {
+ return "files"
+}
+
+func (s *FilesGroup) Apply(d *cgroupData) error {
+ _, err := d.join("files")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
+
+func (s *FilesGroup) Set(path string, cgroup *configs.Cgroup) error {
+ if cgroup.Resources.FilesLimit != 0 {
+ // "max" is the fallback value.
+ limit := "max"
+ if cgroup.Resources.FilesLimit > 0 {
+ limit = strconv.FormatInt(cgroup.Resources.FilesLimit, 10)
+ }
+
+ if err := writeFile(path, "files.limit", limit); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *FilesGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("files"))
+}
+
+func (s *FilesGroup) GetStats(path string, stats *cgroups.Stats) error {
+ usage, err := getCgroupParamUint(path, "files.usage")
+ if err != nil {
+ return fmt.Errorf("failed to parse files.usage - %s", err)
+ }
+
+ maxString, err := getCgroupParamString(path, "files.limit")
+ if err != nil {
+ return fmt.Errorf("failed to parse files.limit - %s", err)
+ }
+
+ // Default if files.limit == "max" is 0 -- which represents "no limit".
+ var max uint64
+ if maxString != "max" {
+ max, err = parseUint(maxString, 10, 64)
+ if err != nil {
+ return fmt.Errorf("failed to parse files.limit -- unable to parse %q as a uint from Cgroup file %q", maxString, filepath.Join(path, "file.limits"))
+ }
+ }
+
+ stats.FilesStats.Usage = usage
+ stats.FilesStats.Limit = max
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go
new file mode 100644
index 0000000000..e70dfe3b95
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go
@@ -0,0 +1,61 @@
+// +build linux
+
+package fs
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type FreezerGroup struct {
+}
+
+func (s *FreezerGroup) Name() string {
+ return "freezer"
+}
+
+func (s *FreezerGroup) Apply(d *cgroupData) error {
+ _, err := d.join("freezer")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
+
+func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
+ switch cgroup.Resources.Freezer {
+ case configs.Frozen, configs.Thawed:
+ if err := writeFile(path, "freezer.state", string(cgroup.Resources.Freezer)); err != nil {
+ return err
+ }
+
+ for {
+ state, err := readFile(path, "freezer.state")
+ if err != nil {
+ return err
+ }
+ if strings.TrimSpace(state) == string(cgroup.Resources.Freezer) {
+ break
+ }
+ time.Sleep(1 * time.Millisecond)
+ }
+ case configs.Undefined:
+ return nil
+ default:
+ return fmt.Errorf("Invalid argument '%s' to freezer.state", string(cgroup.Resources.Freezer))
+ }
+
+ return nil
+}
+
+func (s *FreezerGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("freezer"))
+}
+
+func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go
new file mode 100644
index 0000000000..77708db9a6
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go
@@ -0,0 +1,47 @@
+// +build linux
+
+package fs
+
+import (
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func TestFreezerSetState(t *testing.T) {
+ helper := NewCgroupTestUtil("freezer", t)
+ defer helper.cleanup()
+
+ helper.writeFileContents(map[string]string{
+ "freezer.state": string(configs.Frozen),
+ })
+
+ helper.CgroupData.config.Resources.Freezer = configs.Thawed
+ freezer := &FreezerGroup{}
+ if err := freezer.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamString(helper.CgroupPath, "freezer.state")
+ if err != nil {
+ t.Fatalf("Failed to parse freezer.state - %s", err)
+ }
+ if value != string(configs.Thawed) {
+ t.Fatal("Got the wrong value, set freezer.state failed.")
+ }
+}
+
+func TestFreezerSetInvalidState(t *testing.T) {
+ helper := NewCgroupTestUtil("freezer", t)
+ defer helper.cleanup()
+
+ const (
+ invalidArg configs.FreezerState = "Invalid"
+ )
+
+ helper.CgroupData.config.Resources.Freezer = invalidArg
+ freezer := &FreezerGroup{}
+ if err := freezer.Set(helper.CgroupPath, helper.CgroupData.config); err == nil {
+ t.Fatal("Failed to return invalid argument error")
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs_unsupported.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs_unsupported.go
new file mode 100644
index 0000000000..3ef9e03158
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs_unsupported.go
@@ -0,0 +1,3 @@
+// +build !linux
+
+package fs
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go
new file mode 100644
index 0000000000..2f9727719d
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go
@@ -0,0 +1,71 @@
+// +build linux
+
+package fs
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type HugetlbGroup struct {
+}
+
+func (s *HugetlbGroup) Name() string {
+ return "hugetlb"
+}
+
+func (s *HugetlbGroup) Apply(d *cgroupData) error {
+ _, err := d.join("hugetlb")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
+
+func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
+ for _, hugetlb := range cgroup.Resources.HugetlbLimit {
+ if err := writeFile(path, strings.Join([]string{"hugetlb", hugetlb.Pagesize, "limit_in_bytes"}, "."), strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *HugetlbGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("hugetlb"))
+}
+
+func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error {
+ hugetlbStats := cgroups.HugetlbStats{}
+ for _, pageSize := range HugePageSizes {
+ usage := strings.Join([]string{"hugetlb", pageSize, "usage_in_bytes"}, ".")
+ value, err := getCgroupParamUint(path, usage)
+ if err != nil {
+ return fmt.Errorf("failed to parse %s - %v", usage, err)
+ }
+ hugetlbStats.Usage = value
+
+ maxUsage := strings.Join([]string{"hugetlb", pageSize, "max_usage_in_bytes"}, ".")
+ value, err = getCgroupParamUint(path, maxUsage)
+ if err != nil {
+ return fmt.Errorf("failed to parse %s - %v", maxUsage, err)
+ }
+ hugetlbStats.MaxUsage = value
+
+ failcnt := strings.Join([]string{"hugetlb", pageSize, "failcnt"}, ".")
+ value, err = getCgroupParamUint(path, failcnt)
+ if err != nil {
+ return fmt.Errorf("failed to parse %s - %v", failcnt, err)
+ }
+ hugetlbStats.Failcnt = value
+
+ stats.HugetlbStats[pageSize] = hugetlbStats
+ }
+
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go
new file mode 100644
index 0000000000..2d41c4eb20
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go
@@ -0,0 +1,154 @@
+// +build linux
+
+package fs
+
+import (
+ "fmt"
+ "strconv"
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+const (
+ hugetlbUsageContents = "128\n"
+ hugetlbMaxUsageContents = "256\n"
+ hugetlbFailcnt = "100\n"
+)
+
+var (
+ usage = "hugetlb.%s.usage_in_bytes"
+ limit = "hugetlb.%s.limit_in_bytes"
+ maxUsage = "hugetlb.%s.max_usage_in_bytes"
+ failcnt = "hugetlb.%s.failcnt"
+)
+
+func TestHugetlbSetHugetlb(t *testing.T) {
+ helper := NewCgroupTestUtil("hugetlb", t)
+ defer helper.cleanup()
+
+ const (
+ hugetlbBefore = 256
+ hugetlbAfter = 512
+ )
+
+ for _, pageSize := range HugePageSizes {
+ helper.writeFileContents(map[string]string{
+ fmt.Sprintf(limit, pageSize): strconv.Itoa(hugetlbBefore),
+ })
+ }
+
+ for _, pageSize := range HugePageSizes {
+ helper.CgroupData.config.Resources.HugetlbLimit = []*configs.HugepageLimit{
+ {
+ Pagesize: pageSize,
+ Limit: hugetlbAfter,
+ },
+ }
+ hugetlb := &HugetlbGroup{}
+ if err := hugetlb.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ for _, pageSize := range HugePageSizes {
+ limit := fmt.Sprintf(limit, pageSize)
+ value, err := getCgroupParamUint(helper.CgroupPath, limit)
+ if err != nil {
+ t.Fatalf("Failed to parse %s - %s", limit, err)
+ }
+ if value != hugetlbAfter {
+ t.Fatalf("Set hugetlb.limit_in_bytes failed. Expected: %v, Got: %v", hugetlbAfter, value)
+ }
+ }
+}
+
+func TestHugetlbStats(t *testing.T) {
+ helper := NewCgroupTestUtil("hugetlb", t)
+ defer helper.cleanup()
+ for _, pageSize := range HugePageSizes {
+ helper.writeFileContents(map[string]string{
+ fmt.Sprintf(usage, pageSize): hugetlbUsageContents,
+ fmt.Sprintf(maxUsage, pageSize): hugetlbMaxUsageContents,
+ fmt.Sprintf(failcnt, pageSize): hugetlbFailcnt,
+ })
+ }
+
+ hugetlb := &HugetlbGroup{}
+ actualStats := *cgroups.NewStats()
+ err := hugetlb.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expectedStats := cgroups.HugetlbStats{Usage: 128, MaxUsage: 256, Failcnt: 100}
+ for _, pageSize := range HugePageSizes {
+ expectHugetlbStatEquals(t, expectedStats, actualStats.HugetlbStats[pageSize])
+ }
+}
+
+func TestHugetlbStatsNoUsageFile(t *testing.T) {
+ helper := NewCgroupTestUtil("hugetlb", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ maxUsage: hugetlbMaxUsageContents,
+ })
+
+ hugetlb := &HugetlbGroup{}
+ actualStats := *cgroups.NewStats()
+ err := hugetlb.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestHugetlbStatsNoMaxUsageFile(t *testing.T) {
+ helper := NewCgroupTestUtil("hugetlb", t)
+ defer helper.cleanup()
+ for _, pageSize := range HugePageSizes {
+ helper.writeFileContents(map[string]string{
+ fmt.Sprintf(usage, pageSize): hugetlbUsageContents,
+ })
+ }
+
+ hugetlb := &HugetlbGroup{}
+ actualStats := *cgroups.NewStats()
+ err := hugetlb.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestHugetlbStatsBadUsageFile(t *testing.T) {
+ helper := NewCgroupTestUtil("hugetlb", t)
+ defer helper.cleanup()
+ for _, pageSize := range HugePageSizes {
+ helper.writeFileContents(map[string]string{
+ fmt.Sprintf(usage, pageSize): "bad",
+ maxUsage: hugetlbMaxUsageContents,
+ })
+ }
+
+ hugetlb := &HugetlbGroup{}
+ actualStats := *cgroups.NewStats()
+ err := hugetlb.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestHugetlbStatsBadMaxUsageFile(t *testing.T) {
+ helper := NewCgroupTestUtil("hugetlb", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ usage: hugetlbUsageContents,
+ maxUsage: "bad",
+ })
+
+ hugetlb := &HugetlbGroup{}
+ actualStats := *cgroups.NewStats()
+ err := hugetlb.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go
new file mode 100644
index 0000000000..118cce8f9a
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go
@@ -0,0 +1,301 @@
+// +build linux
+
+package fs
+
+import (
+ "bufio"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "syscall"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+const (
+ cgroupKernelMemoryLimit = "memory.kmem.limit_in_bytes"
+ cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes"
+ cgroupMemoryLimit = "memory.limit_in_bytes"
+)
+
+type MemoryGroup struct {
+}
+
+func (s *MemoryGroup) Name() string {
+ return "memory"
+}
+
+func (s *MemoryGroup) Apply(d *cgroupData) (err error) {
+ path, err := d.path("memory")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ } else if path == "" {
+ return nil
+ }
+ if memoryAssigned(d.config) {
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return err
+ }
+ }
+ if d.config.KernelMemory != 0 {
+ if err := EnableKernelMemoryAccounting(path); err != nil {
+ return err
+ }
+ }
+ }
+ defer func() {
+ if err != nil {
+ os.RemoveAll(path)
+ }
+ }()
+
+ // We need to join memory cgroup after set memory limits, because
+ // kmem.limit_in_bytes can only be set when the cgroup is empty.
+ _, err = d.join("memory")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
+
+func EnableKernelMemoryAccounting(path string) error {
+ // Check if kernel memory is enabled
+ // We have to limit the kernel memory here as it won't be accounted at all
+ // until a limit is set on the cgroup and limit cannot be set once the
+ // cgroup has children, or if there are already tasks in the cgroup.
+ for _, i := range []int64{1, -1} {
+ if err := setKernelMemory(path, i); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func setKernelMemory(path string, kernelMemoryLimit int64) error {
+ if path == "" {
+ return fmt.Errorf("no such directory for %s", cgroupKernelMemoryLimit)
+ }
+ if !cgroups.PathExists(filepath.Join(path, cgroupKernelMemoryLimit)) {
+ // kernel memory is not enabled on the system so we should do nothing
+ return nil
+ }
+ if err := ioutil.WriteFile(filepath.Join(path, cgroupKernelMemoryLimit), []byte(strconv.FormatInt(kernelMemoryLimit, 10)), 0700); err != nil {
+ // Check if the error number returned by the syscall is "EBUSY"
+ // The EBUSY signal is returned on attempts to write to the
+ // memory.kmem.limit_in_bytes file if the cgroup has children or
+ // once tasks have been attached to the cgroup
+ if pathErr, ok := err.(*os.PathError); ok {
+ if errNo, ok := pathErr.Err.(syscall.Errno); ok {
+ if errNo == syscall.EBUSY {
+ return fmt.Errorf("failed to set %s, because either tasks have already joined this cgroup or it has children", cgroupKernelMemoryLimit)
+ }
+ }
+ }
+ return fmt.Errorf("failed to write %v to %v: %v", kernelMemoryLimit, cgroupKernelMemoryLimit, err)
+ }
+ return nil
+}
+
+func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error {
+ // If the memory update is set to -1 we should also
+ // set swap to -1, it means unlimited memory.
+ if cgroup.Resources.Memory == -1 {
+ // Only set swap if it's enabled in kernel
+ if cgroups.PathExists(filepath.Join(path, cgroupMemorySwapLimit)) {
+ cgroup.Resources.MemorySwap = -1
+ }
+ }
+
+ // When memory and swap memory are both set, we need to handle the cases
+ // for updating container.
+ if cgroup.Resources.Memory != 0 && cgroup.Resources.MemorySwap != 0 {
+ memoryUsage, err := getMemoryData(path, "")
+ if err != nil {
+ return err
+ }
+
+ // When update memory limit, we should adapt the write sequence
+ // for memory and swap memory, so it won't fail because the new
+ // value and the old value don't fit kernel's validation.
+ if cgroup.Resources.MemorySwap == -1 || memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) {
+ if err := writeFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
+ return err
+ }
+ if err := writeFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
+ return err
+ }
+ } else {
+ if err := writeFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
+ return err
+ }
+ if err := writeFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
+ return err
+ }
+ }
+ } else {
+ if cgroup.Resources.Memory != 0 {
+ if err := writeFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
+ return err
+ }
+ }
+ if cgroup.Resources.MemorySwap != 0 {
+ if err := writeFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
+ if err := setMemoryAndSwap(path, cgroup); err != nil {
+ return err
+ }
+
+ if cgroup.Resources.KernelMemory != 0 {
+ if err := setKernelMemory(path, cgroup.Resources.KernelMemory); err != nil {
+ return err
+ }
+ }
+
+ if cgroup.Resources.MemoryReservation != 0 {
+ if err := writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemoryReservation, 10)); err != nil {
+ return err
+ }
+ }
+
+ if cgroup.Resources.KernelMemoryTCP != 0 {
+ if err := writeFile(path, "memory.kmem.tcp.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemoryTCP, 10)); err != nil {
+ return err
+ }
+ }
+ if cgroup.Resources.OomKillDisable {
+ if err := writeFile(path, "memory.oom_control", "1"); err != nil {
+ return err
+ }
+ }
+ if cgroup.Resources.MemorySwappiness == nil || int64(*cgroup.Resources.MemorySwappiness) == -1 {
+ return nil
+ } else if *cgroup.Resources.MemorySwappiness <= 100 {
+ if err := writeFile(path, "memory.swappiness", strconv.FormatUint(*cgroup.Resources.MemorySwappiness, 10)); err != nil {
+ return err
+ }
+ } else {
+ return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", *cgroup.Resources.MemorySwappiness)
+ }
+
+ return nil
+}
+
+func (s *MemoryGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("memory"))
+}
+
+func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
+ // Set stats from memory.stat.
+ statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+ defer statsFile.Close()
+
+ sc := bufio.NewScanner(statsFile)
+ for sc.Scan() {
+ t, v, err := getCgroupParamKeyValue(sc.Text())
+ if err != nil {
+ return fmt.Errorf("failed to parse memory.stat (%q) - %v", sc.Text(), err)
+ }
+ stats.MemoryStats.Stats[t] = v
+ }
+ stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
+
+ memoryUsage, err := getMemoryData(path, "")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.Usage = memoryUsage
+ swapUsage, err := getMemoryData(path, "memsw")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.SwapUsage = swapUsage
+ kernelUsage, err := getMemoryData(path, "kmem")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.KernelUsage = kernelUsage
+ kernelTCPUsage, err := getMemoryData(path, "kmem.tcp")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.KernelTCPUsage = kernelTCPUsage
+
+ return nil
+}
+
+func memoryAssigned(cgroup *configs.Cgroup) bool {
+ return cgroup.Resources.Memory != 0 ||
+ cgroup.Resources.MemoryReservation != 0 ||
+ cgroup.Resources.MemorySwap > 0 ||
+ cgroup.Resources.KernelMemory > 0 ||
+ cgroup.Resources.KernelMemoryTCP > 0 ||
+ cgroup.Resources.OomKillDisable ||
+ (cgroup.Resources.MemorySwappiness != nil && int64(*cgroup.Resources.MemorySwappiness) != -1)
+}
+
+func getMemoryData(path, name string) (cgroups.MemoryData, error) {
+ memoryData := cgroups.MemoryData{}
+
+ moduleName := "memory"
+ if name != "" {
+ moduleName = strings.Join([]string{"memory", name}, ".")
+ }
+ usage := strings.Join([]string{moduleName, "usage_in_bytes"}, ".")
+ maxUsage := strings.Join([]string{moduleName, "max_usage_in_bytes"}, ".")
+ failcnt := strings.Join([]string{moduleName, "failcnt"}, ".")
+ limit := strings.Join([]string{moduleName, "limit_in_bytes"}, ".")
+
+ value, err := getCgroupParamUint(path, usage)
+ if err != nil {
+ if moduleName != "memory" && os.IsNotExist(err) {
+ return cgroups.MemoryData{}, nil
+ }
+ return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", usage, err)
+ }
+ memoryData.Usage = value
+ value, err = getCgroupParamUint(path, maxUsage)
+ if err != nil {
+ if moduleName != "memory" && os.IsNotExist(err) {
+ return cgroups.MemoryData{}, nil
+ }
+ return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", maxUsage, err)
+ }
+ memoryData.MaxUsage = value
+ value, err = getCgroupParamUint(path, failcnt)
+ if err != nil {
+ if moduleName != "memory" && os.IsNotExist(err) {
+ return cgroups.MemoryData{}, nil
+ }
+ return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", failcnt, err)
+ }
+ memoryData.Failcnt = value
+ value, err = getCgroupParamUint(path, limit)
+ if err != nil {
+ if moduleName != "memory" && os.IsNotExist(err) {
+ return cgroups.MemoryData{}, nil
+ }
+ return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", limit, err)
+ }
+ memoryData.Limit = value
+
+ return memoryData, nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go
new file mode 100644
index 0000000000..4b656dc628
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go
@@ -0,0 +1,453 @@
+// +build linux
+
+package fs
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+)
+
+const (
+ memoryStatContents = `cache 512
+rss 1024`
+ memoryUsageContents = "2048\n"
+ memoryMaxUsageContents = "4096\n"
+ memoryFailcnt = "100\n"
+ memoryLimitContents = "8192\n"
+)
+
+func TestMemorySetMemory(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+
+ const (
+ memoryBefore = 314572800 // 300M
+ memoryAfter = 524288000 // 500M
+ reservationBefore = 209715200 // 200M
+ reservationAfter = 314572800 // 300M
+ )
+
+ helper.writeFileContents(map[string]string{
+ "memory.limit_in_bytes": strconv.Itoa(memoryBefore),
+ "memory.soft_limit_in_bytes": strconv.Itoa(reservationBefore),
+ })
+
+ helper.CgroupData.config.Resources.Memory = memoryAfter
+ helper.CgroupData.config.Resources.MemoryReservation = reservationAfter
+ memory := &MemoryGroup{}
+ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "memory.limit_in_bytes")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.limit_in_bytes - %s", err)
+ }
+ if value != memoryAfter {
+ t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.")
+ }
+
+ value, err = getCgroupParamUint(helper.CgroupPath, "memory.soft_limit_in_bytes")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.soft_limit_in_bytes - %s", err)
+ }
+ if value != reservationAfter {
+ t.Fatal("Got the wrong value, set memory.soft_limit_in_bytes failed.")
+ }
+}
+
+func TestMemorySetMemoryswap(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+
+ const (
+ memoryswapBefore = 314572800 // 300M
+ memoryswapAfter = 524288000 // 500M
+ )
+
+ helper.writeFileContents(map[string]string{
+ "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore),
+ })
+
+ helper.CgroupData.config.Resources.MemorySwap = memoryswapAfter
+ memory := &MemoryGroup{}
+ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "memory.memsw.limit_in_bytes")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.memsw.limit_in_bytes - %s", err)
+ }
+ if value != memoryswapAfter {
+ t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.")
+ }
+}
+
+func TestMemorySetMemoryLargerThanSwap(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+
+ const (
+ memoryBefore = 314572800 // 300M
+ memoryswapBefore = 524288000 // 500M
+ memoryAfter = 629145600 // 600M
+ memoryswapAfter = 838860800 // 800M
+ )
+
+ helper.writeFileContents(map[string]string{
+ "memory.limit_in_bytes": strconv.Itoa(memoryBefore),
+ "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore),
+ // Set will call getMemoryData when memory and swap memory are
+ // both set, fake these fields so we don't get error.
+ "memory.usage_in_bytes": "0",
+ "memory.max_usage_in_bytes": "0",
+ "memory.failcnt": "0",
+ })
+
+ helper.CgroupData.config.Resources.Memory = memoryAfter
+ helper.CgroupData.config.Resources.MemorySwap = memoryswapAfter
+ memory := &MemoryGroup{}
+ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "memory.limit_in_bytes")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.limit_in_bytes - %s", err)
+ }
+ if value != memoryAfter {
+ t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.")
+ }
+ value, err = getCgroupParamUint(helper.CgroupPath, "memory.memsw.limit_in_bytes")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.memsw.limit_in_bytes - %s", err)
+ }
+ if value != memoryswapAfter {
+ t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.")
+ }
+}
+
+func TestMemorySetSwapSmallerThanMemory(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+
+ const (
+ memoryBefore = 629145600 // 600M
+ memoryswapBefore = 838860800 // 800M
+ memoryAfter = 314572800 // 300M
+ memoryswapAfter = 524288000 // 500M
+ )
+
+ helper.writeFileContents(map[string]string{
+ "memory.limit_in_bytes": strconv.Itoa(memoryBefore),
+ "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore),
+ // Set will call getMemoryData when memory and swap memory are
+ // both set, fake these fields so we don't get error.
+ "memory.usage_in_bytes": "0",
+ "memory.max_usage_in_bytes": "0",
+ "memory.failcnt": "0",
+ })
+
+ helper.CgroupData.config.Resources.Memory = memoryAfter
+ helper.CgroupData.config.Resources.MemorySwap = memoryswapAfter
+ memory := &MemoryGroup{}
+ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "memory.limit_in_bytes")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.limit_in_bytes - %s", err)
+ }
+ if value != memoryAfter {
+ t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.")
+ }
+ value, err = getCgroupParamUint(helper.CgroupPath, "memory.memsw.limit_in_bytes")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.memsw.limit_in_bytes - %s", err)
+ }
+ if value != memoryswapAfter {
+ t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.")
+ }
+}
+
+func TestMemorySetKernelMemory(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+
+ const (
+ kernelMemoryBefore = 314572800 // 300M
+ kernelMemoryAfter = 524288000 // 500M
+ )
+
+ helper.writeFileContents(map[string]string{
+ "memory.kmem.limit_in_bytes": strconv.Itoa(kernelMemoryBefore),
+ })
+
+ helper.CgroupData.config.Resources.KernelMemory = kernelMemoryAfter
+ memory := &MemoryGroup{}
+ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "memory.kmem.limit_in_bytes")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.kmem.limit_in_bytes - %s", err)
+ }
+ if value != kernelMemoryAfter {
+ t.Fatal("Got the wrong value, set memory.kmem.limit_in_bytes failed.")
+ }
+}
+
+func TestMemorySetKernelMemoryTCP(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+
+ const (
+ kernelMemoryTCPBefore = 314572800 // 300M
+ kernelMemoryTCPAfter = 524288000 // 500M
+ )
+
+ helper.writeFileContents(map[string]string{
+ "memory.kmem.tcp.limit_in_bytes": strconv.Itoa(kernelMemoryTCPBefore),
+ })
+
+ helper.CgroupData.config.Resources.KernelMemoryTCP = kernelMemoryTCPAfter
+ memory := &MemoryGroup{}
+ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "memory.kmem.tcp.limit_in_bytes")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.kmem.tcp.limit_in_bytes - %s", err)
+ }
+ if value != kernelMemoryTCPAfter {
+ t.Fatal("Got the wrong value, set memory.kmem.tcp.limit_in_bytes failed.")
+ }
+}
+
+func TestMemorySetMemorySwappinessDefault(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+
+ swappinessBefore := 60 // default is 60
+ swappinessAfter := uint64(0)
+
+ helper.writeFileContents(map[string]string{
+ "memory.swappiness": strconv.Itoa(swappinessBefore),
+ })
+
+ helper.CgroupData.config.Resources.MemorySwappiness = &swappinessAfter
+ memory := &MemoryGroup{}
+ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "memory.swappiness")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.swappiness - %s", err)
+ }
+ if value != swappinessAfter {
+ t.Fatalf("Got the wrong value (%d), set memory.swappiness = %d failed.", value, swappinessAfter)
+ }
+}
+
+func TestMemoryStats(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "memory.stat": memoryStatContents,
+ "memory.usage_in_bytes": memoryUsageContents,
+ "memory.limit_in_bytes": memoryLimitContents,
+ "memory.max_usage_in_bytes": memoryMaxUsageContents,
+ "memory.failcnt": memoryFailcnt,
+ "memory.memsw.usage_in_bytes": memoryUsageContents,
+ "memory.memsw.max_usage_in_bytes": memoryMaxUsageContents,
+ "memory.memsw.failcnt": memoryFailcnt,
+ "memory.memsw.limit_in_bytes": memoryLimitContents,
+ "memory.kmem.usage_in_bytes": memoryUsageContents,
+ "memory.kmem.max_usage_in_bytes": memoryMaxUsageContents,
+ "memory.kmem.failcnt": memoryFailcnt,
+ "memory.kmem.limit_in_bytes": memoryLimitContents,
+ })
+
+ memory := &MemoryGroup{}
+ actualStats := *cgroups.NewStats()
+ err := memory.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expectedStats := cgroups.MemoryStats{Cache: 512, Usage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, SwapUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, KernelUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, Stats: map[string]uint64{"cache": 512, "rss": 1024}}
+ expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats)
+}
+
+func TestMemoryStatsNoStatFile(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "memory.usage_in_bytes": memoryUsageContents,
+ "memory.max_usage_in_bytes": memoryMaxUsageContents,
+ "memory.limit_in_bytes": memoryLimitContents,
+ })
+
+ memory := &MemoryGroup{}
+ actualStats := *cgroups.NewStats()
+ err := memory.GetStats(helper.CgroupPath, &actualStats)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMemoryStatsNoUsageFile(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "memory.stat": memoryStatContents,
+ "memory.max_usage_in_bytes": memoryMaxUsageContents,
+ "memory.limit_in_bytes": memoryLimitContents,
+ })
+
+ memory := &MemoryGroup{}
+ actualStats := *cgroups.NewStats()
+ err := memory.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestMemoryStatsNoMaxUsageFile(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "memory.stat": memoryStatContents,
+ "memory.usage_in_bytes": memoryUsageContents,
+ "memory.limit_in_bytes": memoryLimitContents,
+ })
+
+ memory := &MemoryGroup{}
+ actualStats := *cgroups.NewStats()
+ err := memory.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestMemoryStatsNoLimitInBytesFile(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "memory.stat": memoryStatContents,
+ "memory.usage_in_bytes": memoryUsageContents,
+ "memory.max_usage_in_bytes": memoryMaxUsageContents,
+ })
+
+ memory := &MemoryGroup{}
+ actualStats := *cgroups.NewStats()
+ err := memory.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestMemoryStatsBadStatFile(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "memory.stat": "rss rss",
+ "memory.usage_in_bytes": memoryUsageContents,
+ "memory.max_usage_in_bytes": memoryMaxUsageContents,
+ "memory.limit_in_bytes": memoryLimitContents,
+ })
+
+ memory := &MemoryGroup{}
+ actualStats := *cgroups.NewStats()
+ err := memory.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestMemoryStatsBadUsageFile(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "memory.stat": memoryStatContents,
+ "memory.usage_in_bytes": "bad",
+ "memory.max_usage_in_bytes": memoryMaxUsageContents,
+ "memory.limit_in_bytes": memoryLimitContents,
+ })
+
+ memory := &MemoryGroup{}
+ actualStats := *cgroups.NewStats()
+ err := memory.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestMemoryStatsBadMaxUsageFile(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "memory.stat": memoryStatContents,
+ "memory.usage_in_bytes": memoryUsageContents,
+ "memory.max_usage_in_bytes": "bad",
+ "memory.limit_in_bytes": memoryLimitContents,
+ })
+
+ memory := &MemoryGroup{}
+ actualStats := *cgroups.NewStats()
+ err := memory.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestMemoryStatsBadLimitInBytesFile(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+ helper.writeFileContents(map[string]string{
+ "memory.stat": memoryStatContents,
+ "memory.usage_in_bytes": memoryUsageContents,
+ "memory.max_usage_in_bytes": memoryMaxUsageContents,
+ "memory.limit_in_bytes": "bad",
+ })
+
+ memory := &MemoryGroup{}
+ actualStats := *cgroups.NewStats()
+ err := memory.GetStats(helper.CgroupPath, &actualStats)
+ if err == nil {
+ t.Fatal("Expected failure")
+ }
+}
+
+func TestMemorySetOomControl(t *testing.T) {
+ helper := NewCgroupTestUtil("memory", t)
+ defer helper.cleanup()
+
+ const (
+ oomKillDisable = 1 // disable oom killer, default is 0
+ )
+
+ helper.writeFileContents(map[string]string{
+ "memory.oom_control": strconv.Itoa(oomKillDisable),
+ })
+
+ memory := &MemoryGroup{}
+ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "memory.oom_control")
+ if err != nil {
+ t.Fatalf("Failed to parse memory.oom_control - %s", err)
+ }
+
+ if value != oomKillDisable {
+ t.Fatalf("Got the wrong value, set memory.oom_control failed.")
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go
new file mode 100644
index 0000000000..d8cf1d87c0
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go
@@ -0,0 +1,40 @@
+// +build linux
+
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type NameGroup struct {
+ GroupName string
+ Join bool
+}
+
+func (s *NameGroup) Name() string {
+ return s.GroupName
+}
+
+func (s *NameGroup) Apply(d *cgroupData) error {
+ if s.Join {
+ // ignore errors if the named cgroup does not exist
+ d.join(s.GroupName)
+ }
+ return nil
+}
+
+func (s *NameGroup) Set(path string, cgroup *configs.Cgroup) error {
+ return nil
+}
+
+func (s *NameGroup) Remove(d *cgroupData) error {
+ if s.Join {
+ removePath(d.path(s.GroupName))
+ }
+ return nil
+}
+
+func (s *NameGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go
new file mode 100644
index 0000000000..8e74b645ea
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go
@@ -0,0 +1,43 @@
+// +build linux
+
+package fs
+
+import (
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type NetClsGroup struct {
+}
+
+func (s *NetClsGroup) Name() string {
+ return "net_cls"
+}
+
+func (s *NetClsGroup) Apply(d *cgroupData) error {
+ _, err := d.join("net_cls")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
+
+func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error {
+ if cgroup.Resources.NetClsClassid != 0 {
+ if err := writeFile(path, "net_cls.classid", strconv.FormatUint(uint64(cgroup.Resources.NetClsClassid), 10)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *NetClsGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("net_cls"))
+}
+
+func (s *NetClsGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go
new file mode 100644
index 0000000000..c00e8a8d6f
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go
@@ -0,0 +1,39 @@
+// +build linux
+
+package fs
+
+import (
+ "strconv"
+ "testing"
+)
+
+const (
+ classidBefore = 0x100002
+ classidAfter = 0x100001
+)
+
+func TestNetClsSetClassid(t *testing.T) {
+ helper := NewCgroupTestUtil("net_cls", t)
+ defer helper.cleanup()
+
+ helper.writeFileContents(map[string]string{
+ "net_cls.classid": strconv.FormatUint(classidBefore, 10),
+ })
+
+ helper.CgroupData.config.Resources.NetClsClassid = classidAfter
+ netcls := &NetClsGroup{}
+ if err := netcls.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ // As we are in mock environment, we can't get correct value of classid from
+ // net_cls.classid.
+ // So. we just judge if we successfully write classid into file
+ value, err := getCgroupParamUint(helper.CgroupPath, "net_cls.classid")
+ if err != nil {
+ t.Fatalf("Failed to parse net_cls.classid - %s", err)
+ }
+ if value != classidAfter {
+ t.Fatal("Got the wrong value, set net_cls.classid failed.")
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go
new file mode 100644
index 0000000000..d0ab2af894
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go
@@ -0,0 +1,41 @@
+// +build linux
+
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type NetPrioGroup struct {
+}
+
+func (s *NetPrioGroup) Name() string {
+ return "net_prio"
+}
+
+func (s *NetPrioGroup) Apply(d *cgroupData) error {
+ _, err := d.join("net_prio")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
+
+func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error {
+ for _, prioMap := range cgroup.Resources.NetPrioIfpriomap {
+ if err := writeFile(path, "net_prio.ifpriomap", prioMap.CgroupString()); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *NetPrioGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("net_prio"))
+}
+
+func (s *NetPrioGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go
new file mode 100644
index 0000000000..efbf0639a0
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go
@@ -0,0 +1,38 @@
+// +build linux
+
+package fs
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+var (
+ prioMap = []*configs.IfPrioMap{
+ {
+ Interface: "test",
+ Priority: 5,
+ },
+ }
+)
+
+func TestNetPrioSetIfPrio(t *testing.T) {
+ helper := NewCgroupTestUtil("net_prio", t)
+ defer helper.cleanup()
+
+ helper.CgroupData.config.Resources.NetPrioIfpriomap = prioMap
+ netPrio := &NetPrioGroup{}
+ if err := netPrio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamString(helper.CgroupPath, "net_prio.ifpriomap")
+ if err != nil {
+ t.Fatalf("Failed to parse net_prio.ifpriomap - %s", err)
+ }
+ if !strings.Contains(value, "test 5") {
+ t.Fatal("Got the wrong value, set net_prio.ifpriomap failed.")
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go
new file mode 100644
index 0000000000..5693676d3a
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go
@@ -0,0 +1,35 @@
+// +build linux
+
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type PerfEventGroup struct {
+}
+
+func (s *PerfEventGroup) Name() string {
+ return "perf_event"
+}
+
+func (s *PerfEventGroup) Apply(d *cgroupData) error {
+ // we just want to join this group even though we don't set anything
+ if _, err := d.join("perf_event"); err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
+
+func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error {
+ return nil
+}
+
+func (s *PerfEventGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("perf_event"))
+}
+
+func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go
new file mode 100644
index 0000000000..f1e3720551
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go
@@ -0,0 +1,73 @@
+// +build linux
+
+package fs
+
+import (
+ "fmt"
+ "path/filepath"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type PidsGroup struct {
+}
+
+func (s *PidsGroup) Name() string {
+ return "pids"
+}
+
+func (s *PidsGroup) Apply(d *cgroupData) error {
+ _, err := d.join("pids")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
+
+func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error {
+ if cgroup.Resources.PidsLimit != 0 {
+ // "max" is the fallback value.
+ limit := "max"
+
+ if cgroup.Resources.PidsLimit > 0 {
+ limit = strconv.FormatInt(cgroup.Resources.PidsLimit, 10)
+ }
+
+ if err := writeFile(path, "pids.max", limit); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *PidsGroup) Remove(d *cgroupData) error {
+ return removePath(d.path("pids"))
+}
+
+func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error {
+ current, err := getCgroupParamUint(path, "pids.current")
+ if err != nil {
+ return fmt.Errorf("failed to parse pids.current - %s", err)
+ }
+
+ maxString, err := getCgroupParamString(path, "pids.max")
+ if err != nil {
+ return fmt.Errorf("failed to parse pids.max - %s", err)
+ }
+
+ // Default if pids.max == "max" is 0 -- which represents "no limit".
+ var max uint64
+ if maxString != "max" {
+ max, err = parseUint(maxString, 10, 64)
+ if err != nil {
+ return fmt.Errorf("failed to parse pids.max - unable to parse %q as a uint from Cgroup file %q", maxString, filepath.Join(path, "pids.max"))
+ }
+ }
+
+ stats.PidsStats.Current = current
+ stats.PidsStats.Limit = max
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go
new file mode 100644
index 0000000000..10671247ba
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go
@@ -0,0 +1,111 @@
+// +build linux
+
+package fs
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+)
+
+const (
+ maxUnlimited = -1
+ maxLimited = 1024
+)
+
+func TestPidsSetMax(t *testing.T) {
+ helper := NewCgroupTestUtil("pids", t)
+ defer helper.cleanup()
+
+ helper.writeFileContents(map[string]string{
+ "pids.max": "max",
+ })
+
+ helper.CgroupData.config.Resources.PidsLimit = maxLimited
+ pids := &PidsGroup{}
+ if err := pids.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamUint(helper.CgroupPath, "pids.max")
+ if err != nil {
+ t.Fatalf("Failed to parse pids.max - %s", err)
+ }
+
+ if value != maxLimited {
+ t.Fatalf("Expected %d, got %d for setting pids.max - limited", maxLimited, value)
+ }
+}
+
+func TestPidsSetUnlimited(t *testing.T) {
+ helper := NewCgroupTestUtil("pids", t)
+ defer helper.cleanup()
+
+ helper.writeFileContents(map[string]string{
+ "pids.max": strconv.Itoa(maxLimited),
+ })
+
+ helper.CgroupData.config.Resources.PidsLimit = maxUnlimited
+ pids := &PidsGroup{}
+ if err := pids.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
+ t.Fatal(err)
+ }
+
+ value, err := getCgroupParamString(helper.CgroupPath, "pids.max")
+ if err != nil {
+ t.Fatalf("Failed to parse pids.max - %s", err)
+ }
+
+ if value != "max" {
+ t.Fatalf("Expected %s, got %s for setting pids.max - unlimited", "max", value)
+ }
+}
+
+func TestPidsStats(t *testing.T) {
+ helper := NewCgroupTestUtil("pids", t)
+ defer helper.cleanup()
+
+ helper.writeFileContents(map[string]string{
+ "pids.current": strconv.Itoa(1337),
+ "pids.max": strconv.Itoa(maxLimited),
+ })
+
+ pids := &PidsGroup{}
+ stats := *cgroups.NewStats()
+ if err := pids.GetStats(helper.CgroupPath, &stats); err != nil {
+ t.Fatal(err)
+ }
+
+ if stats.PidsStats.Current != 1337 {
+ t.Fatalf("Expected %d, got %d for pids.current", 1337, stats.PidsStats.Current)
+ }
+
+ if stats.PidsStats.Limit != maxLimited {
+ t.Fatalf("Expected %d, got %d for pids.max", maxLimited, stats.PidsStats.Limit)
+ }
+}
+
+func TestPidsStatsUnlimited(t *testing.T) {
+ helper := NewCgroupTestUtil("pids", t)
+ defer helper.cleanup()
+
+ helper.writeFileContents(map[string]string{
+ "pids.current": strconv.Itoa(4096),
+ "pids.max": "max",
+ })
+
+ pids := &PidsGroup{}
+ stats := *cgroups.NewStats()
+ if err := pids.GetStats(helper.CgroupPath, &stats); err != nil {
+ t.Fatal(err)
+ }
+
+ if stats.PidsStats.Current != 4096 {
+ t.Fatalf("Expected %d, got %d for pids.current", 4096, stats.PidsStats.Current)
+ }
+
+ if stats.PidsStats.Limit != 0 {
+ t.Fatalf("Expected %d, got %d for pids.max", 0, stats.PidsStats.Limit)
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go
new file mode 100644
index 0000000000..d0ab047418
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go
@@ -0,0 +1,117 @@
+// +build linux
+
+package fs
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+)
+
+func blkioStatEntryEquals(expected, actual []cgroups.BlkioStatEntry) error {
+ if len(expected) != len(actual) {
+ return fmt.Errorf("blkioStatEntries length do not match")
+ }
+ for i, expValue := range expected {
+ actValue := actual[i]
+ if expValue != actValue {
+ return fmt.Errorf("Expected blkio stat entry %v but found %v", expValue, actValue)
+ }
+ }
+ return nil
+}
+
+func expectBlkioStatsEquals(t *testing.T, expected, actual cgroups.BlkioStats) {
+ if err := blkioStatEntryEquals(expected.IoServiceBytesRecursive, actual.IoServiceBytesRecursive); err != nil {
+ logrus.Printf("blkio IoServiceBytesRecursive do not match - %s\n", err)
+ t.Fail()
+ }
+
+ if err := blkioStatEntryEquals(expected.IoServicedRecursive, actual.IoServicedRecursive); err != nil {
+ logrus.Printf("blkio IoServicedRecursive do not match - %s\n", err)
+ t.Fail()
+ }
+
+ if err := blkioStatEntryEquals(expected.IoQueuedRecursive, actual.IoQueuedRecursive); err != nil {
+ logrus.Printf("blkio IoQueuedRecursive do not match - %s\n", err)
+ t.Fail()
+ }
+
+ if err := blkioStatEntryEquals(expected.SectorsRecursive, actual.SectorsRecursive); err != nil {
+ logrus.Printf("blkio SectorsRecursive do not match - %s\n", err)
+ t.Fail()
+ }
+
+ if err := blkioStatEntryEquals(expected.IoServiceTimeRecursive, actual.IoServiceTimeRecursive); err != nil {
+ logrus.Printf("blkio IoServiceTimeRecursive do not match - %s\n", err)
+ t.Fail()
+ }
+
+ if err := blkioStatEntryEquals(expected.IoWaitTimeRecursive, actual.IoWaitTimeRecursive); err != nil {
+ logrus.Printf("blkio IoWaitTimeRecursive do not match - %s\n", err)
+ t.Fail()
+ }
+
+ if err := blkioStatEntryEquals(expected.IoMergedRecursive, actual.IoMergedRecursive); err != nil {
+ logrus.Printf("blkio IoMergedRecursive do not match - %v vs %v\n", expected.IoMergedRecursive, actual.IoMergedRecursive)
+ t.Fail()
+ }
+
+ if err := blkioStatEntryEquals(expected.IoTimeRecursive, actual.IoTimeRecursive); err != nil {
+ logrus.Printf("blkio IoTimeRecursive do not match - %s\n", err)
+ t.Fail()
+ }
+}
+
+func expectThrottlingDataEquals(t *testing.T, expected, actual cgroups.ThrottlingData) {
+ if expected != actual {
+ logrus.Printf("Expected throttling data %v but found %v\n", expected, actual)
+ t.Fail()
+ }
+}
+
+func expectHugetlbStatEquals(t *testing.T, expected, actual cgroups.HugetlbStats) {
+ if expected != actual {
+ logrus.Printf("Expected hugetlb stats %v but found %v\n", expected, actual)
+ t.Fail()
+ }
+}
+
+func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats) {
+ expectMemoryDataEquals(t, expected.Usage, actual.Usage)
+ expectMemoryDataEquals(t, expected.SwapUsage, actual.SwapUsage)
+ expectMemoryDataEquals(t, expected.KernelUsage, actual.KernelUsage)
+
+ for key, expValue := range expected.Stats {
+ actValue, ok := actual.Stats[key]
+ if !ok {
+ logrus.Printf("Expected memory stat key %s not found\n", key)
+ t.Fail()
+ }
+ if expValue != actValue {
+ logrus.Printf("Expected memory stat value %d but found %d\n", expValue, actValue)
+ t.Fail()
+ }
+ }
+}
+
+func expectMemoryDataEquals(t *testing.T, expected, actual cgroups.MemoryData) {
+ if expected.Usage != actual.Usage {
+ logrus.Printf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage)
+ t.Fail()
+ }
+ if expected.MaxUsage != actual.MaxUsage {
+ logrus.Printf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage)
+ t.Fail()
+ }
+ if expected.Failcnt != actual.Failcnt {
+ logrus.Printf("Expected memory failcnt %d but found %d\n", expected.Failcnt, actual.Failcnt)
+ t.Fail()
+ }
+ if expected.Limit != actual.Limit {
+ logrus.Printf("Expected memory limit %d but found %d\n", expected.Limit, actual.Limit)
+ t.Fail()
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go
new file mode 100644
index 0000000000..7067e799fb
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go
@@ -0,0 +1,67 @@
+// +build linux
+
+/*
+Utility for testing cgroup operations.
+
+Creates a mock of the cgroup filesystem for the duration of the test.
+*/
+package fs
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type cgroupTestUtil struct {
+ // cgroup data to use in tests.
+ CgroupData *cgroupData
+
+ // Path to the mock cgroup directory.
+ CgroupPath string
+
+ // Temporary directory to store mock cgroup filesystem.
+ tempDir string
+ t *testing.T
+}
+
+// Creates a new test util for the specified subsystem
+func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil {
+ d := &cgroupData{
+ config: &configs.Cgroup{},
+ }
+ d.config.Resources = &configs.Resources{}
+ tempDir, err := ioutil.TempDir("", "cgroup_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ d.root = tempDir
+ testCgroupPath := filepath.Join(d.root, subsystem)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Ensure the full mock cgroup path exists.
+ err = os.MkdirAll(testCgroupPath, 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t}
+}
+
+func (c *cgroupTestUtil) cleanup() {
+ os.RemoveAll(c.tempDir)
+}
+
+// Write the specified contents on the mock of the specified cgroup files.
+func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) {
+ for file, contents := range fileContents {
+ err := writeFile(c.CgroupPath, file, contents)
+ if err != nil {
+ c.t.Fatal(err)
+ }
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils.go
new file mode 100644
index 0000000000..5ff0a16150
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils.go
@@ -0,0 +1,78 @@
+// +build linux
+
+package fs
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "strconv"
+ "strings"
+)
+
+var (
+ ErrNotValidFormat = errors.New("line is not a valid key value format")
+)
+
+// Saturates negative values at zero and returns a uint64.
+// Due to kernel bugs, some of the memory cgroup stats can be negative.
+func parseUint(s string, base, bitSize int) (uint64, error) {
+ value, err := strconv.ParseUint(s, base, bitSize)
+ if err != nil {
+ intValue, intErr := strconv.ParseInt(s, base, bitSize)
+ // 1. Handle negative values greater than MinInt64 (and)
+ // 2. Handle negative values lesser than MinInt64
+ if intErr == nil && intValue < 0 {
+ return 0, nil
+ } else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 {
+ return 0, nil
+ }
+
+ return value, err
+ }
+
+ return value, nil
+}
+
+// Parses a cgroup param and returns as name, value
+// i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234
+func getCgroupParamKeyValue(t string) (string, uint64, error) {
+ parts := strings.Fields(t)
+ switch len(parts) {
+ case 2:
+ value, err := parseUint(parts[1], 10, 64)
+ if err != nil {
+ return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err)
+ }
+
+ return parts[0], value, nil
+ default:
+ return "", 0, ErrNotValidFormat
+ }
+}
+
+// Gets a single uint64 value from the specified cgroup file.
+func getCgroupParamUint(cgroupPath, cgroupFile string) (uint64, error) {
+ fileName := filepath.Join(cgroupPath, cgroupFile)
+ contents, err := ioutil.ReadFile(fileName)
+ if err != nil {
+ return 0, err
+ }
+
+ res, err := parseUint(strings.TrimSpace(string(contents)), 10, 64)
+ if err != nil {
+ return res, fmt.Errorf("unable to parse %q as a uint from Cgroup file %q", string(contents), fileName)
+ }
+ return res, nil
+}
+
+// Gets a string value from the specified cgroup file
+func getCgroupParamString(cgroupPath, cgroupFile string) (string, error) {
+ contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile))
+ if err != nil {
+ return "", err
+ }
+
+ return strings.TrimSpace(string(contents)), nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go
new file mode 100644
index 0000000000..99cdc18e07
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go
@@ -0,0 +1,97 @@
+// +build linux
+
+package fs
+
+import (
+ "io/ioutil"
+ "math"
+ "os"
+ "path/filepath"
+ "strconv"
+ "testing"
+)
+
+const (
+ cgroupFile = "cgroup.file"
+ floatValue = 2048.0
+ floatString = "2048"
+)
+
+func TestGetCgroupParamsInt(t *testing.T) {
+ // Setup tempdir.
+ tempDir, err := ioutil.TempDir("", "cgroup_utils_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempDir)
+ tempFile := filepath.Join(tempDir, cgroupFile)
+
+ // Success.
+ err = ioutil.WriteFile(tempFile, []byte(floatString), 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ value, err := getCgroupParamUint(tempDir, cgroupFile)
+ if err != nil {
+ t.Fatal(err)
+ } else if value != floatValue {
+ t.Fatalf("Expected %d to equal %f", value, floatValue)
+ }
+
+ // Success with new line.
+ err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ value, err = getCgroupParamUint(tempDir, cgroupFile)
+ if err != nil {
+ t.Fatal(err)
+ } else if value != floatValue {
+ t.Fatalf("Expected %d to equal %f", value, floatValue)
+ }
+
+ // Success with negative values
+ err = ioutil.WriteFile(tempFile, []byte("-12345"), 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ value, err = getCgroupParamUint(tempDir, cgroupFile)
+ if err != nil {
+ t.Fatal(err)
+ } else if value != 0 {
+ t.Fatalf("Expected %d to equal %d", value, 0)
+ }
+
+ // Success with negative values lesser than min int64
+ s := strconv.FormatFloat(math.MinInt64, 'f', -1, 64)
+ err = ioutil.WriteFile(tempFile, []byte(s), 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ value, err = getCgroupParamUint(tempDir, cgroupFile)
+ if err != nil {
+ t.Fatal(err)
+ } else if value != 0 {
+ t.Fatalf("Expected %d to equal %d", value, 0)
+ }
+
+ // Not a float.
+ err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = getCgroupParamUint(tempDir, cgroupFile)
+ if err == nil {
+ t.Fatal("Expecting error, got none")
+ }
+
+ // Unknown file.
+ err = os.Remove(tempFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = getCgroupParamUint(tempDir, cgroupFile)
+ if err == nil {
+ t.Fatal("Expecting error, got none")
+ }
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/rootless.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/rootless.go
new file mode 100644
index 0000000000..b1efbfd999
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/rootless.go
@@ -0,0 +1,128 @@
+// +build linux
+
+package rootless
+
+import (
+ "fmt"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/opencontainers/runc/libcontainer/configs/validate"
+)
+
+// TODO: This is copied from libcontainer/cgroups/fs, which duplicates this code
+// needlessly. We should probably export this list.
+
+var subsystems = []subsystem{
+ &fs.CpusetGroup{},
+ &fs.DevicesGroup{},
+ &fs.MemoryGroup{},
+ &fs.CpuGroup{},
+ &fs.CpuacctGroup{},
+ &fs.PidsGroup{},
+ &fs.BlkioGroup{},
+ &fs.HugetlbGroup{},
+ &fs.NetClsGroup{},
+ &fs.NetPrioGroup{},
+ &fs.PerfEventGroup{},
+ &fs.FreezerGroup{},
+ &fs.NameGroup{GroupName: "name=systemd"},
+}
+
+type subsystem interface {
+ // Name returns the name of the subsystem.
+ Name() string
+
+ // Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
+ GetStats(path string, stats *cgroups.Stats) error
+}
+
+// The noop cgroup manager is used for rootless containers, because we currently
+// cannot manage cgroups if we are in a rootless setup. This manager is chosen
+// by factory if we are in rootless mode. We error out if any cgroup options are
+// set in the config -- this may change in the future with upcoming kernel features
+// like the cgroup namespace.
+
+type Manager struct {
+ Cgroups *configs.Cgroup
+ Paths map[string]string
+}
+
+func (m *Manager) Apply(pid int) error {
+ // If there are no cgroup settings, there's nothing to do.
+ if m.Cgroups == nil {
+ return nil
+ }
+
+ // We can't set paths.
+ // TODO(cyphar): Implement the case where the runner of a rootless container
+ // owns their own cgroup, which would allow us to set up a
+ // cgroup for each path.
+ if m.Cgroups.Paths != nil {
+ return fmt.Errorf("cannot change cgroup path in rootless container")
+ }
+
+ // We load the paths into the manager.
+ paths := make(map[string]string)
+ for _, sys := range subsystems {
+ name := sys.Name()
+
+ path, err := cgroups.GetOwnCgroupPath(name)
+ if err != nil {
+ // Ignore paths we couldn't resolve.
+ continue
+ }
+
+ paths[name] = path
+ }
+
+ m.Paths = paths
+ return nil
+}
+
+func (m *Manager) GetPaths() map[string]string {
+ return m.Paths
+}
+
+func (m *Manager) Set(container *configs.Config) error {
+ // We have to re-do the validation here, since someone might decide to
+ // update a rootless container.
+ return validate.New().Validate(container)
+}
+
+func (m *Manager) GetPids() ([]int, error) {
+ dir, err := cgroups.GetOwnCgroupPath("devices")
+ if err != nil {
+ return nil, err
+ }
+ return cgroups.GetPids(dir)
+}
+
+func (m *Manager) GetAllPids() ([]int, error) {
+ dir, err := cgroups.GetOwnCgroupPath("devices")
+ if err != nil {
+ return nil, err
+ }
+ return cgroups.GetAllPids(dir)
+}
+
+func (m *Manager) GetStats() (*cgroups.Stats, error) {
+ // TODO(cyphar): We can make this work if we figure out a way to allow usage
+ // of cgroups with a rootless container. While this doesn't
+ // actually require write access to a cgroup directory, the
+ // statistics are not useful if they can be affected by
+ // non-container processes.
+ return nil, fmt.Errorf("cannot get cgroup stats in rootless container")
+}
+
+func (m *Manager) Freeze(state configs.FreezerState) error {
+ // TODO(cyphar): We can make this work if we figure out a way to allow usage
+ // of cgroups with a rootless container.
+ return fmt.Errorf("cannot use freezer cgroup in rootless container")
+}
+
+func (m *Manager) Destroy() error {
+ // We don't have to do anything here because we didn't do any setup.
+ return nil
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go
index 8eeedc55b0..e11c5c7416 100644
--- a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go
@@ -64,6 +64,13 @@ type PidsStats struct {
Limit uint64 `json:"limit,omitempty"`
}
+type FilesStats struct {
+ // number of pids in the cgroup
+ Usage uint64 `json:"usage,omitempty"`
+ // active pids hard limit
+ Limit uint64 `json:"limit,omitempty"`
+}
+
type BlkioStatEntry struct {
Major uint64 `json:"major,omitempty"`
Minor uint64 `json:"minor,omitempty"`
@@ -96,6 +103,7 @@ type Stats struct {
CpuStats CpuStats `json:"cpu_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
PidsStats PidsStats `json:"pids_stats,omitempty"`
+ FilesStats FilesStats `json:"files_stats,omitempty"`
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
// the map is in the format "size of hugepage: stats of the hugepage"
HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"`
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go
new file mode 100644
index 0000000000..7de9ae6050
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go
@@ -0,0 +1,55 @@
+// +build !linux
+
+package systemd
+
+import (
+ "fmt"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type Manager struct {
+ Cgroups *configs.Cgroup
+ Paths map[string]string
+}
+
+func UseSystemd() bool {
+ return false
+}
+
+func (m *Manager) Apply(pid int) error {
+ return fmt.Errorf("Systemd not supported")
+}
+
+func (m *Manager) GetPids() ([]int, error) {
+ return nil, fmt.Errorf("Systemd not supported")
+}
+
+func (m *Manager) GetAllPids() ([]int, error) {
+ return nil, fmt.Errorf("Systemd not supported")
+}
+
+func (m *Manager) Destroy() error {
+ return fmt.Errorf("Systemd not supported")
+}
+
+func (m *Manager) GetPaths() map[string]string {
+ return nil
+}
+
+func (m *Manager) GetStats() (*cgroups.Stats, error) {
+ return nil, fmt.Errorf("Systemd not supported")
+}
+
+func (m *Manager) Set(container *configs.Config) error {
+ return nil, fmt.Errorf("Systemd not supported")
+}
+
+func (m *Manager) Freeze(state configs.FreezerState) 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/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go
new file mode 100644
index 0000000000..0411b72390
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go
@@ -0,0 +1,556 @@
+// +build linux
+
+package systemd
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+
+ systemdDbus "github.com/coreos/go-systemd/dbus"
+ systemdUtil "github.com/coreos/go-systemd/util"
+ "github.com/godbus/dbus"
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type Manager struct {
+ mu sync.Mutex
+ Cgroups *configs.Cgroup
+ Paths map[string]string
+}
+
+type subsystem interface {
+ // Name returns the name of the subsystem.
+ Name() string
+ // Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
+ GetStats(path string, stats *cgroups.Stats) error
+ // Set the cgroup represented by cgroup.
+ Set(path string, cgroup *configs.Cgroup) error
+}
+
+var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
+
+type subsystemSet []subsystem
+
+func (s subsystemSet) Get(name string) (subsystem, error) {
+ for _, ss := range s {
+ if ss.Name() == name {
+ return ss, nil
+ }
+ }
+ return nil, errSubsystemDoesNotExist
+}
+
+var subsystems = subsystemSet{
+ &fs.CpusetGroup{},
+ &fs.DevicesGroup{},
+ &fs.MemoryGroup{},
+ &fs.CpuGroup{},
+ &fs.CpuacctGroup{},
+ &fs.PidsGroup{},
+ &fs.FilesGroup{},
+ &fs.BlkioGroup{},
+ &fs.HugetlbGroup{},
+ &fs.PerfEventGroup{},
+ &fs.FreezerGroup{},
+ &fs.NetPrioGroup{},
+ &fs.NetClsGroup{},
+ &fs.NameGroup{GroupName: "name=systemd"},
+}
+
+const (
+ testScopeWait = 4
+ testSliceWait = 4
+)
+
+var (
+ connLock sync.Mutex
+ theConn *systemdDbus.Conn
+ hasStartTransientUnit bool
+ hasStartTransientSliceUnit bool
+ hasTransientDefaultDependencies bool
+ hasDelegate bool
+)
+
+func newProp(name string, units interface{}) systemdDbus.Property {
+ return systemdDbus.Property{
+ Name: name,
+ Value: dbus.MakeVariant(units),
+ }
+}
+
+func UseSystemd() bool {
+ if !systemdUtil.IsRunningSystemd() {
+ return false
+ }
+
+ connLock.Lock()
+ defer connLock.Unlock()
+
+ if theConn == nil {
+ var err error
+ theConn, err = systemdDbus.New()
+ if err != nil {
+ return false
+ }
+
+ // Assume we have StartTransientUnit
+ hasStartTransientUnit = true
+
+ // But if we get UnknownMethod error we don't
+ if _, err := theConn.StartTransientUnit("test.scope", "invalid", nil, nil); err != nil {
+ if dbusError, ok := err.(dbus.Error); ok {
+ if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" {
+ hasStartTransientUnit = false
+ return hasStartTransientUnit
+ }
+ }
+ }
+
+ // Ensure the scope name we use doesn't exist. Use the Pid to
+ // avoid collisions between multiple libcontainer users on a
+ // single host.
+ scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid())
+ testScopeExists := true
+ for i := 0; i <= testScopeWait; i++ {
+ if _, err := theConn.StopUnit(scope, "replace", nil); err != nil {
+ if dbusError, ok := err.(dbus.Error); ok {
+ if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") {
+ testScopeExists = false
+ break
+ }
+ }
+ }
+ time.Sleep(time.Millisecond)
+ }
+
+ // Bail out if we can't kill this scope without testing for DefaultDependencies
+ if testScopeExists {
+ return hasStartTransientUnit
+ }
+
+ // Assume StartTransientUnit on a scope allows DefaultDependencies
+ hasTransientDefaultDependencies = true
+ ddf := newProp("DefaultDependencies", false)
+ if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{ddf}, nil); err != nil {
+ if dbusError, ok := err.(dbus.Error); ok {
+ if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
+ hasTransientDefaultDependencies = false
+ }
+ }
+ }
+
+ // Not critical because of the stop unit logic above.
+ theConn.StopUnit(scope, "replace", nil)
+
+ // Assume StartTransientUnit on a scope allows Delegate
+ hasDelegate = true
+ dl := newProp("Delegate", true)
+ if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{dl}, nil); err != nil {
+ if dbusError, ok := err.(dbus.Error); ok {
+ if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
+ hasDelegate = false
+ }
+ }
+ }
+
+ // Assume we have the ability to start a transient unit as a slice
+ // This was broken until systemd v229, but has been back-ported on RHEL environments >= 219
+ // For details, see: https://bugzilla.redhat.com/show_bug.cgi?id=1370299
+ hasStartTransientSliceUnit = true
+
+ // To ensure simple clean-up, we create a slice off the root with no hierarchy
+ slice := fmt.Sprintf("libcontainer_%d_systemd_test_default.slice", os.Getpid())
+ if _, err := theConn.StartTransientUnit(slice, "replace", nil, nil); err != nil {
+ if _, ok := err.(dbus.Error); ok {
+ hasStartTransientSliceUnit = false
+ }
+ }
+
+ for i := 0; i <= testSliceWait; i++ {
+ if _, err := theConn.StopUnit(slice, "replace", nil); err != nil {
+ if dbusError, ok := err.(dbus.Error); ok {
+ if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") {
+ hasStartTransientSliceUnit = false
+ break
+ }
+ }
+ } else {
+ break
+ }
+ time.Sleep(time.Millisecond)
+ }
+
+ // Not critical because of the stop unit logic above.
+ theConn.StopUnit(scope, "replace", nil)
+ theConn.StopUnit(slice, "replace", nil)
+ }
+ return hasStartTransientUnit
+}
+
+func (m *Manager) Apply(pid int) error {
+ var (
+ c = m.Cgroups
+ unitName = getUnitName(c)
+ slice = "system.slice"
+ properties []systemdDbus.Property
+ )
+
+ if c.Paths != nil {
+ paths := make(map[string]string)
+ for name, path := range c.Paths {
+ _, err := getSubsystemPath(m.Cgroups, name)
+ if err != nil {
+ // Don't fail if a cgroup hierarchy was not found, just skip this subsystem
+ if cgroups.IsNotFound(err) {
+ continue
+ }
+ return err
+ }
+ paths[name] = path
+ }
+ m.Paths = paths
+ return cgroups.EnterPid(m.Paths, pid)
+ }
+
+ if c.Parent != "" {
+ slice = c.Parent
+ }
+
+ properties = append(properties, systemdDbus.PropDescription("libcontainer container "+c.Name))
+
+ // if we create a slice, the parent is defined via a Wants=
+ if strings.HasSuffix(unitName, ".slice") {
+ // This was broken until systemd v229, but has been back-ported on RHEL environments >= 219
+ if !hasStartTransientSliceUnit {
+ return fmt.Errorf("systemd version does not support ability to start a slice as transient unit")
+ }
+ properties = append(properties, systemdDbus.PropWants(slice))
+ } else {
+ // otherwise, we use Slice=
+ properties = append(properties, systemdDbus.PropSlice(slice))
+ }
+
+ // only add pid if its valid, -1 is used w/ general slice creation.
+ if pid != -1 {
+ properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
+ }
+
+ if hasDelegate {
+ // This is only supported on systemd versions 218 and above.
+ properties = append(properties, newProp("Delegate", true))
+ }
+
+ // Always enable accounting, this gets us the same behaviour as the fs implementation,
+ // plus the kernel has some problems with joining the memory cgroup at a later time.
+ properties = append(properties,
+ newProp("MemoryAccounting", true),
+ newProp("CPUAccounting", true),
+ newProp("BlockIOAccounting", true))
+
+ if hasTransientDefaultDependencies {
+ properties = append(properties,
+ newProp("DefaultDependencies", false))
+ }
+
+ if c.Resources.Memory != 0 {
+ properties = append(properties,
+ newProp("MemoryLimit", c.Resources.Memory))
+ }
+
+ if c.Resources.CpuShares != 0 {
+ properties = append(properties,
+ newProp("CPUShares", c.Resources.CpuShares))
+ }
+
+ // cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
+ if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
+ cpuQuotaPerSecUSec := uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
+ properties = append(properties,
+ newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
+ }
+
+ if c.Resources.BlkioWeight != 0 {
+ properties = append(properties,
+ newProp("BlockIOWeight", uint64(c.Resources.BlkioWeight)))
+ }
+
+ // We have to set kernel memory here, as we can't change it once
+ // processes have been attached to the cgroup.
+ if c.Resources.KernelMemory != 0 {
+ if err := setKernelMemory(c); err != nil {
+ return err
+ }
+ }
+
+ if _, err := theConn.StartTransientUnit(unitName, "replace", properties, nil); err != nil && !isUnitExists(err) {
+ return err
+ }
+
+ if err := joinCgroups(c, pid); err != nil {
+ return err
+ }
+
+ paths := make(map[string]string)
+ for _, s := range subsystems {
+ subsystemPath, err := getSubsystemPath(m.Cgroups, s.Name())
+ if err != nil {
+ // Don't fail if a cgroup hierarchy was not found, just skip this subsystem
+ if cgroups.IsNotFound(err) {
+ continue
+ }
+ return err
+ }
+ paths[s.Name()] = subsystemPath
+ }
+ m.Paths = paths
+ return nil
+}
+
+func (m *Manager) Destroy() error {
+ if m.Cgroups.Paths != nil {
+ return nil
+ }
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil)
+ if err := cgroups.RemovePaths(m.Paths); err != nil {
+ return err
+ }
+ m.Paths = make(map[string]string)
+ return nil
+}
+
+func (m *Manager) GetPaths() map[string]string {
+ m.mu.Lock()
+ paths := m.Paths
+ m.mu.Unlock()
+ return paths
+}
+
+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 {
+ return "", err
+ }
+ if err := cgroups.WriteCgroupProc(path, pid); err != nil {
+ return "", err
+ }
+ return path, nil
+}
+
+func joinCgroups(c *configs.Cgroup, pid int) error {
+ for _, sys := range subsystems {
+ name := sys.Name()
+ switch name {
+ case "name=systemd":
+ // let systemd handle this
+ break
+ case "cpuset":
+ path, err := getSubsystemPath(c, name)
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+ s := &fs.CpusetGroup{}
+ if err := s.ApplyDir(path, c, pid); err != nil {
+ return err
+ }
+ break
+ default:
+ _, err := join(c, name, pid)
+ if err != nil {
+ // Even if it's `not found` error, we'll return err
+ // because devices cgroup is hard requirement for
+ // container security.
+ if name == "devices" {
+ return err
+ }
+ // For other subsystems, omit the `not found` error
+ // because they are optional.
+ if !cgroups.IsNotFound(err) {
+ return err
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+// systemd represents slice hierarchy using `-`, so we need to follow suit when
+// generating the path of slice. Essentially, test-a-b.slice becomes
+// test.slice/test-a.slice/test-a-b.slice.
+func ExpandSlice(slice string) (string, error) {
+ suffix := ".slice"
+ // Name has to end with ".slice", but can't be just ".slice".
+ if len(slice) < len(suffix) || !strings.HasSuffix(slice, suffix) {
+ return "", fmt.Errorf("invalid slice name: %s", slice)
+ }
+
+ // Path-separators are not allowed.
+ if strings.Contains(slice, "/") {
+ return "", fmt.Errorf("invalid slice name: %s", slice)
+ }
+
+ var path, prefix string
+ sliceName := strings.TrimSuffix(slice, suffix)
+ // if input was -.slice, we should just return root now
+ if sliceName == "-" {
+ return "/", nil
+ }
+ for _, component := range strings.Split(sliceName, "-") {
+ // test--a.slice isn't permitted, nor is -test.slice.
+ if component == "" {
+ return "", fmt.Errorf("invalid slice name: %s", slice)
+ }
+
+ // Append the component to the path and to the prefix.
+ path += prefix + component + suffix + "/"
+ prefix += component + "-"
+ }
+
+ return path, nil
+}
+
+func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) {
+ mountpoint, err := cgroups.FindCgroupMountpoint(subsystem)
+ if err != nil {
+ return "", err
+ }
+
+ initPath, err := cgroups.GetInitCgroup(subsystem)
+ if err != nil {
+ return "", err
+ }
+ // if pid 1 is systemd 226 or later, it will be in init.scope, not the root
+ initPath = strings.TrimSuffix(filepath.Clean(initPath), "init.scope")
+
+ slice := "system.slice"
+ if c.Parent != "" {
+ slice = c.Parent
+ }
+
+ slice, err = ExpandSlice(slice)
+ if err != nil {
+ return "", err
+ }
+
+ return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil
+}
+
+func (m *Manager) Freeze(state configs.FreezerState) error {
+ path, err := getSubsystemPath(m.Cgroups, "freezer")
+ if err != nil {
+ return err
+ }
+ prevState := m.Cgroups.Resources.Freezer
+ m.Cgroups.Resources.Freezer = state
+ freezer, err := subsystems.Get("freezer")
+ if err != nil {
+ return err
+ }
+ err = freezer.Set(path, m.Cgroups)
+ if err != nil {
+ m.Cgroups.Resources.Freezer = prevState
+ return err
+ }
+ return nil
+}
+
+func (m *Manager) GetPids() ([]int, error) {
+ path, err := getSubsystemPath(m.Cgroups, "devices")
+ if err != nil {
+ return nil, err
+ }
+ return cgroups.GetPids(path)
+}
+
+func (m *Manager) GetAllPids() ([]int, error) {
+ path, err := getSubsystemPath(m.Cgroups, "devices")
+ if err != nil {
+ return nil, err
+ }
+ return cgroups.GetAllPids(path)
+}
+
+func (m *Manager) GetStats() (*cgroups.Stats, error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ stats := cgroups.NewStats()
+ for name, path := range m.Paths {
+ sys, err := subsystems.Get(name)
+ if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
+ continue
+ }
+ if err := sys.GetStats(path, stats); err != nil {
+ return nil, err
+ }
+ }
+
+ return stats, nil
+}
+
+func (m *Manager) Set(container *configs.Config) error {
+ // If Paths are set, then we are just joining cgroups paths
+ // and there is no need to set any values.
+ if m.Cgroups.Paths != nil {
+ return nil
+ }
+ for _, sys := range subsystems {
+ // Get the subsystem path, but don't error out for not found cgroups.
+ path, err := getSubsystemPath(container.Cgroups, sys.Name())
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+
+ if err := sys.Set(path, container.Cgroups); err != nil {
+ return err
+ }
+ }
+
+ if m.Paths["cpu"] != "" {
+ if err := fs.CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func getUnitName(c *configs.Cgroup) string {
+ // by default, we create a scope unless the user explicitly asks for a slice.
+ if !strings.HasSuffix(c.Name, ".slice") {
+ return fmt.Sprintf("%s-%s.scope", c.ScopePrefix, c.Name)
+ }
+ return c.Name
+}
+
+func setKernelMemory(c *configs.Cgroup) error {
+ path, err := getSubsystemPath(c, "memory")
+ if err != nil && !cgroups.IsNotFound(err) {
+ return err
+ }
+
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return err
+ }
+ return fs.EnableKernelMemoryAccounting(path)
+}
+
+// isUnitExists returns true if the error is that a systemd unit already exists.
+func isUnitExists(err error) bool {
+ if err != nil {
+ if dbusError, ok := err.(dbus.Error); ok {
+ return strings.Contains(dbusError.Name, "org.freedesktop.systemd1.UnitExists")
+ }
+ }
+ return false
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go
index e15a662f52..c5634f597e 100644
--- a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go
@@ -81,6 +81,9 @@ type Resources struct {
// Process limit; set <= `0' to disable limit.
PidsLimit int64 `json:"pids_limit"`
+ // Process open files limit.
+ FilesLimit int64 `json:"files_limit"`
+
// Specifies per cgroup weight, range is from 10 to 1000.
BlkioWeight uint16 `json:"blkio_weight"`
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go
new file mode 100644
index 0000000000..2cbb6491a7
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go
@@ -0,0 +1,95 @@
+// +build linux
+
+package utils
+
+/*
+ * Copyright 2016, 2017 SUSE LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import (
+ "fmt"
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+// MaxSendfdLen is the maximum length of the name of a file descriptor being
+// sent using SendFd. The name of the file handle returned by RecvFd will never
+// be larger than this value.
+const MaxNameLen = 4096
+
+// oobSpace is the size of the oob slice required to store a single FD. Note
+// that unix.UnixRights appears to make the assumption that fd is always int32,
+// so sizeof(fd) = 4.
+var oobSpace = unix.CmsgSpace(4)
+
+// RecvFd waits for a file descriptor to be sent over the given AF_UNIX
+// socket. The file name of the remote file descriptor will be recreated
+// locally (it is sent as non-auxiliary data in the same payload).
+func RecvFd(socket *os.File) (*os.File, error) {
+ // For some reason, unix.Recvmsg uses the length rather than the capacity
+ // when passing the msg_controllen and other attributes to recvmsg. So we
+ // have to actually set the length.
+ name := make([]byte, MaxNameLen)
+ oob := make([]byte, oobSpace)
+
+ sockfd := socket.Fd()
+ n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ if n >= MaxNameLen || oobn != oobSpace {
+ return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
+ }
+
+ // Truncate.
+ name = name[:n]
+ oob = oob[:oobn]
+
+ scms, err := unix.ParseSocketControlMessage(oob)
+ if err != nil {
+ return nil, err
+ }
+ if len(scms) != 1 {
+ return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
+ }
+ scm := scms[0]
+
+ fds, err := unix.ParseUnixRights(&scm)
+ if err != nil {
+ return nil, err
+ }
+ if len(fds) != 1 {
+ return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds))
+ }
+ fd := uintptr(fds[0])
+
+ return os.NewFile(fd, string(name)), nil
+}
+
+// SendFd sends a file descriptor over the given AF_UNIX socket. In
+// addition, the file.Name() of the given file will also be sent as
+// non-auxiliary data in the same payload (allowing to send contextual
+// information for a file descriptor).
+func SendFd(socket, file *os.File) error {
+ name := []byte(file.Name())
+ if len(name) >= MaxNameLen {
+ return fmt.Errorf("sendfd: filename too long: %s", file.Name())
+ }
+ oob := unix.UnixRights(int(file.Fd()))
+
+ return unix.Sendmsg(int(socket.Fd()), name, oob, nil, 0)
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
new file mode 100644
index 0000000000..2b35b9a7b6
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
@@ -0,0 +1,126 @@
+package utils
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "encoding/json"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+const (
+ exitSignalOffset = 128
+)
+
+// GenerateRandomName returns a new name joined with a prefix. This size
+// specified is used to truncate the randomly generated value
+func GenerateRandomName(prefix string, size int) (string, error) {
+ id := make([]byte, 32)
+ if _, err := io.ReadFull(rand.Reader, id); err != nil {
+ return "", err
+ }
+ if size > 64 {
+ size = 64
+ }
+ return prefix + hex.EncodeToString(id)[:size], nil
+}
+
+// ResolveRootfs ensures that the current working directory is
+// not a symlink and returns the absolute path to the rootfs
+func ResolveRootfs(uncleanRootfs string) (string, error) {
+ rootfs, err := filepath.Abs(uncleanRootfs)
+ if err != nil {
+ return "", err
+ }
+ return filepath.EvalSymlinks(rootfs)
+}
+
+// ExitStatus returns the correct exit status for a process based on if it
+// was signaled or exited cleanly
+func ExitStatus(status syscall.WaitStatus) int {
+ if status.Signaled() {
+ return exitSignalOffset + int(status.Signal())
+ }
+ return status.ExitStatus()
+}
+
+// WriteJSON writes the provided struct v to w using standard json marshaling
+func WriteJSON(w io.Writer, v interface{}) error {
+ data, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(data)
+ return err
+}
+
+// CleanPath makes a path safe for use with filepath.Join. This is done by not
+// only cleaning the path, but also (if the path is relative) adding a leading
+// '/' and cleaning it (then removing the leading '/'). This ensures that a
+// path resulting from prepending another path will always resolve to lexically
+// be a subdirectory of the prefixed path. This is all done lexically, so paths
+// that include symlinks won't be safe as a result of using CleanPath.
+func CleanPath(path string) string {
+ // Deal with empty strings nicely.
+ if path == "" {
+ return ""
+ }
+
+ // Ensure that all paths are cleaned (especially problematic ones like
+ // "/../../../../../" which can cause lots of issues).
+ path = filepath.Clean(path)
+
+ // If the path isn't absolute, we need to do more processing to fix paths
+ // such as "../../../../<etc>/some/path". We also shouldn't convert absolute
+ // paths to relative ones.
+ if !filepath.IsAbs(path) {
+ path = filepath.Clean(string(os.PathSeparator) + path)
+ // This can't fail, as (by definition) all paths are relative to root.
+ path, _ = filepath.Rel(string(os.PathSeparator), path)
+ }
+
+ // Clean the path again for good measure.
+ return filepath.Clean(path)
+}
+
+// SearchLabels searches a list of key-value pairs for the provided key and
+// returns the corresponding value. The pairs must be separated with '='.
+func SearchLabels(labels []string, query string) string {
+ for _, l := range labels {
+ parts := strings.SplitN(l, "=", 2)
+ if len(parts) < 2 {
+ continue
+ }
+ if parts[0] == query {
+ return parts[1]
+ }
+ }
+ return ""
+}
+
+// Annotations returns the bundle path and user defined annotations from the
+// libcontainer state. We need to remove the bundle because that is a label
+// added by libcontainer.
+func Annotations(labels []string) (bundle string, userAnnotations map[string]string) {
+ userAnnotations = make(map[string]string)
+ for _, l := range labels {
+ parts := strings.SplitN(l, "=", 2)
+ if len(parts) < 2 {
+ continue
+ }
+ if parts[0] == "bundle" {
+ bundle = parts[1]
+ } else {
+ userAnnotations[parts[0]] = parts[1]
+ }
+ }
+ return
+}
+
+func GetIntSize() int {
+ return int(unsafe.Sizeof(1))
+}
diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go
new file mode 100644
index 0000000000..7b798cc79d
--- /dev/null
+++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go
@@ -0,0 +1,43 @@
+// +build !windows
+
+package utils
+
+import (
+ "io/ioutil"
+ "os"
+ "strconv"
+ "syscall"
+)
+
+func CloseExecFrom(minFd int) error {
+ fdList, err := ioutil.ReadDir("/proc/self/fd")
+ if err != nil {
+ return err
+ }
+ for _, fi := range fdList {
+ fd, err := strconv.Atoi(fi.Name())
+ if err != nil {
+ // ignore non-numeric file names
+ continue
+ }
+
+ if fd < minFd {
+ // ignore descriptors lower than our specified minimum
+ continue
+ }
+
+ // intentionally ignore errors from syscall.CloseOnExec
+ syscall.CloseOnExec(fd)
+ // the cases where this might fail are basically file descriptors that have already been closed (including and especially the one that was created when ioutil.ReadDir did the "opendir" syscall)
+ }
+ return nil
+}
+
+// NewSockPair returns a new unix socket pair
+func NewSockPair(name string) (parent *os.File, child *os.File, err error) {
+ fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil
+}
--
2.17.1
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。