5760 lines
172 KiB
Diff
5760 lines
172 KiB
Diff
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
|
|
|