Skip to content

Commit 11d141b

Browse files
authored
Merge pull request opencontainers#3090 from kolyshkin/cfq_quota_period
libct/cg/v1: work around CPU quota period set failure
2 parents 654f331 + 6394457 commit 11d141b

File tree

6 files changed

+226
-11
lines changed

6 files changed

+226
-11
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ vendor/pkg
22
/runc
33
/runc-*
44
contrib/cmd/recvtty/recvtty
5+
contrib/cmd/sd-helper/sd-helper
56
man/man8
67
release
78
Vagrantfile

Makefile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@ GO_BUILD_STATIC := CGO_ENABLED=1 $(GO) build -trimpath $(EXTRA_FLAGS) -tags "$(B
3030
runc:
3131
$(GO_BUILD) -o runc .
3232

33-
all: runc recvtty
33+
all: runc recvtty sd-helper
3434

35-
recvtty:
36-
$(GO_BUILD) -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
35+
recvtty sd-helper:
36+
$(GO_BUILD) -o contrib/cmd/$@/$@ ./contrib/cmd/$@
3737

3838
static:
3939
$(GO_BUILD_STATIC) -o runc .
4040
$(GO_BUILD_STATIC) -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
41+
$(GO_BUILD_STATIC) -o contrib/cmd/sd-helper/sd-helper ./contrib/cmd/sd-helper
4142

4243
release:
4344
script/release.sh -r release/$(VERSION) -v $(VERSION)
@@ -110,6 +111,7 @@ install-man: man
110111
clean:
111112
rm -f runc runc-*
112113
rm -f contrib/cmd/recvtty/recvtty
114+
rm -f contrib/cmd/sd-helper/sd-helper
113115
rm -rf release
114116
rm -rf man/man8
115117

@@ -147,7 +149,7 @@ localcross:
147149
CGO_ENABLED=1 GOARCH=arm64 CC=aarch64-linux-gnu-gcc $(GO_BUILD) -o runc-arm64 .
148150
CGO_ENABLED=1 GOARCH=ppc64le CC=powerpc64le-linux-gnu-gcc $(GO_BUILD) -o runc-ppc64le .
149151

150-
.PHONY: runc all recvtty static release dbuild lint man runcimage \
152+
.PHONY: runc all recvtty sd-helper static release dbuild lint man runcimage \
151153
test localtest unittest localunittest integration localintegration \
152154
rootlessintegration localrootlessintegration shell install install-bash \
153155
install-man clean cfmt shfmt shellcheck \

contrib/cmd/sd-helper/helper.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
8+
"github.com/sirupsen/logrus"
9+
10+
"github.com/opencontainers/runc/libcontainer/cgroups"
11+
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
12+
"github.com/opencontainers/runc/libcontainer/configs"
13+
)
14+
15+
func usage() {
16+
fmt.Print(`Open Container Initiative contrib/cmd/sd-helper
17+
18+
sd-helper is a tool that uses runc/libcontainer/cgroups/systemd package
19+
functionality to communicate to systemd in order to perform various operations.
20+
Currently this is limited to starting and stopping systemd transient slice
21+
units.
22+
23+
Usage:
24+
sd-helper [-debug] [-parent <pname>] {start|stop} <name>
25+
26+
Example:
27+
sd-helper -parent system.slice start system-pod123.slice
28+
`)
29+
os.Exit(1)
30+
}
31+
32+
var (
33+
debug = flag.Bool("debug", false, "enable debug output")
34+
parent = flag.String("parent", "", "parent unit name")
35+
)
36+
37+
func main() {
38+
if !systemd.IsRunningSystemd() {
39+
logrus.Fatal("systemd is required")
40+
}
41+
42+
// Set the flags.
43+
flag.Parse()
44+
if *debug {
45+
logrus.SetLevel(logrus.DebugLevel)
46+
}
47+
if flag.NArg() != 2 {
48+
usage()
49+
}
50+
51+
cmd := flag.Arg(0)
52+
unit := flag.Arg(1)
53+
54+
err := unitCommand(cmd, unit, *parent)
55+
if err != nil {
56+
logrus.Fatal(err)
57+
}
58+
}
59+
60+
func newManager(config *configs.Cgroup) cgroups.Manager {
61+
if cgroups.IsCgroup2UnifiedMode() {
62+
return systemd.NewUnifiedManager(config, "", false)
63+
}
64+
return systemd.NewLegacyManager(config, nil)
65+
}
66+
67+
func unitCommand(cmd, name, parent string) error {
68+
podConfig := &configs.Cgroup{
69+
Name: name,
70+
Parent: parent,
71+
Resources: &configs.Resources{},
72+
}
73+
pm := newManager(podConfig)
74+
75+
switch cmd {
76+
case "start":
77+
return pm.Apply(-1)
78+
case "stop":
79+
return pm.Destroy()
80+
}
81+
82+
return fmt.Errorf("unknown command: %s", cmd)
83+
}

libcontainer/cgroups/fs/cpu.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ package fs
44

