diff --git a/systemd/systemd_test.go b/systemd/systemd_test.go index 60a6c1f..4a54245 100644 --- a/systemd/systemd_test.go +++ b/systemd/systemd_test.go @@ -3,6 +3,7 @@ package systemd import ( "os" "reflect" + "strings" "testing" systemdDbus "github.com/coreos/go-systemd/v22/dbus" @@ -97,6 +98,82 @@ func TestUnitExistsIgnored(t *testing.T) { } } +func TestSetUnified(t *testing.T) { + if !IsRunningSystemd() { + t.Skip("Test requires systemd.") + } + if !cgroups.IsCgroup2UnifiedMode() { + t.Skip("Test requires cgroup v2.") + } + if os.Geteuid() != 0 { + t.Skip("Test requires root.") + } + + testCases := []struct { + name string + file string + setVal string + clearVal string + }{ + { + name: "memory.min", + file: "memory.min", + setVal: "4096", + clearVal: "0", + }, + { + name: "memory.low", + file: "memory.low", + setVal: "4096", + clearVal: "0", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config := &cgroups.Cgroup{ + Parent: "system.slice", + Name: "system-runc_test_set_unified_" + tc.name + ".slice", + Resources: &cgroups.Resources{ + Unified: map[string]string{tc.file: tc.setVal}, + }, + } + m, err := NewUnifiedManager(config, "") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = m.Destroy() }) + + if err := m.Apply(-1); err != nil { + t.Fatal(err) + } + if err := m.Set(config.Resources); err != nil { + t.Fatal(err) + } + + val, err := cgroups.ReadFile(m.Path(""), tc.file) + if err != nil { + t.Fatal(err) + } + if v := strings.TrimSpace(val); v != tc.setVal { + t.Fatalf("after Set: expected %s=%s, got %q", tc.file, tc.setVal, v) + } + + if err := m.SetUnified(map[string]string{tc.file: tc.clearVal}); err != nil { + t.Fatal(err) + } + + val, err = cgroups.ReadFile(m.Path(""), tc.file) + if err != nil { + t.Fatal(err) + } + if v := strings.TrimSpace(val); v != tc.clearVal { + t.Fatalf("after SetUnified: expected %s=%s, got %q", tc.file, tc.clearVal, v) + } + }) + } +} + func TestUnifiedResToSystemdProps(t *testing.T) { if !IsRunningSystemd() { t.Skip("Test requires systemd.") diff --git a/systemd/v2.go b/systemd/v2.go index f76c93e..4f7fce9 100644 --- a/systemd/v2.go +++ b/systemd/v2.go @@ -516,6 +516,19 @@ func (m *UnifiedManager) Set(r *cgroups.Resources) error { return m.fsMgr.Set(r) } +// SetUnified writes unified cgroup v2 resource values directly to cgroupfs, +// bypassing systemd's SetUnitProperties. This avoids the side effect where +// systemd may reset unrelated cgroup properties when processing a property +// update via dbus. +func (m *UnifiedManager) SetUnified(unified map[string]string) error { + for k, v := range unified { + if err := cgroups.WriteFileByLine(m.path, k, v); err != nil { + return fmt.Errorf("unable to set unified resource %q: %w", k, err) + } + } + return nil +} + func (m *UnifiedManager) GetPaths() map[string]string { paths := make(map[string]string, 1) paths[""] = m.path