Skip to content

Commit

Permalink
libct/cg/sd: support setting cpu.idle via systemd
Browse files Browse the repository at this point in the history
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] systemd/systemd#23299
[2] opencontainers#3786

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
  • Loading branch information
kolyshkin committed Mar 28, 2023
1 parent 9fd7e6a commit b8d81f3
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/systemd.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ The following tables summarize which properties are translated.
| cpu.mems | AllowedMemoryNodes | v244 |
| unified.cpu.max | CPUQuota, CPUQuotaPeriodSec | v242 |
| unified.cpu.weight | CPUWeight | |
| unified.cpu.idle | CPUWeight | v252 |
| unified.cpuset.cpus | AllowedCPUs | v244 |
| unified.cpuset.mems | AllowedMemoryNodes | v244 |
| unified.memory.high | MemoryHigh | |
Expand Down
84 changes: 84 additions & 0 deletions libcontainer/cgroups/systemd/systemd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ 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"
)
Expand Down Expand Up @@ -95,3 +97,85 @@ func TestUnitExistsIgnored(t *testing.T) {
}
}
}

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)
}
})
}
}
36 changes: 35 additions & 1 deletion libcontainer/cgroups/systemd/v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)

const (
cpuIdleSupportedVersion = 252
)

type UnifiedManager struct {
mu sync.Mutex
cgroups *configs.Cgroup
Expand Down Expand Up @@ -60,6 +64,13 @@ func NewUnifiedManager(config *configs.Cgroup, path string) (*UnifiedManager, er
func unifiedResToSystemdProps(cm *dbusConnManager, res map[string]string) (props []systemdDbus.Property, _ error) {
var err error

shouldSetIdle := func(v string) bool {
// The only valid values for cpu.idle is 0 or 1. Since it is
// not possible to directly set cpu.idle to 0 via systemd,
// ignore 0.
return v == "1" && systemdVersion(cm) >= cpuIdleSupportedVersion
}

for k, v := range res {
if strings.Contains(k, "/") {
return nil, fmt.Errorf("unified resource %q must be a file name (no slashes)", k)
Expand Down Expand Up @@ -97,13 +108,26 @@ func unifiedResToSystemdProps(cm *dbusConnManager, res map[string]string) (props
addCpuQuota(cm, &props, quota, period)

case "cpu.weight":
if shouldSetIdle(strings.TrimSpace(res["cpu.idle"])) {
// Do not add duplicate CPUWeight property
// (see case "cpu.idle" below).
continue
}
num, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return nil, fmt.Errorf("unified resource %q value conversion error: %w", k, err)
}
props = append(props,
newProp("CPUWeight", num))

case "cpu.idle":
if shouldSetIdle(v) {
// Setting CPUWeight to 0 tells systemd
// to set cpu.idle to 1.
props = append(props,
newProp("CPUWeight", uint64(0)))
}

case "cpuset.cpus", "cpuset.mems":
bits, err := RangeToBits(v)
if err != nil {
Expand Down Expand Up @@ -212,7 +236,17 @@ func genV2ResourcesProperties(dirPath string, r *configs.Resources, cm *dbusConn
newProp("MemorySwapMax", uint64(swap)))
}

if r.CpuWeight != 0 {
CPUWeightSet := false
if r.CPUIdle != nil {
sdVer := systemdVersion(cm)
if sdVer >= cpuIdleSupportedVersion && *r.CPUIdle != 0 {
properties = append(properties,
newProp("CPUWeight", uint64(0)))
CPUWeightSet = true
}
}
// Ignore CpuWeight if CPUIdle is set.
if !CPUWeightSet && r.CpuWeight != 0 {
properties = append(properties,
newProp("CPUWeight", r.CpuWeight))
}
Expand Down
49 changes: 49 additions & 0 deletions tests/integration/update.bats
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,55 @@ EOF
check_cgroup_value "cpu.idle" "1"
}

@test "update cgroup cpu.idle via systemd v252+" {
requires systemd cgroups_cpu_idle
[ $EUID -ne 0 ] && requires rootless_cgroup
if [ "$(systemd_version)" -lt 252 ]; then
skip "requires systemd >= v252"
fi

runc run -d --console-socket "$CONSOLE_SOCKET" test_update
[ "$status" -eq 0 ]
check_cgroup_value "cpu.idle" "0"

runc update --cpu-idle 1 test_update
check_cgroup_value "cpu.idle" "1"

runc update --cpu-idle 0 test_update
check_cgroup_value "cpu.idle" "0"

# If cpu-idle is set, cpu-period is ignored.
runc update --cpu-period 10000 --cpu-idle 1 test_update
check_cgroup_value "cpu.idle" "1"

# Setting any cpu-period should reset cpu.idle to 0.
runc update --cpu-period 20000 test_update
check_cgroup_value "cpu.idle" "0"

# Setting values via unified map.

# If cpu.idle is set, cpu.weight is ignored.
runc update -r - test_update <<EOF
{
"unified": {
"cpu.idle": "1",
"cpu.weight": "8"
}
}
EOF
check_cgroup_value "cpu.idle" "1"

# Setting any cpu.weight should reset cpu.idle to 0.
runc update -r - test_update <<EOF
{
"unified": {
"cpu.weight": "8"
}
}
EOF
check_cgroup_value "cpu.idle" "0"
}

@test "update cgroup v2 resources via unified map" {
[ $EUID -ne 0 ] && requires rootless_cgroup
requires cgroups_v2
Expand Down

0 comments on commit b8d81f3

Please sign in to comment.