Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ matrix:
- sudo ssh default sudo podman run --privileged --cgroupns=private -v /lib/modules:/lib/modules:ro test make localunittest
# cgroupv2+systemd: test on vagrant host itself as we need systemd
- sudo ssh default -t 'cd /vagrant && sudo make localintegration RUNC_USE_SYSTEMD=yes'
# same setup but with fs2 driver instead of systemd
- sudo ssh default -t 'cd /vagrant && sudo make localintegration'
allow_failures:
- go: tip

Expand Down
8 changes: 8 additions & 0 deletions libcontainer/cgroups/fs2/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)

func isCpuSet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.CpuWeight != 0 || cgroup.Resources.CpuMax != ""
}

func setCpu(dirPath string, cgroup *configs.Cgroup) error {
if !isCpuSet(cgroup) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure whether this validation is useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I added it is peculiar -- if someone will be modifying setCpu(), adding more fields to set, they will be reminded to also modify isCpuSet(). Having this check here might also help to uncover bugs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g. someone adds cpuFoobar setting to setCpu(), but forgets to modify isCpuSet(). They add a unit test to check if cpuFoobar is applied, and the test fails! Hope it makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be just code comment lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be just code comment lines?

What do you mean? Add a comment like "if you're modifying this, don't forget to modify isSetCpu"? In my experience, this never works. The added runtime check might actually help to uncover such a bug.

Or do you mean adding a comment stating the reason why this check is added?

Copy link
Contributor Author

@kolyshkin kolyshkin Apr 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do you mean adding a comment stating the reason why this check is added?

I have amended the commit telling why is*Set is added to set*:

  1. Amend all setFoo() functions to call isFooSet(). While this might
    seem unnecessary, it might actually help to uncover a bug.
    Imagine someone:

    • adds a cgroup.Resources.CpuFoo setting;
    • modifies setCpu() to apply the new setting;
    • but forgets to amend isCpuSet() accordingly <-- BUG

    In this case, a test case modifying CpuFoo will help
    to uncover the BUG. This is the reason why it's added.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the former, but not a blocker

return nil
}

// NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
if cgroup.Resources.CpuWeight != 0 {
if err := fscommon.WriteFile(dirPath, "cpu.weight", strconv.FormatUint(cgroup.Resources.CpuWeight, 10)); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions libcontainer/cgroups/fs2/cpuset.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)

func isCpusetSet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.CpusetCpus != "" || cgroup.Resources.CpusetMems != ""
}

