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
9 changes: 9 additions & 0 deletions runsc/cgroup/cgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,10 @@ func (c *CgroupJSON) MarshalJSON() ([]byte, error) {
func (c *cgroupV1) Install(res *specs.LinuxResources) error {
log.Debugf("Installing cgroup path %q", c.Name)

if res != nil && len(res.Unified) > 0 {
return fmt.Errorf("unified resources are only supported with cgroupv2")
}

// Clean up partially created cgroups on error. Errors during cleanup itself
// are ignored.
clean := cleanup.Make(func() { _ = c.Uninstall() })
Expand Down Expand Up @@ -546,6 +550,11 @@ func (c *cgroupV1) Install(res *specs.LinuxResources) error {
// Update updates the cgroup resources.
func (c *cgroupV1) Update(res *specs.LinuxResources) error {
log.Debugf("Updating cgroup resources for %q", c.Name)

if res != nil && len(res.Unified) > 0 {
return fmt.Errorf("unified resources are only supported with cgroupv2")
}

for key, ctrlr := range controllers {
if !c.Own[key] {
// cgroup is managed by caller, don't touch it.
Expand Down
28 changes: 28 additions & 0 deletions runsc/cgroup/cgroup_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,34 @@ func (c *cgroupV2) Update(res *specs.LinuxResources) error {
return fmt.Errorf("mandatory cgroup controller %q is missing for %q", controllerName, path)
}
}
// Override any values set above with the unified resource, if set.
if res != nil {
for k, v := range res.Unified {
if strings.Contains(k, "/") {
return fmt.Errorf("unified resource %q must be a file name (no slashes)", k)
}
if err := setValue(path, k, v); err != nil {
if errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist) {
controller, _, ok := strings.Cut(k, ".")
if !ok {
return fmt.Errorf("unified resource %q must be in the form CONTROLLER.PARAMETER", k)
}
// Try providing a helpful message if the controller is unknown.
found := false
for _, knownController := range c.Controllers {
if controller == knownController {
found = true
break
}
}
if !found && controller != "cgroup" {
return fmt.Errorf("unified resource %q can't be set: controller %q not available", k, controller)
}
}
return fmt.Errorf("unable to set unified resource %q: %w", k, err)
}
}
}
return nil
}

Expand Down
111 changes: 111 additions & 0 deletions runsc/cgroup/cgroup_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,114 @@ func TestUpdate(t *testing.T) {
})
}
}

func TestUpdateUnified(t *testing.T) {
for _, tc := range []struct {
name string
resources *specs.LinuxResources
// pre-created files in the fake cgroup.
seedFiles []string
// expected file contents after Update()
wantFiles map[string]string
wantErrSub string
}{
{
name: "sets arbitrary unified key",
resources: &specs.LinuxResources{
Unified: map[string]string{
"memory.high": "1000000",
},
},
seedFiles: []string{"memory.high"},
wantFiles: map[string]string{
"memory.high": "1000000",
},
},
{
name: "unified overrides controller-set value",
resources: &specs.LinuxResources{
Memory: &specs.LinuxMemory{
Limit: int64Ptr(2048),
},
Unified: map[string]string{
"memory.max": "4096",
},
},
seedFiles: []string{"memory.max"},
wantFiles: map[string]string{
"memory.max": "4096",
},
},
{
name: "rejects keys containing slashes",
resources: &specs.LinuxResources{
Unified: map[string]string{
"foo/bar": "1",
},
},
wantErrSub: "must be a file name",
},
{
name: "rejects keys without dot separator",
resources: &specs.LinuxResources{
Unified: map[string]string{
"nodot": "1",
},
},
wantErrSub: "must be in the form CONTROLLER.PARAMETER",
},
{
name: "reports unavailable controller",
resources: &specs.LinuxResources{
Unified: map[string]string{
"fake.knob": "1",
},
},
wantErrSub: `controller "fake" not available`,
},
} {
t.Run(tc.name, func(t *testing.T) {
dir, err := os.MkdirTemp(testutil.TmpDir(), "cgroup")
if err != nil {
t.Fatalf("error creating temporary directory: %v", err)
}
defer os.RemoveAll(dir)

cg := &cgroupV2{
Mountpoint: dir,
Path: "user.slice",
Controllers: mandatoryControllers,
}
cgPath := filepath.Join(cg.Mountpoint, cg.Path)
if err := os.MkdirAll(cgPath, 0o777); err != nil {
t.Fatalf("os.MkdirAll(): %v", err)
}
for _, f := range tc.seedFiles {
if err := os.WriteFile(filepath.Join(cgPath, f), nil, 0o666); err != nil {
t.Fatalf("os.WriteFile(%q): %v", f, err)
}
}

err = cg.Update(tc.resources)
if tc.wantErrSub != "" {
if err == nil || !strings.Contains(err.Error(), tc.wantErrSub) {
t.Fatalf("Update() error = %v, want substring %q", err, tc.wantErrSub)
}
return
}
if err != nil {
t.Fatalf("Update(): %v", err)
}

for name, want := range tc.wantFiles {
got, err := os.ReadFile(filepath.Join(cgPath, name))
if err != nil {
t.Fatalf("ReadFile(%q): %v", name, err)
}
if string(got) != want {
t.Errorf("file %q = %q, want %q", name, string(got), want)
}
}
})
}
}