55
import (
66
"bufio"
7+
"errors"
78
"fmt"
89
"os"
910
"strconv"
1011

1112
"github.com/opencontainers/runc/libcontainer/cgroups"
1213
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
1314
"github.com/opencontainers/runc/libcontainer/configs"
15+
"golang.org/x/sys/unix"
1416
)
1517

1618
type CpuGroup struct{}
@@ -71,15 +73,33 @@ func (s *CpuGroup) Set(path string, r *configs.Resources) error {
7173
return fmt.Errorf("the minimum allowed cpu-shares is %d", sharesRead)
7274
}
7375
}
76+
77+
var period string
7478
if r.CpuPeriod != 0 {
75-
if err := cgroups.WriteFile(path, "cpu.cfs_period_us", strconv.FormatUint(r.CpuPeriod, 10)); err != nil {
76-
return err
79+
period = strconv.FormatUint(r.CpuPeriod, 10)
80+
if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
81+
// Sometimes when the period to be set is smaller
82+
// than the current one, it is rejected by the kernel
83+
// (EINVAL) as old_quota/new_period exceeds the parent
84+
// cgroup quota limit. If this happens and the quota is
85+
// going to be set, ignore the error for now and retry
86+
// after setting the quota.
87+
if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
88+
return err
89+
}
90+
} else {
91+
period = ""
7792
}
7893
}
7994
if r.CpuQuota != 0 {
8095
if err := cgroups.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(r.CpuQuota, 10)); err != nil {
8196
return err
8297
}
98+
if period != "" {
99+
if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
100+
return err
101+
}
102+
}
83103
}
84104
return s.SetRtSched(path, r)
85105
}

tests/integration/helpers.bash

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ unset IMAGES
1616

1717
RUNC="${INTEGRATION_ROOT}/../../runc"
1818
RECVTTY="${INTEGRATION_ROOT}/../../contrib/cmd/recvtty/recvtty"
19+
SD_HELPER="${INTEGRATION_ROOT}/../../contrib/cmd/sd-helper/sd-helper"
1920

2021
# Test data path.
2122
# shellcheck disable=SC2034
@@ -131,24 +132,85 @@ function init_cgroup_paths() {
131132
fi
132133
}
133134

135+
function create_parent() {
136+
if [ -n "$RUNC_USE_SYSTEMD" ]; then
137+
[ -z "$SD_PARENT_NAME" ] && return
138+
"$SD_HELPER" --parent machine.slice start "$SD_PARENT_NAME"
139+
else
140+
[ -z "$REL_PARENT_PATH" ] && return
141+
if [ "$CGROUP_UNIFIED" == "yes" ]; then
142+
mkdir "/sys/fs/cgroup$REL_PARENT_PATH"
143+
else
144+
local subsys
145+
for subsys in ${CGROUP_SUBSYSTEMS}; do
146+
# Have to ignore EEXIST (-p) as some subsystems
147+
# are mounted together (e.g. cpu,cpuacct), so
148+
# the path is created more than once.
149+
mkdir -p "/sys/fs/cgroup/$subsys$REL_PARENT_PATH"
150+
done
151+
fi
152+
fi
153+
}
154+
155+
function remove_parent() {
156+
if [ -n "$RUNC_USE_SYSTEMD" ]; then
157+
[ -z "$SD_PARENT_NAME" ] && return
158+
"$SD_HELPER" --parent machine.slice stop "$SD_PARENT_NAME"
159+
else
160+
[ -z "$REL_PARENT_PATH" ] && return
161+
if [ "$CGROUP_UNIFIED" == "yes" ]; then
162+
rmdir "/sys/fs/cgroup/$REL_PARENT_PATH"
163+
else
164+
local subsys
165+
for subsys in ${CGROUP_SUBSYSTEMS} systemd; do
166+
rmdir "/sys/fs/cgroup/$subsys/$REL_PARENT_PATH"
167+
done
168+
fi
169+
fi
170+
unset SD_PARENT_NAME
171+
unset REL_PARENT_PATH
172+
}
173+
174+
function set_parent_systemd_properties() {
175+
[ -z "$SD_PARENT_NAME" ] && return
176+
local user
177+
[ "$(id -u)" != "0" ] && user="--user"
178+
systemctl set-property $user "$SD_PARENT_NAME" "$@"
179+
}
180+
134181
# Randomize cgroup path(s), and update cgroupsPath in config.json.
135182
# This function sets a few cgroup-related variables.
183+
#
184+
# Optional parameter $1 is a pod/parent name. If set, a parent/pod cgroup is
185+
# created, and variables $REL_PARENT_PATH and $SD_PARENT_NAME can be used to
186+
# refer to it.
136187
function set_cgroups_path() {
137188
init_cgroup_paths
189+
local pod dash_pod slash_pod pod_slice
190+
if [ "$#" -ne 0 ] && [ "$1" != "" ]; then
191+
# Set up a parent/pod cgroup.
192+
pod="$1"
193+
dash_pod="-$pod"
194+
slash_pod="/$pod"
195+
SD_PARENT_NAME="machine-${pod}.slice"
196+
pod_slice="/$SD_PARENT_NAME"
197+
fi
138198

139199
local rnd="$RANDOM"
140200
if [ -n "${RUNC_USE_SYSTEMD}" ]; then
141201
SD_UNIT_NAME="runc-cgroups-integration-test-${rnd}.scope"
142202
if [ "$(id -u)" = "0" ]; then
143-
REL_CGROUPS_PATH="/machine.slice/$SD_UNIT_NAME"
144-
OCI_CGROUPS_PATH="machine.slice:runc-cgroups:integration-test-${rnd}"
203+
REL_PARENT_PATH="/machine.slice${pod_slice}"
204+
OCI_CGROUPS_PATH="machine${dash_pod}.slice:runc-cgroups:integration-test-${rnd}"
145205
else
146-
REL_CGROUPS_PATH="/user.slice/user-$(id -u).slice/user@$(id -u).service/machine.slice/$SD_UNIT_NAME"
206+
REL_PARENT_PATH="/user.slice/user-$(id -u).slice/user@$(id -u).service/machine.slice${pod_slice}"
147207
# OCI path doesn't contain "/user.slice/user-$(id -u).slice/user@$(id -u).service/" prefix
148-
OCI_CGROUPS_PATH="machine.slice:runc-cgroups:integration-test-${rnd}"
208+
OCI_CGROUPS_PATH="machine${dash_pod}.slice:runc-cgroups:integration-test-${rnd}"
149209
fi
210+
REL_CGROUPS_PATH="$REL_PARENT_PATH/$SD_UNIT_NAME"
150211
else
151-
REL_CGROUPS_PATH="/runc-cgroups-integration-test/test-cgroup-${rnd}"
212+
REL_PARENT_PATH="/runc-cgroups-integration-test${slash_pod}"
213+
REL_CGROUPS_PATH="$REL_PARENT_PATH/test-cgroup-${rnd}"
152214
OCI_CGROUPS_PATH=$REL_CGROUPS_PATH
153215
fi
154216

@@ -157,6 +219,8 @@ function set_cgroups_path() {
157219
CGROUP_PATH=${CGROUP_BASE_PATH}${REL_CGROUPS_PATH}
158220
fi
159221

222+
[ -n "$pod" ] && create_parent
223+
160224
update_config '.linux.cgroupsPath |= "'"${OCI_CGROUPS_PATH}"'"'
161225
}
162226

@@ -479,4 +543,5 @@ function teardown_bundle() {
479543
__runc delete -f "$ct"
480544
done
481545
rm -rf "$ROOT"
546+
remove_parent
482547
}

tests/integration/update.bats

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,15 @@ EOF
345345
check_cpu_quota -1 1000000 "infinity"
346346
}
347347