func setCpuset(dirPath string, cgroup *configs.Cgroup) error {
if !isCpusetSet(cgroup) {
return nil
}

if cgroup.Resources.CpusetCpus != "" {
if err := fscommon.WriteFile(dirPath, "cpuset.cpus", cgroup.Resources.CpusetCpus); err != nil {
return err
Expand Down
111 changes: 111 additions & 0 deletions libcontainer/cgroups/fs2/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package fs2

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

"github.com/opencontainers/runc/libcontainer/configs"
)

// neededControllers returns the string to write to cgroup.subtree_control,
// containing the list of controllers to enable (for example, "+cpu +pids"),
// based on (1) controllers available and (2) resources that are being set.
//
// The resulting string does not include "pseudo" controllers such as
// "freezer" and "devices".
func neededControllers(cgroup *configs.Cgroup) ([]string, error) {
var list []string

if cgroup == nil {
return list, nil
}

// list of all available controllers
const file = UnifiedMountpoint + "/cgroup.controllers"
content, err := ioutil.ReadFile(file)
if err != nil {
return list, err
}
avail := make(map[string]struct{})
for _, ctr := range strings.Fields(string(content)) {
avail[ctr] = struct{}{}
}

// add the controller if available
add := func(controller string) {
if _, ok := avail[controller]; ok {
list = append(list, "+"+controller)
}
}

if isPidsSet(cgroup) {
add("pids")
}
if isMemorySet(cgroup) {
add("memory")
}
if isIoSet(cgroup) {
add("io")
}
if isCpuSet(cgroup) {
add("cpu")
}
if isCpusetSet(cgroup) {
add("cpuset")
}
if isHugeTlbSet(cgroup) {
add("hugetlb")
}

return list, nil
}

// CreateCgroupPath creates cgroupv2 path, enabling all the
// needed controllers in the process.
func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
if !strings.HasPrefix(path, UnifiedMountpoint) {
return fmt.Errorf("invalid cgroup path %s", path)
}

ctrs, err := neededControllers(c)
if err != nil {
return err
}
allCtrs := strings.Join(ctrs, " ")

elements := strings.Split(path, "/")
elements = elements[3:]
current := "/sys/fs"
for i, e := range elements {
current = filepath.Join(current, e)
if i > 0 {
if err := os.Mkdir(current, 0755); err != nil {
if !os.IsExist(err) {
return err
}
} else {
// If the directory was created, be sure it is not left around on errors.
current := current
defer func() {
if Err != nil {
os.Remove(current)
}
}()
}
}
// enable needed controllers
if i < len(elements)-1 {
file := filepath.Join(current, "cgroup.subtree_control")
if err := ioutil.WriteFile(file, []byte(allCtrs), 0755); err != nil {
// XXX: we can enable _some_ controllers doing it one-by one
// instead of erroring out -- does it makes sense to do so?
return err
}
}
}

return nil
}
80 changes: 41 additions & 39 deletions libcontainer/cgroups/fs2/fs2.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,75 +8,67 @@ import (
"path/filepath"
"strings"

securejoin "github.com/cyphar/filepath-securejoin"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/pkg/errors"
)

type manager struct {
config *configs.Cgroup
// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
dirPath string
// controllers is content of "cgroup.controllers" file.
// excludes pseudo-controllers ("devices" and "freezer").
controllers map[string]struct{}
rootless bool
}

// NewManager creates a manager for cgroup v2 unified hierarchy.
// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope".
// If dirPath is empty, it is automatically set using config.
func NewManager(config *configs.Cgroup, dirPath string, rootless bool) (cgroups.Manager, error) {
if config == nil {
config = &configs.Cgroup{}
}
if dirPath != "" {
if filepath.Clean(dirPath) != dirPath || !filepath.IsAbs(dirPath) {
return nil, errors.Errorf("invalid dir path %q", dirPath)
}
} else {
if dirPath == "" {
var err error
dirPath, err = defaultDirPath(config)
if err != nil {
return nil, err
}
}
controllers, err := detectControllers(dirPath)
if err != nil && !rootless {
return nil, err
}

m := &manager{
config: config,
dirPath: dirPath,
controllers: controllers,
rootless: rootless,
config: config,
dirPath: dirPath,
rootless: rootless,
}
return m, nil
}

func detectControllers(dirPath string) (map[string]struct{}, error) {
if err := os.MkdirAll(dirPath, 0755); err != nil {
return nil, err
}
controllersPath, err := securejoin.SecureJoin(dirPath, "cgroup.controllers")
if err != nil {
return nil, err
func (m *manager) getControllers() error {
if m.controllers != nil {
return nil
}
controllersData, err := ioutil.ReadFile(controllersPath)
if err != nil {
return nil, err

file := filepath.Join(m.dirPath, "cgroup.controllers")
data, err := ioutil.ReadFile(file)
if err != nil && !m.rootless {
return err
}
controllersFields := strings.Fields(string(controllersData))
controllers := make(map[string]struct{}, len(controllersFields))
for _, c := range controllersFields {
controllers[c] = struct{}{}
fields := strings.Fields(string(data))
m.controllers = make(map[string]struct{}, len(fields))
for _, c := range fields {
m.controllers[c] = struct{}{}
}
return controllers, nil
}

type manager struct {
config *configs.Cgroup
// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
dirPath string
// controllers is content of "cgroup.controllers" file.
// excludes pseudo-controllers ("devices" and "freezer").
controllers map[string]struct{}
rootless bool
return nil
}

func (m *manager) Apply(pid int) error {
if err := CreateCgroupPath(m.dirPath, m.config); err != nil {
return err
}
if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil && !m.rootless {
return err
}
Expand All @@ -97,6 +89,9 @@ func (m *manager) GetStats() (*cgroups.Stats, error) {
)

st := cgroups.NewStats()
if err := m.getControllers(); err != nil {
return st, err
}

// pids (since kernel 4.5)
if _, ok := m.controllers["pids"]; ok {
Expand Down Expand Up @@ -147,11 +142,15 @@ func (m *manager) Freeze(state configs.FreezerState) error {
}

func (m *manager) Destroy() error {
return os.RemoveAll(m.dirPath)
if err := os.Remove(m.dirPath); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}

// GetPaths is for compatibility purpose and should be removed in future
func (m *manager) GetPaths() map[string]string {
_ = m.getControllers()
paths := map[string]string{
// pseudo-controller for compatibility
"devices": m.dirPath,
Expand All @@ -171,6 +170,9 @@ func (m *manager) Set(container *configs.Config) error {
if container == nil || container.Cgroups == nil {
return nil
}
if err := m.getControllers(); err != nil {
return err
}
var errs []error
// pids (since kernel 4.5)
if _, ok := m.controllers["pids"]; ok {
Expand Down
7 changes: 7 additions & 0 deletions libcontainer/cgroups/fs2/hugetlb.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)

func isHugeTlbSet(cgroup *configs.Cgroup) bool {
return len(cgroup.Resources.HugetlbLimit) > 0
}

func setHugeTlb(dirPath string, cgroup *configs.Cgroup) error {
if !isHugeTlbSet(cgroup) {
return nil
}
for _, hugetlb := range cgroup.Resources.HugetlbLimit {
if err := fscommon.WriteFile(dirPath, strings.Join([]string{"hugetlb", hugetlb.Pagesize, "max"}, "."), strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
return err
Expand Down
12 changes: 12 additions & 0 deletions libcontainer/cgroups/fs2/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)

func isIoSet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.BlkioWeight != 0 ||
len(cgroup.Resources.BlkioThrottleReadBpsDevice) > 0 ||
len(cgroup.Resources.BlkioThrottleWriteBpsDevice) > 0 ||
len(cgroup.Resources.BlkioThrottleReadIOPSDevice) > 0 ||
len(cgroup.Resources.BlkioThrottleWriteIOPSDevice) > 0
}

func setIo(dirPath string, cgroup *configs.Cgroup) error {
if !isIoSet(cgroup) {
return nil
}

if cgroup.Resources.BlkioWeight != 0 {
filename := "io.bfq.weight"
if err := fscommon.WriteFile(dirPath, filename,
Expand Down
8 changes: 8 additions & 0 deletions libcontainer/cgroups/fs2/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,15 @@ func numToStr(value int64) (ret string) {
return ret
}

func isMemorySet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.MemoryReservation != 0 ||
cgroup.Resources.Memory != 0 || cgroup.Resources.MemorySwap != 0
}

func setMemory(dirPath string, cgroup *configs.Cgroup) error {
if !isMemorySet(cgroup) {
return nil
}
swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(cgroup.Resources.MemorySwap, cgroup.Resources.Memory)
if err != nil {
return err
Expand Down
7 changes: 7 additions & 0 deletions libcontainer/cgroups/fs2/pids.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ import (
"golang.org/x/sys/unix"
)

func isPidsSet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.PidsLimit != 0
}

func setPids(dirPath string, cgroup *configs.Cgroup) error {
if !isPidsSet(cgroup) {
return nil
}
if val := numToStr(cgroup.Resources.PidsLimit); val != "" {
if err := fscommon.WriteFile(dirPath, "pids.max", val); err != nil {
return err
Expand Down
Loading