@@ -194,6 +194,20 @@ def move_pid(self, cgname, pid):
194
194
cg_pids = self .root .joinpath (f"{ cgname } /cgroup.procs" )
195
195
cg_pids .write_text (f"{ pid } \n " , encoding = "ascii" )
196
196
197
+ def enable_controller_in_subtree (self , cgname , controller ):
198
+ """Enable a controller in subtree_control of a cgroup and its ancestors"""
199
+ # Enable the controller in all ancestors if not already enabled.
200
+ parent_cg = self .root .joinpath (cgname ).parent
201
+ parent_subtree_control = parent_cg .joinpath ("cgroup.subtree_control" )
202
+ if controller not in parent_subtree_control .read_text (encoding = "ascii" ):
203
+ self .enable_controller_in_subtree (
204
+ parent_cg .relative_to (self .root ), controller
205
+ )
206
+
207
+ subtree_control = self .root .joinpath (f"{ cgname } /cgroup.subtree_control" )
208
+ subtree_control .write_text (f"+{ controller } " , encoding = "ascii" )
209
+ assert controller in subtree_control .read_text (encoding = "ascii" )
210
+
197
211
198
212
@pytest .fixture (scope = "session" , autouse = True )
199
213
def cgroups_info ():
@@ -230,11 +244,7 @@ def check_cgroups_v2(vm):
230
244
cg_parent = cg .root / parent_cgroup
231
245
cg_jail = cg_parent / vm .jailer .jailer_id
232
246
233
- # if no cgroups were specified, then the jailer should move the FC process
234
- # to the parent group
235
- if len (vm .jailer .cgroups ) == 0 :
236
- procs = cg_parent .joinpath ("cgroup.procs" ).read_text ().splitlines ()
237
- assert str (vm .firecracker_pid ) in procs
247
+ assert len (vm .jailer .cgroups ) > 1
238
248
239
249
for cgroup in vm .jailer .cgroups :
240
250
controller = cgroup .split ("." )[0 ]
@@ -393,11 +403,23 @@ def test_v1_default_cgroups(uvm_plain, cgroups_info):
393
403
check_cgroups_v1 (test_microvm .jailer .cgroups , test_microvm .jailer .jailer_id )
394
404
395
405
396
- def test_cgroups_custom_parent_move (uvm_plain , cgroups_info ):
406
+ @pytest .mark .parametrize (
407
+ "parent_exists,domain_controller_in_subtree" ,
408
+ [(True , False ), (True , True ), (False , None )],
409
+ )
410
+ def test_cgroups_parent_cgroup_but_no_cgroup (
411
+ uvm_plain , cgroups_info , parent_exists , domain_controller_in_subtree
412
+ ):
397
413
"""
398
- Test cgroups when a custom parent cgroup is used and no cgroups are specified
414
+ Test cgroups when `-- parent- cgroup` is used but no `--cgroup` are specified.
399
415
400
- In this case we just want to move under the parent cgroup
416
+ If the cgroup specified with `--parent-cgroup` exists, the jailer should
417
+ move to the specified cgroup instead of creating a new cgroup under it.
418
+ However, if the specified cgroup has domain controllers (e.g. `memory`)
419
+ enabled in `cgroup.subtree_control`, the move should fail.
420
+
421
+ If the specified cgroup does not exist, the jailer does not move the process
422
+ to any cgroup and proceeds without error.
401
423
"""
402
424
if cgroups_info .version != 2 :
403
425
pytest .skip ("cgroupsv2 only" )
@@ -407,9 +429,45 @@ def test_cgroups_custom_parent_move(uvm_plain, cgroups_info):
407
429
parent_cgroup = f"custom_cgroup/{ test_microvm .id [:8 ]} "
408
430
test_microvm .jailer .parent_cgroup = parent_cgroup
409
431
410
- cgroups_info .new_cgroup (parent_cgroup )
411
- test_microvm .spawn ()
412
- check_cgroups_v2 (test_microvm )
432
+ if parent_exists :
433
+ # Create the parent cgroup.
434
+ cgroups_info .new_cgroup (parent_cgroup )
435
+ if domain_controller_in_subtree :
436
+ # Enable "memory" controller in cgroup.subtree_control of the parent.
437
+ cgroups_info .enable_controller_in_subtree (parent_cgroup , "memory" )
438
+
439
+ # Check no --cgroups are specified just in case.
440
+ assert len (test_microvm .jailer .cgroups ) == 0
441
+
442
+ cg_parent = cgroups_info .root / parent_cgroup
443
+
444
+ if parent_exists :
445
+ if domain_controller_in_subtree :
446
+ # The jailer should have failed to move to the `parent_cgroup`
447
+ # since it has domain controllers enabled in
448
+ # `cgroup.subtree_control` due to the no internal process
449
+ # constraint.
450
+ # https://docs.kernel.org/admin-guide/cgroup-v2.html#no-internal-process-constraint
451
+ with pytest .raises (
452
+ ChildProcessError ,
453
+ match = (
454
+ rf"Failed to move process to cgroup \({ cg_parent } \) "
455
+ r"due to no internal process constraint: "
456
+ r"Resource busy \(os error 16\)"
457
+ ),
458
+ ):
459
+ test_microvm .spawn ()
460
+ else :
461
+ # The jailer should have moved to the `parent_cgroup` instead of
462
+ # creating a new cgroup under it and move to the new cgroup.
463
+ test_microvm .spawn ()
464
+ procs = cg_parent .joinpath ("cgroup.procs" ).read_text ().splitlines ()
465
+ assert str (test_microvm .firecracker_pid ) in procs
466
+ else :
467
+ # The jailer should not have moved to any cgroup and the parent
468
+ # still does not exist.
469
+ test_microvm .spawn ()
470
+ assert not cg_parent .exists ()
413
471
414
472
415
473
def test_args_default_resource_limits (uvm_plain ):
0 commit comments