348+
@test "set cpu period with no quota (invalid period)" {
349+
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
350+
351+
update_config '.linux.resources.cpu |= { "period": 100 }'
352+
353+
runc run -d --console-socket "$CONSOLE_SOCKET" test_update
354+
[ "$status" -eq 1 ]
355+
}
356+
348357
@test "set cpu quota with no period" {
349358
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
350359

@@ -383,6 +392,41 @@ EOF
383392
check_cpu_quota 30000 100000 "300ms"
384393
}
385394

395+
@test "update cpu period in a pod cgroup with pod limit set" {
396+
requires cgroups_v1
397+
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
398+
399+
set_cgroups_path "pod_${RANDOM}"
400+
401+
# Set parent/pod CPU quota limit to 50%.
402+
if [ -n "${RUNC_USE_SYSTEMD}" ]; then
403+
set_parent_systemd_properties CPUQuota="50%"
404+
else
405+
echo 50000 >"/sys/fs/cgroup/cpu/$REL_PARENT_PATH/cpu.cfs_quota_us"
406+
fi
407+
# Sanity checks.
408+
run cat "/sys/fs/cgroup/cpu$REL_PARENT_PATH/cpu.cfs_period_us"
409+
[ "$output" -eq 100000 ]
410+
run cat "/sys/fs/cgroup/cpu$REL_PARENT_PATH/cpu.cfs_quota_us"
411+
[ "$output" -eq 50000 ]
412+
413+
runc run -d --console-socket "$CONSOLE_SOCKET" test_update
414+
[ "$status" -eq 0 ]
415+
# Get the current period.
416+
local cur
417+
cur=$(get_cgroup_value cpu.cfs_period_us)
418+
419+
# Sanity check: as the parent cgroup sets the limit to 50%,
420+
# setting a higher limit (e.g. 60%) is expected to fail.
421+
runc update --cpu-quota $((cur * 6 / 10)) test_update
422+
[ "$status" -eq 1 ]
423+
424+
# Finally, the test itself: set 30% limit but with lower period.
425+
runc update --cpu-period 10000 --cpu-quota 3000 test_update
426+
[ "$status" -eq 0 ]
427+
check_cpu_quota 3000 10000 "300ms"
428+
}
429+
386430
@test "update cgroup v2 resources via unified map" {
387431
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
388432
requires cgroups_v2

0 commit comments

Comments
 (0)