Skip to content

Commit

Permalink
libct/cgroups: support Cgroups.Resources.Unified
Browse files Browse the repository at this point in the history
Add support for unified resource map (as per [1]), and add some test
cases for the new functionality.

[1] opencontainers/runtime-spec#1040

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
  • Loading branch information
kolyshkin committed Sep 16, 2020
1 parent 74b57fe commit b14556f
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 2 deletions.
8 changes: 7 additions & 1 deletion libcontainer/cgroups/fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,10 @@ func (m *manager) Apply(pid int) (err error) {
m.mu.Lock()
defer m.mu.Unlock()

var c = m.cgroups
c := m.cgroups
if c.Resources.Unified != nil {
return cgroups.ErrV1NoUnified
}

m.paths = make(map[string]string)
if c.Paths != nil {
Expand Down Expand Up @@ -309,6 +312,9 @@ func (m *manager) Set(container *configs.Config) error {
if m.cgroups != nil && m.cgroups.Paths != nil {
return nil
}
if container.Cgroups.Resources.Unified != nil {
return cgroups.ErrV1NoUnified
}

m.mu.Lock()
defer m.mu.Unlock()
Expand Down
35 changes: 35 additions & 0 deletions libcontainer/cgroups/fs2/fs2.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
package fs2

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -206,10 +209,42 @@ func (m *manager) Set(container *configs.Config) error {
if err := setFreezer(m.dirPath, container.Cgroups.Freezer); err != nil {
return err
}
if err := m.setUnified(container.Cgroups.Unified); err != nil {
return err
}
m.config = container.Cgroups
return nil
}

func (m *manager) setUnified(res map[string]string) error {
for k, v := range res {
if strings.Contains(k, "/") {
return fmt.Errorf("unified resource %q must be a file name (no slashes)", k)
}
if err := fscommon.WriteFile(m.dirPath, k, v); err != nil {
errC := errors.Cause(err)
// cgroup2 returns EPERM (rather than ENOENT) for
// non-existent files. Do check for both.
if errors.Is(errC, os.ErrPermission) || errors.Is(errC, os.ErrNotExist) {
// Check if a controller is available,
// to give more specific error if not.
sk := strings.SplitN(k, ".", 2)
if len(sk) != 2 {
return fmt.Errorf("unified resource %q must be in the form CONTROLLER.PARAMETER", k)
}
c := sk[0]
if _, ok := m.controllers[c]; !ok && c != "cgroup" {
return fmt.Errorf("unified resource %q can't be set: controller %q not available", k, c)
}
}
return errors.Wrapf(err, "can't set unified resource %q", k)
}

}

return nil
}

func (m *manager) GetPaths() map[string]string {
paths := make(map[string]string, 1)
paths[""] = m.dirPath
Expand Down
7 changes: 7 additions & 0 deletions libcontainer/cgroups/systemd/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ func (m *legacyManager) Apply(pid int) error {
properties []systemdDbus.Property
)

if c.Resources.Unified != nil {
return cgroups.ErrV1NoUnified
}

m.mu.Lock()
defer m.mu.Unlock()
if c.Paths != nil {
Expand Down Expand Up @@ -342,6 +346,9 @@ func (m *legacyManager) Set(container *configs.Config) error {
if m.cgroups.Paths != nil {
return nil
}
if container.Cgroups.Resources.Unified != nil {
return cgroups.ErrV1NoUnified
}
dbusConnection, err := getDbusConnection(false)
if err != nil {
return err
Expand Down
3 changes: 2 additions & 1 deletion libcontainer/cgroups/v1_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const (
)

var (
errUnified = errors.New("not implemented for cgroup v2 unified hierarchy")
errUnified = errors.New("not implemented for cgroup v2 unified hierarchy")
ErrV1NoUnified = errors.New("invalid configuration: cannot use unified on cgroup v1")
)

type NotFoundError struct {
Expand Down
3 changes: 3 additions & 0 deletions libcontainer/configs/cgroup_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ type Resources struct {
// CpuWeight sets a proportional bandwidth limit.
CpuWeight uint64 `json:"cpu_weight"`

// Unified is cgroupv2-only key-value map.
Unified map[string]string `json:"unified"`

// SkipDevices allows to skip configuring device permissions.
// Used by e.g. kubelet while creating a parent cgroup (kubepods)
// common for many containers.
Expand Down
144 changes: 144 additions & 0 deletions libcontainer/integration/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,150 @@ func testRunWithKernelMemory(t *testing.T, systemd bool) {
}
}

func TestCgroupResourcesUnifiedErrorOnV1(t *testing.T) {
testCgroupResourcesUnifiedErrorOnV1(t, false)
}

func TestCgroupResourcesUnifiedErrorOnV1Systemd(t *testing.T) {
if !systemd.IsRunningSystemd() {
t.Skip("Systemd is unsupported")
}
testCgroupResourcesUnifiedErrorOnV1(t, true)
}

func testCgroupResourcesUnifiedErrorOnV1(t *testing.T, systemd bool) {
if testing.Short() {
return
}
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("requires cgroup v1")
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)

config := newTemplateConfig(rootfs)
if systemd {
config.Cgroups.Parent = "system.slice"
}
config.Cgroups.Resources.Unified = map[string]string{
"memory.min": "10240",
}
_, _, err = runContainer(config, "", "true")
if !strings.Contains(err.Error(), cgroups.ErrV1NoUnified.Error()) {
t.Fatalf("expected error to contain %v, got %v", cgroups.ErrV1NoUnified, err)
}
}

func TestCgroupResourcesUnified(t *testing.T) {
testCgroupResourcesUnified(t, false)
}

func TestCgroupResourcesUnifiedSystemd(t *testing.T) {
if !systemd.IsRunningSystemd() {
t.Skip("Systemd is unsupported")
}
testCgroupResourcesUnified(t, true)
}

func testCgroupResourcesUnified(t *testing.T, systemd bool) {
if testing.Short() {
return
}
if !cgroups.IsCgroup2UnifiedMode() {
t.Skip("requires cgroup v2")
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)

config := newTemplateConfig(rootfs)
config.Cgroups.Resources.Memory = 536870912 // 512M
config.Cgroups.Resources.MemorySwap = 536870912 // 512M, i.e. no swap
config.Namespaces.Add(configs.NEWCGROUP, "")
config.Mounts = append(config.Mounts, &configs.Mount{
Destination: "/sys/fs/cgroup",
Device: "cgroup",
Flags: defaultMountFlags | unix.MS_RDONLY,
})
if systemd {
config.Cgroups.Parent = "system.slice"
}

testCases := []struct {
name string
cfg map[string]string
expError string
cmd []string
exp string
}{
{
name: "dummy",
cmd: []string{"true"},
exp: "",
},
{
name: "set memory.min",
cfg: map[string]string{"memory.min": "131072"},
cmd: []string{"cat", "/sys/fs/cgroup/memory.min"},
exp: "131072\n",
},
{
name: "check memory.max",
cmd: []string{"cat", "/sys/fs/cgroup/memory.max"},
exp: strconv.Itoa(int(config.Cgroups.Resources.Memory)) + "\n",
},

{
name: "overwrite memory.max",
cfg: map[string]string{"memory.max": "268435456"},
cmd: []string{"cat", "/sys/fs/cgroup/memory.max"},
exp: "268435456\n",
},
{
name: "no such controller error",
cfg: map[string]string{"privet.vsem": "vam"},
expError: "controller \"privet\" not available",
},
{
name: "slash in key error",
cfg: map[string]string{"bad/key": "val"},
expError: "must be a file name (no slashes)",
},
{
name: "no dot in key error",
cfg: map[string]string{"badkey": "val"},
expError: "must be in the form CONTROLLER.PARAMETER",
},
}

for _, tc := range testCases {
config.Cgroups.Resources.Unified = tc.cfg
buffers, ret, err := runContainer(config, "", tc.cmd...)
if tc.expError != "" {
if err == nil {
t.Errorf("case %q failed: expected error, got nil", tc.name)
continue
}
if !strings.Contains(err.Error(), tc.expError) {
t.Errorf("case %q failed: expected error to contain %q, got %q", tc.name, tc.expError, err)
}
continue
}
if err != nil {
t.Errorf("case %q failed: expected no error, got %v (command: %v, status: %d, stderr: %q)",
tc.name, err, tc.cmd, ret, buffers.Stderr.String())
continue
}
if tc.exp != "" {
out := buffers.Stdout.String()
if out != tc.exp {
t.Errorf("expected %q, got %q", tc.exp, out)
}
}
}
}

func TestContainerState(t *testing.T) {
if testing.Short() {
return
Expand Down
7 changes: 7 additions & 0 deletions libcontainer/specconv/spec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,13 @@ func CreateCgroupConfig(opts *CreateOpts, defaultDevs []*configs.Device) (*confi
})
}
}
if len(r.Unified) > 0 {
// copy the map
c.Resources.Unified = make(map[string]string, len(r.Unified))
for k, v := range r.Unified {
c.Resources.Unified[k] = v
}
}
}
}

Expand Down

0 comments on commit b14556f

Please sign in to comment.