1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
|
From 3ce5282fcf5519c288ab677def3e93488ec01b80 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Sun, 20 Sep 2026 19:55:55 +0100
Subject: [PATCH] validation: skip cgroup v1 only resource fields
The cgroup test bundles ask for memory.swappiness, memory.disableOOMKiller,
memory.kernel, memory.kernelTCP, blockIO.leafWeight and the block IO
weights on every host. None of those exist in the unified hierarchy, and
the create operation requires a runtime that cannot apply a property to
generate an error and not create the container. A correct cgroup v2 only
runtime therefore fails these tests by doing exactly what the spec asks
of it.
Set those fields only where the hierarchy can carry them. cpu.shares goes
the same way: runtimes map it onto cpu.weight with a lossy conversion the
runtime-spec does not define, so the configured value cannot be recovered
and asserting on it would test a convention rather than the spec.
Compare the remaining fields with a helper that reports a value neither
side carries as a diagnostic instead of a failure, so that one unsupported
field no longer hides the checks that follow it: the block IO and cpu
validators used to return early and silently drop every later assertion.
linux_cgroups_relative_cpus grows the same behaviour by using the shared
CPU validator its siblings already use rather than its own copy of it.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
.../linux_cgroups_blkio.go | 16 +++++--
.../linux_cgroups_cpus/linux_cgroups_cpus.go | 5 +-
.../linux_cgroups_memory.go | 10 ++--
.../linux_cgroups_relative_blkio.go | 13 ++++--
.../linux_cgroups_relative_cpus.go | 41 +++--------------
.../linux_cgroups_relative_memory.go | 10 ++--
validation/util/linux_cgroups.go | 31 +++++++++++++
validation/util/linux_resources_blkio.go | 24 ++--------
validation/util/linux_resources_cpus.go | 46 +++----------------
validation/util/linux_resources_memory.go | 28 ++++-------
10 files changed, 92 insertions(+), 132 deletions(-)
create mode 100644 validation/util/linux_cgroups.go
--- a/validation/linux_cgroups_blkio/linux_cgroups_blkio.go
+++ b/validation/linux_cgroups_blkio/linux_cgroups_blkio.go
@@ -30,13 +30,19 @@ func testBlkioCgroups(rate uint64, isEmp
g.SetLinuxCgroupsPath(cgroups.AbsCgroupPath)
- if !isEmpty {
- g.SetLinuxResourcesBlockIOWeight(weight)
- g.SetLinuxResourcesBlockIOLeafWeight(leafWeight)
+ // The unified hierarchy has no leaf weights, and writes the weight to a
+ // file that depends on the IO scheduler in use, so it cannot be read
+ // back to compare it with the configured value.
+ if !util.UnifiedCgroups() {
+ if !isEmpty {
+ g.SetLinuxResourcesBlockIOWeight(weight)
+ g.SetLinuxResourcesBlockIOLeafWeight(leafWeight)
+ }
+
+ g.AddLinuxResourcesBlockIOWeightDevice(major, minor, weight)
+ g.AddLinuxResourcesBlockIOLeafWeightDevice(major, minor, leafWeight)
}
- g.AddLinuxResourcesBlockIOWeightDevice(major, minor, weight)
- g.AddLinuxResourcesBlockIOLeafWeightDevice(major, minor, leafWeight)
g.AddLinuxResourcesBlockIOThrottleReadBpsDevice(major, minor, rate)
g.AddLinuxResourcesBlockIOThrottleWriteBpsDevice(major, minor, rate)
g.AddLinuxResourcesBlockIOThrottleReadIOPSDevice(major, minor, rate)
--- a/validation/linux_cgroups_cpus/linux_cgroups_cpus.go
+++ b/validation/linux_cgroups_cpus/linux_cgroups_cpus.go
@@ -59,7 +59,10 @@ func testCPUCgroups() error {
g.SetLinuxCgroupsPath(cgroups.AbsCgroupPath)
- if c.shares > 0 {
+ // cpu.shares is mapped onto cpu.weight by a conversion the
+ // runtime-spec does not define, and the mapping is lossy, so
+ // the configured value cannot be recovered.
+ if c.shares > 0 && !util.UnifiedCgroups() {
g.SetLinuxResourcesCPUShares(c.shares)
}
--- a/validation/linux_cgroups_memory/linux_cgroups_memory.go
+++ b/validation/linux_cgroups_memory/linux_cgroups_memory.go
@@ -38,10 +38,12 @@ func main() {
g.SetLinuxResourcesMemoryLimit(c.limit)
g.SetLinuxResourcesMemoryReservation(c.limit)
g.SetLinuxResourcesMemorySwap(c.limit)
- g.SetLinuxResourcesMemoryKernel(c.limit)
- g.SetLinuxResourcesMemoryKernelTCP(c.limit)
- g.SetLinuxResourcesMemorySwappiness(c.swappiness)
- g.SetLinuxResourcesMemoryDisableOOMKiller(true)
+ if !util.UnifiedCgroups() {
+ g.SetLinuxResourcesMemoryKernel(c.limit)
+ g.SetLinuxResourcesMemoryKernelTCP(c.limit)
+ g.SetLinuxResourcesMemorySwappiness(c.swappiness)
+ g.SetLinuxResourcesMemoryDisableOOMKiller(true)
+ }
err = util.RuntimeOutsideValidate(g, t, util.ValidateLinuxResourcesMemory)
if err != nil {
t.Fail(err.Error())
--- a/validation/linux_cgroups_relative_blkio/linux_cgroups_relative_blkio.go
+++ b/validation/linux_cgroups_relative_blkio/linux_cgroups_relative_blkio.go
@@ -21,10 +21,15 @@ func main() {
util.Fatal(err)
}
g.SetLinuxCgroupsPath(cgroups.RelCgroupPath)
- g.SetLinuxResourcesBlockIOWeight(weight)
- g.SetLinuxResourcesBlockIOLeafWeight(leafWeight)
- g.AddLinuxResourcesBlockIOWeightDevice(major, minor, weight)
- g.AddLinuxResourcesBlockIOLeafWeightDevice(major, minor, leafWeight)
+ // The unified hierarchy has no leaf weights, and writes the weight to a
+ // file that depends on the IO scheduler in use, so it cannot be read
+ // back to compare it with the configured value.
+ if !util.UnifiedCgroups() {
+ g.SetLinuxResourcesBlockIOWeight(weight)
+ g.SetLinuxResourcesBlockIOLeafWeight(leafWeight)
+ g.AddLinuxResourcesBlockIOWeightDevice(major, minor, weight)
+ g.AddLinuxResourcesBlockIOLeafWeightDevice(major, minor, leafWeight)
+ }
g.AddLinuxResourcesBlockIOThrottleReadBpsDevice(major, minor, rate)
g.AddLinuxResourcesBlockIOThrottleWriteBpsDevice(major, minor, rate)
g.AddLinuxResourcesBlockIOThrottleReadIOPSDevice(major, minor, rate)
--- a/validation/linux_cgroups_relative_cpus/linux_cgroups_relative_cpus.go
+++ b/validation/linux_cgroups_relative_cpus/linux_cgroups_relative_cpus.go
@@ -2,7 +2,6 @@ package main
import (
"github.com/mndrix/tap-go"
- rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/cgroups"
"github.com/opencontainers/runtime-tools/validation/util"
)
@@ -24,43 +23,17 @@ func main() {
util.Fatal(err)
}
g.SetLinuxCgroupsPath(cgroups.RelCgroupPath)
- g.SetLinuxResourcesCPUShares(shares)
+ // cpu.shares is mapped onto cpu.weight by a conversion the runtime-spec
+ // does not define, and the mapping is lossy, so the configured value
+ // cannot be recovered.
+ if !util.UnifiedCgroups() {
+ g.SetLinuxResourcesCPUShares(shares)
+ }
g.SetLinuxResourcesCPUQuota(quota)
g.SetLinuxResourcesCPUPeriod(period)
g.SetLinuxResourcesCPUCpus(cpus)
g.SetLinuxResourcesCPUMems(mems)
- err = util.RuntimeOutsideValidate(g, t, func(config *rspec.Spec, t *tap.T, state *rspec.State) error {
- cg, err := cgroups.FindCgroup()
- t.Ok((err == nil), "find cpus cgroup")
- if err != nil {
- t.Diagnostic(err.Error())
- return nil
- }
-
- lcd, err := cg.GetCPUData(state.Pid, config.Linux.CgroupsPath)
- t.Ok((err == nil), "get cpus cgroup data")
- if err != nil {
- t.Diagnostic(err.Error())
- return nil
- }
-
- t.Ok(*lcd.Shares == shares, "cpus shares limit is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", shares, lcd.Shares)
-
- t.Ok(*lcd.Quota == quota, "cpus quota is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", quota, lcd.Quota)
-
- t.Ok(*lcd.Period == period, "cpus period is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", period, lcd.Period)
-
- t.Ok(lcd.Cpus == cpus, "cpus cpus is set correctly")
- t.Diagnosticf("expect: %s, actual: %s", cpus, lcd.Cpus)
-
- t.Ok(lcd.Mems == mems, "cpus mems is set correctly")
- t.Diagnosticf("expect: %s, actual: %s", mems, lcd.Mems)
-
- return nil
- })
+ err = util.RuntimeOutsideValidate(g, t, util.ValidateLinuxResourcesCPU)
if err != nil {
t.Fail(err.Error())
--- a/validation/linux_cgroups_relative_memory/linux_cgroups_relative_memory.go
+++ b/validation/linux_cgroups_relative_memory/linux_cgroups_relative_memory.go
@@ -22,10 +22,12 @@ func main() {
g.SetLinuxResourcesMemoryLimit(limit)
g.SetLinuxResourcesMemoryReservation(limit)
g.SetLinuxResourcesMemorySwap(limit)
- g.SetLinuxResourcesMemoryKernel(limit)
- g.SetLinuxResourcesMemoryKernelTCP(limit)
- g.SetLinuxResourcesMemorySwappiness(swappiness)
- g.SetLinuxResourcesMemoryDisableOOMKiller(true)
+ if !util.UnifiedCgroups() {
+ g.SetLinuxResourcesMemoryKernel(limit)
+ g.SetLinuxResourcesMemoryKernelTCP(limit)
+ g.SetLinuxResourcesMemorySwappiness(swappiness)
+ g.SetLinuxResourcesMemoryDisableOOMKiller(true)
+ }
err = util.RuntimeOutsideValidate(g, t, util.ValidateLinuxResourcesMemory)
if err != nil {
t.Fail(err.Error())
--- /dev/null
+++ b/validation/util/linux_cgroups.go
@@ -0,0 +1,31 @@
+package util
+
+import (
+ "github.com/mndrix/tap-go"
+ "github.com/opencontainers/runtime-tools/cgroups"
+)
+
+// UnifiedCgroups reports whether the host mounts the unified hierarchy,
+// on which several of the cgroup v1 resource fields have no counterpart.
+func UnifiedCgroups() bool {
+ cg, err := cgroups.FindCgroup()
+ if err != nil {
+ return false
+ }
+
+ return cg.Version() == 2
+}
+
+// checkValue compares a configured resource with the value read back from
+// the cgroup filesystem. A field that either side does not carry is
+// reported as a diagnostic rather than counted as a failed check, because
+// the hierarchy in use may have no way to express it.
+func checkValue[T comparable](t *tap.T, name string, expected *T, actual *T) {
+ if expected == nil || actual == nil {
+ t.Diagnosticf("skipping %s: not expressed by this cgroup hierarchy", name)
+ return
+ }
+
+ t.Ok(*expected == *actual, name+" is set correctly")
+ t.Diagnosticf("expect: %v, actual: %v", *expected, *actual)
+}
--- a/validation/util/linux_resources_blkio.go
+++ b/validation/util/linux_resources_blkio.go
@@ -24,32 +24,16 @@ func ValidateLinuxResourcesBlockIO(confi
return nil
}
- if lbd.Weight == nil || config.Linux.Resources.BlockIO.Weight == nil {
- t.Diagnostic(fmt.Sprintf("unable to get weight: lbd.Weight == %v, config.Linux.Resources.BlockIO.Weight == %v", lbd.Weight, config.Linux.Resources.BlockIO.Weight))
- return nil
- }
-
- t.Ok(*lbd.Weight == *config.Linux.Resources.BlockIO.Weight, "blkio weight is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.BlockIO.Weight, *lbd.Weight)
-
- if lbd.LeafWeight == nil || config.Linux.Resources.BlockIO.LeafWeight == nil {
- t.Diagnostic(fmt.Sprintf("unable to get leafWeight: lbd.LeafWeight == %v, config.Linux.Resources.BlockIO.LeafWeight == %v", lbd.LeafWeight, config.Linux.Resources.BlockIO.LeafWeight))
- return nil
- }
-
- t.Ok(*lbd.LeafWeight == *config.Linux.Resources.BlockIO.LeafWeight, "blkio leafWeight is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.BlockIO.LeafWeight, *lbd.LeafWeight)
+ checkValue(t, "blkio weight", config.Linux.Resources.BlockIO.Weight, lbd.Weight)
+ checkValue(t, "blkio leafWeight", config.Linux.Resources.BlockIO.LeafWeight, lbd.LeafWeight)
for _, device := range config.Linux.Resources.BlockIO.WeightDevice {
found := false
for _, wd := range lbd.WeightDevice {
if wd.Major == device.Major && wd.Minor == device.Minor {
found = true
- t.Ok(*wd.Weight == *device.Weight, fmt.Sprintf("blkio weight for %d:%d is set correctly", device.Major, device.Minor))
- t.Diagnosticf("expect: %d, actual: %d", *device.Weight, *wd.Weight)
-
- t.Ok(*wd.LeafWeight == *device.LeafWeight, fmt.Sprintf("blkio leafWeight for %d:%d is set correctly", device.Major, device.Minor))
- t.Diagnosticf("expect: %d, actual: %d", *device.LeafWeight, *wd.LeafWeight)
+ checkValue(t, fmt.Sprintf("blkio weight for %d:%d", device.Major, device.Minor), device.Weight, wd.Weight)
+ checkValue(t, fmt.Sprintf("blkio leafWeight for %d:%d", device.Major, device.Minor), device.LeafWeight, wd.LeafWeight)
}
}
t.Ok(found, fmt.Sprintf("blkio weightDevice for %d:%d found", device.Major, device.Minor))
--- a/validation/util/linux_resources_cpus.go
+++ b/validation/util/linux_resources_cpus.go
@@ -35,26 +35,9 @@ func ValidateLinuxResourcesCPU(config *r
return nil
}
- if lcd.Shares == nil || config.Linux.Resources.CPU.Shares == nil {
- t.Diagnostic(fmt.Sprintf("unable to get cpu shares, lcd.Shares == %v, config.Linux.Resources.CPU.Shares == %v", lcd.Shares, config.Linux.Resources.CPU.Shares))
- return nil
- }
- t.Ok(*lcd.Shares == *config.Linux.Resources.CPU.Shares, "cpu shares is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.CPU.Shares, *lcd.Shares)
-
- if lcd.Period == nil || config.Linux.Resources.CPU.Period == nil {
- t.Diagnostic(fmt.Sprintf("unable to get cpu period, lcd.Period == %v, config.Linux.Resources.CPU.Period == %v", lcd.Period, config.Linux.Resources.CPU.Period))
- return nil
- }
- t.Ok(*lcd.Period == *config.Linux.Resources.CPU.Period, "cpu period is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.CPU.Period, *lcd.Period)
-
- if lcd.Quota == nil || config.Linux.Resources.CPU.Quota == nil {
- t.Diagnostic(fmt.Sprintf("unable to get cpu quota, lcd.Quota == %v, config.Linux.Resources.CPU.Quota == %v", lcd.Quota, config.Linux.Resources.CPU.Quota))
- return nil
- }
- t.Ok(*lcd.Quota == *config.Linux.Resources.CPU.Quota, "cpu quota is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.CPU.Quota, *lcd.Quota)
+ checkValue(t, "cpu shares", config.Linux.Resources.CPU.Shares, lcd.Shares)
+ checkValue(t, "cpu period", config.Linux.Resources.CPU.Period, lcd.Period)
+ checkValue(t, "cpu quota", config.Linux.Resources.CPU.Quota, lcd.Quota)
t.Ok(lcd.Cpus == config.Linux.Resources.CPU.Cpus, "cpu cpus is set correctly")
t.Diagnosticf("expect: %s, actual: %s", config.Linux.Resources.CPU.Cpus, lcd.Cpus)
@@ -106,26 +89,9 @@ func ValidateLinuxResourcesCPUEmpty(conf
return nil
}
- if lcd.Shares == nil {
- t.Diagnostic(fmt.Sprintf("unable to get cpu shares, lcd.Shares == %v", lcd.Shares))
- return nil
- }
- t.Ok(*lcd.Shares == defaultShares, "cpu shares is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", defaultShares, *lcd.Shares)
-
- if lcd.Period == nil {
- t.Diagnostic(fmt.Sprintf("unable to get cpu period, lcd.Period == %v", lcd.Period))
- return nil
- }
- t.Ok(*lcd.Period == defaultPeriod, "cpu period is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", defaultPeriod, *lcd.Period)
-
- if lcd.Quota == nil {
- t.Diagnostic(fmt.Sprintf("unable to get cpu quota, lcd.Quota == %v", lcd.Quota))
- return nil
- }
- t.Ok(*lcd.Quota == defaultQuota, "cpu quota is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", defaultQuota, *lcd.Quota)
+ checkValue(t, "cpu shares", &defaultShares, lcd.Shares)
+ checkValue(t, "cpu period", &defaultPeriod, lcd.Period)
+ checkValue(t, "cpu quota", &defaultQuota, lcd.Quota)
t.Ok(lcd.Cpus == defaultCpus, "cpu cpus is set correctly")
t.Diagnosticf("expect: %s, actual: %s", defaultCpus, lcd.Cpus)
--- a/validation/util/linux_resources_memory.go
+++ b/validation/util/linux_resources_memory.go
@@ -22,26 +22,14 @@ func ValidateLinuxResourcesMemory(config
return nil
}
- t.Ok(*lm.Limit == *config.Linux.Resources.Memory.Limit, "memory limit is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.Memory.Limit, *lm.Limit)
-
- t.Ok(*lm.Reservation == *config.Linux.Resources.Memory.Reservation, "memory reservation is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.Memory.Reservation, *lm.Reservation)
-
- t.Ok(*lm.Swap == *config.Linux.Resources.Memory.Swap, "memory swap is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.Memory.Swap, *lm.Reservation)
-
- t.Ok(*lm.Kernel == *config.Linux.Resources.Memory.Kernel, "memory kernel is set correctly") //nolint:staticcheck // Ignore SA1019: lm.Kernel is deprecated
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.Memory.Kernel, *lm.Kernel) //nolint:staticcheck // Ignore SA1019: config.Linux.Resources.Memory.Kernel is deprecated
-
- t.Ok(*lm.KernelTCP == *config.Linux.Resources.Memory.KernelTCP, "memory kernelTCP is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.Memory.KernelTCP, *lm.Kernel) //nolint:staticcheck // Ignore SA1019: lm.Kernel is deprecated
-
- t.Ok(*lm.Swappiness == *config.Linux.Resources.Memory.Swappiness, "memory swappiness is set correctly")
- t.Diagnosticf("expect: %d, actual: %d", *config.Linux.Resources.Memory.Swappiness, *lm.Swappiness)
-
- t.Ok(*lm.DisableOOMKiller == *config.Linux.Resources.Memory.DisableOOMKiller, "memory oom is set correctly")
- t.Diagnosticf("expect: %t, actual: %t", *config.Linux.Resources.Memory.DisableOOMKiller, *lm.DisableOOMKiller)
+ lrm := config.Linux.Resources.Memory
+ checkValue(t, "memory limit", lrm.Limit, lm.Limit)
+ checkValue(t, "memory reservation", lrm.Reservation, lm.Reservation)
+ checkValue(t, "memory swap", lrm.Swap, lm.Swap)
+ checkValue(t, "memory kernel", lrm.Kernel, lm.Kernel) //nolint:staticcheck // Ignore SA1019: Kernel is deprecated
+ checkValue(t, "memory kernelTCP", lrm.KernelTCP, lm.KernelTCP)
+ checkValue(t, "memory swappiness", lrm.Swappiness, lm.Swappiness)
+ checkValue(t, "memory oom", lrm.DisableOOMKiller, lm.DisableOOMKiller)
return nil
}
|