Files
runc/libcontainer/cgroups/systemd/systemd_test.go
T
Kir Kolyshkin ed9651bc71 libct/cg/sd: support setting cpu.idle via systemd
Systemd v252 (available in CentOS Stream 9 in our CI) added support
for setting cpu.idle (see [1]). The way it works is:
 - if CPUWeight == 0, cpu.idle is set to 1;
 - if CPUWeight != 0, cpu.idle is set to 0.

This commit implements setting cpu.idle in systemd cgroup driver via a
unit property. In case CPUIdle is set to non-zero value, the driver sets
adds CPUWeight=0 property, which will result in systemd setting cpu.idle
to 1.

Unfortunately, there's no way to set cpu.idle to 0 without also changing
the CPUWeight value, so the driver doesn't do anything if CPUIdle is
explicitly set to 0. This case is handled by the fs driver which is
always used as a followup to setting systemd unit properties.

Also, handle cpu.idle set via unified map. In case it is set to non-zero
value, add CPUWeight=0 property, and ignore cpu.weight (otherwise we'll
get two different CPUWeight properties set).

Add a unit test for new values in unified map, and an integration test case.

[1] https://github.com/systemd/systemd/pull/23299
[2] https://github.com/opencontainers/runc/issues/3786

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2023-04-03 18:25:07 -07:00

182 lines
4.1 KiB
Go

package systemd
import (
"os"
"reflect"
"testing"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
func newManager(t *testing.T, config *configs.Cgroup) (m cgroups.Manager) {
t.Helper()
var err error
if cgroups.IsCgroup2UnifiedMode() {
m, err = NewUnifiedManager(config, "")
} else {
m, err = NewLegacyManager(config, nil)
}
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = m.Destroy() })
return m
}
func TestSystemdVersion(t *testing.T) {
systemdVersionTests := []struct {
verStr string
expectedVer int
expectErr bool
}{
{`"219"`, 219, false},
{`"v245.4-1.fc32"`, 245, false},
{`"241-1"`, 241, false},
{`"v241-1"`, 241, false},
{`333.45"`, 333, false},
{`v321-0`, 321, false},
{"NaN", -1, true},
{"", -1, true},
{"v", -1, true},
}
for _, sdTest := range systemdVersionTests {
ver, err := systemdVersionAtoi(sdTest.verStr)
if !sdTest.expectErr && err != nil {
t.Errorf("systemdVersionAtoi(%s); want nil; got %v", sdTest.verStr, err)
}
if sdTest.expectErr && err == nil {
t.Errorf("systemdVersionAtoi(%s); wanted failure; got nil", sdTest.verStr)
}
if ver != sdTest.expectedVer {
t.Errorf("systemdVersionAtoi(%s); want %d; got %d", sdTest.verStr, sdTest.expectedVer, ver)
}
}
}
func TestValidUnitTypes(t *testing.T) {
testCases := []struct {
unitName string
expectedUnitType string
}{
{"system.slice", "Slice"},
{"kubepods.slice", "Slice"},
{"testing-container:ab.scope", "Scope"},
}
for _, sdTest := range testCases {
unitType := getUnitType(sdTest.unitName)
if unitType != sdTest.expectedUnitType {
t.Errorf("getUnitType(%s); want %q; got %q", sdTest.unitName, sdTest.expectedUnitType, unitType)
}
}
}
func TestUnitExistsIgnored(t *testing.T) {
if !IsRunningSystemd() {
t.Skip("Test requires systemd.")
}
if os.Geteuid() != 0 {
t.Skip("Test requires root.")
}
podConfig := &configs.Cgroup{
Parent: "system.slice",
Name: "system-runc_test_exists.slice",
Resources: &configs.Resources{},
}
// Create "pods" cgroup (a systemd slice to hold containers).
pm := newManager(t, podConfig)
// create twice to make sure "UnitExists" error is ignored.
for i := 0; i < 2; i++ {
if err := pm.Apply(-1); err != nil {
t.Fatal(err)
}
}
}
func TestUnifiedResToSystemdProps(t *testing.T) {
if !IsRunningSystemd() {
t.Skip("Test requires systemd.")
}
if !cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is required")
}
cm := newDbusConnManager(os.Geteuid() != 0)
testCases := []struct {
name string
minVer int
res map[string]string
expError bool
expProps []systemdDbus.Property
}{
{
name: "empty map",
res: map[string]string{},
},
{
name: "only cpu.idle=1",
minVer: cpuIdleSupportedVersion,
res: map[string]string{
"cpu.idle": "1",
},
expProps: []systemdDbus.Property{
newProp("CPUWeight", uint64(0)),
},
},
{
name: "only cpu.idle=0",
minVer: cpuIdleSupportedVersion,
res: map[string]string{
"cpu.idle": "0",
},
},
{
name: "cpu.idle=1 and cpu.weight=1000",
minVer: cpuIdleSupportedVersion,
res: map[string]string{
"cpu.idle": "1",
"cpu.weight": "1000",
},
expProps: []systemdDbus.Property{
newProp("CPUWeight", uint64(0)),
},
},
{
name: "cpu.idle=0 and cpu.weight=1000",
minVer: cpuIdleSupportedVersion,
res: map[string]string{
"cpu.idle": "0",
"cpu.weight": "1000",
},
expProps: []systemdDbus.Property{
newProp("CPUWeight", uint64(1000)),
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
if tc.minVer != 0 && systemdVersion(cm) < tc.minVer {
t.Skipf("requires systemd >= %d", tc.minVer)
}
props, err := unifiedResToSystemdProps(cm, tc.res)
if err != nil && !tc.expError {
t.Fatalf("expected no error, got: %v", err)
}
if err == nil && tc.expError {
t.Fatal("expected error, got nil")
}
if !reflect.DeepEqual(tc.expProps, props) {
t.Errorf("wrong properties (exp %+v, got %+v)", tc.expProps, props)
}
})
}
}