Skip to content

Commit ea38465

Browse files
authored
Merge pull request #3783 from utam0k/io-prio
Add I/O priority
2 parents a1acca9 + bfbd030 commit ea38465

File tree

10 files changed

+124
-1
lines changed

10 files changed

+124
-1
lines changed

docs/spec-conformance.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ The following features are not implemented yet:
88
Spec version | Feature | PR
99
-------------|------------------------------------------------|----------------------------------------------------------
1010
v1.1.0 | `SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV` | [#3862](https://github.com/opencontainers/runc/pull/3862)
11-
v1.1.0 | `.process.ioPriority` | [#3783](https://github.com/opencontainers/runc/pull/3783)
1211

1312
## Architectures
1413

libcontainer/configs/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ type Config struct {
222222

223223
// Personality contains configuration for the Linux personality syscall.
224224
Personality *LinuxPersonality `json:"personality,omitempty"`
225+
226+
// IOPriority is the container's I/O priority.
227+
IOPriority *IOPriority `json:"io_priority,omitempty"`
225228
}
226229

227230
// Scheduler is based on the Linux sched_setattr(2) syscall.
@@ -283,6 +286,14 @@ func ToSchedAttr(scheduler *Scheduler) (*unix.SchedAttr, error) {
283286
}, nil
284287
}
285288

289+
var IOPrioClassMapping = map[specs.IOPriorityClass]int{
290+
specs.IOPRIO_CLASS_RT: 1,
291+
specs.IOPRIO_CLASS_BE: 2,
292+
specs.IOPRIO_CLASS_IDLE: 3,
293+
}
294+
295+
type IOPriority = specs.LinuxIOPriority
296+
286297
type (
287298
HookName string
288299
HookList []Hook

libcontainer/configs/validate/validator.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func Validate(config *configs.Config) error {
3232
rootlessEUIDCheck,
3333
mountsStrict,
3434
scheduler,
35+
ioPriority,
3536
}
3637
for _, c := range checks {
3738
if err := c(config); err != nil {
@@ -396,3 +397,14 @@ func scheduler(config *configs.Config) error {
396397
}
397398
return nil
398399
}
400+
401+
func ioPriority(config *configs.Config) error {
402+
if config.IOPriority == nil {
403+
return nil
404+
}
405+
priority := config.IOPriority.Priority
406+
if priority < 0 || priority > 7 {
407+
return fmt.Errorf("invalid ioPriority.Priority: %d", priority)
408+
}
409+
return nil
410+
}

libcontainer/configs/validate/validator_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,3 +842,32 @@ func TestValidateScheduler(t *testing.T) {
842842
}
843843
}
844844
}
845+
846+
func TestValidateIOPriority(t *testing.T) {
847+
testCases := []struct {
848+
isErr bool
849+
priority int
850+
}{
851+
{isErr: false, priority: 0},
852+
{isErr: false, priority: 7},
853+
{isErr: true, priority: -1},
854+
}
855+
856+
for _, tc := range testCases {
857+
ioPriroty := configs.IOPriority{
858+
Priority: tc.priority,
859+
}
860+
config := &configs.Config{
861+
Rootfs: "/var",
862+
IOPriority: &ioPriroty,
863+
}
864+
865+
err := Validate(config)
866+
if tc.isErr && err == nil {
867+
t.Errorf("iopriority: %d, expected error, got nil", tc.priority)
868+
}
869+
if !tc.isErr && err != nil {
870+
t.Errorf("iopriority: %d, expected nil, got error %v", tc.priority, err)
871+
}
872+
}
873+
}

libcontainer/process.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ type Process struct {
100100
SubCgroupPaths map[string]string
101101

102102
Scheduler *configs.Scheduler
103+
104+
IOPriority *configs.IOPriority
103105
}
104106

105107
// Wait waits for the process to exit.

libcontainer/process_linux.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ func (p *setnsProcess) signal(sig os.Signal) error {
124124

125125
func (p *setnsProcess) start() (retErr error) {
126126
defer p.comm.closeParent()
127+
128+
if p.process.IOPriority != nil {
129+
if err := setIOPriority(p.process.IOPriority); err != nil {
130+
return err
131+
}
132+
}
133+
127134
// get the "before" value of oom kill count
128135
oom, _ := p.manager.OOMKillCount()
129136
err := p.cmd.Start()
@@ -972,3 +979,21 @@ func initWaiter(r io.Reader) chan error {
972979

973980
return ch
974981
}
982+
983+
func setIOPriority(ioprio *configs.IOPriority) error {
984+
const ioprioWhoPgrp = 1
985+
986+
class, ok := configs.IOPrioClassMapping[ioprio.Class]
987+
if !ok {
988+
return fmt.Errorf("invalid io priority class: %s", ioprio.Class)
989+
}
990+
991+
// Combine class and priority into a single value
992+
// https://github.com/torvalds/linux/blob/v5.18/include/uapi/linux/ioprio.h#L5-L17
993+
iop := (class << 13) | ioprio.Priority
994+
_, _, errno := unix.RawSyscall(unix.SYS_IOPRIO_SET, ioprioWhoPgrp, 0, uintptr(iop))
995+
if errno != 0 {
996+
return fmt.Errorf("failed to set io priority: %w", errno)
997+
}
998+
return nil
999+
}

libcontainer/specconv/spec_linux.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,11 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
534534
s := *spec.Process.Scheduler
535535
config.Scheduler = &s
536536
}
537+
538+
if spec.Process.IOPriority != nil {
539+
ioPriority := *spec.Process.IOPriority
540+
config.IOPriority = &ioPriority
541+
}
537542
}
538543
createHooks(spec, config)
539544
config.Version = specs.Version

libcontainer/standard_init_linux.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ func (l *linuxStandardInit) Init() error {
161161
return err
162162
}
163163
}
164+
if l.config.Config.IOPriority != nil {
165+
if err := setIOPriority(l.config.Config.IOPriority); err != nil {
166+
return err
167+
}
168+
}
164169

165170
// Tell our parent that we're ready to Execv. This must be done before the
166171
// Seccomp rules have been applied, because we need to be able to read and

tests/integration/ioprio.bats

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env bats
2+
3+
load helpers
4+
5+
function setup() {
6+
setup_debian
7+
}
8+
9+
function teardown() {
10+
teardown_bundle
11+
}
12+
13+
@test "ioprio_set is applied to process group" {
14+
# Create a container with a specific I/O priority.
15+
update_config '.process.ioPriority = {"class": "IOPRIO_CLASS_BE", "priority": 4}'
16+
17+
runc run -d --console-socket "$CONSOLE_SOCKET" test_ioprio
18+
[ "$status" -eq 0 ]
19+
20+
# Check the init process.
21+
runc exec test_ioprio ionice -p 1
22+
[ "$status" -eq 0 ]
23+
[[ "$output" = *'best-effort: prio 4'* ]]
24+
25+
# Check the process made from the exec command.
26+
runc exec test_ioprio ionice
27+
[ "$status" -eq 0 ]
28+
29+
[[ "$output" = *'best-effort: prio 4'* ]]
30+
}

utils_linux.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ func newProcess(p specs.Process) (*libcontainer.Process, error) {
6767
lp.Scheduler = &s
6868
}
6969

70+
if p.IOPriority != nil {
71+
ioPriority := *p.IOPriority
72+
lp.IOPriority = &ioPriority
73+
}
74+
7075
if p.Capabilities != nil {
7176
lp.Capabilities = &configs.Capabilities{}
7277
lp.Capabilities.Bounding = p.Capabilities.Bounding

0 commit comments

Comments
 (0)