Skip to content

Commit aa295b4

Browse files
jsanemetauniyal61
authored andcommitted
Have host look for CPU controller of cgroupsv2 location.
Make the host class look under '/sys/fs/cgroup/cgroup.controllers' for support of the cpu controller. The host will try searching through cgroupsv1 first, just like up until now, and in the case that fails, it will try cgroupsv2 then. The host will not support the feature if both checks fail. This new check needs to be mocked by all tests that focus on this piece of code, as it touches a system file that requires privileges. For such thing, the CGroupsFixture is defined to easily add suck mocking to all test cases that require so. I also removed old mocking at test_driver.py in favor of the fixture from above. Conflicts: nova/tests/unit/virt/libvirt/test_driver.py NOTE(auniyal): - as new cgroup fixture is added, removed old mocking in few more unit test cases in test_driver - did not remove test_guest_cpu_shares_with_multi_vcpu from test_driver Partial-Bug: #2008102 Change-Id: I99b57c27c8a4425389bec2b7f05af660bab85610 (cherry picked from commit 973ff4f) (cherry picked from commit eb3fe4d) (cherry picked from commit 9e86be5)
1 parent db8c7a6 commit aa295b4

File tree

9 files changed

+171
-51
lines changed

9 files changed

+171
-51
lines changed

nova/tests/fixtures/nova.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,77 @@ def setUp(self):
13561356
nova.privsep.sys_admin_pctxt, 'client_mode', False))
13571357

13581358

1359+
class CGroupsFixture(fixtures.Fixture):
1360+
"""Mocks checks made for available subsystems on the host's control group.
1361+
1362+
The fixture mocks all calls made on the host to verify the capabilities
1363+
provided by its kernel. Through this, one can simulate the underlying
1364+
system hosts work on top of and have tests react to expected outcomes from
1365+
such.
1366+
1367+
Use sample:
1368+
>>> cgroups = self.useFixture(CGroupsFixture())
1369+
>>> cgroups = self.useFixture(CGroupsFixture(version=2))
1370+
>>> cgroups = self.useFixture(CGroupsFixture())
1371+
... cgroups.version = 2
1372+
1373+
:attr version: Arranges mocks to simulate the host interact with nova
1374+
following the given version of cgroups.
1375+
Available values are:
1376+
- 0: All checks related to cgroups will return False.
1377+
- 1: Checks related to cgroups v1 will return True.
1378+
- 2: Checks related to cgroups v2 will return True.
1379+
Defaults to 1.
1380+
"""
1381+
1382+
def __init__(self, version=1):
1383+
self._cpuv1 = None
1384+
self._cpuv2 = None
1385+
1386+
self._version = version
1387+
1388+
@property
1389+
def version(self):
1390+
return self._version
1391+
1392+
@version.setter
1393+
def version(self, value):
1394+
self._version = value
1395+
self._update_mocks()
1396+
1397+
def setUp(self):
1398+
super().setUp()
1399+
self._cpuv1 = self.useFixture(fixtures.MockPatch(
1400+
'nova.virt.libvirt.host.Host._has_cgroupsv1_cpu_controller')).mock
1401+
self._cpuv2 = self.useFixture(fixtures.MockPatch(
1402+
'nova.virt.libvirt.host.Host._has_cgroupsv2_cpu_controller')).mock
1403+
self._update_mocks()
1404+
1405+
def _update_mocks(self):
1406+
if not self._cpuv1:
1407+
return
1408+
1409+
if not self._cpuv2:
1410+
return
1411+
1412+
if self.version == 0:
1413+
self._cpuv1.return_value = False
1414+
self._cpuv2.return_value = False
1415+
return
1416+
1417+
if self.version == 1:
1418+
self._cpuv1.return_value = True
1419+
self._cpuv2.return_value = False
1420+
return
1421+
1422+
if self.version == 2:
1423+
self._cpuv1.return_value = False
1424+
self._cpuv2.return_value = True
1425+
return
1426+
1427+
raise ValueError(f"Unknown cgroups version: '{self.version}'.")
1428+
1429+
13591430
class NoopQuotaDriverFixture(fixtures.Fixture):
13601431
"""A fixture to run tests using the NoopQuotaDriver.
13611432

nova/tests/functional/libvirt/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def setUp(self):
4242
super(ServersTestBase, self).setUp()
4343

4444
self.useFixture(nova_fixtures.LibvirtImageBackendFixture())
45+
self.useFixture(nova_fixtures.CGroupsFixture())
4546
self.libvirt = self.useFixture(nova_fixtures.LibvirtFixture())
4647
self.useFixture(nova_fixtures.OSBrickFixture())
4748

nova/tests/functional/libvirt/test_evacuate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ def setUp(self):
427427
self.useFixture(nova_fixtures.NeutronFixture(self))
428428
self.useFixture(nova_fixtures.GlanceFixture(self))
429429
self.useFixture(func_fixtures.PlacementFixture())
430+
self.useFixture(nova_fixtures.CGroupsFixture())
430431
fake_network.set_stub_network_methods(self)
431432

432433
api_fixture = self.useFixture(

nova/tests/functional/libvirt/test_vpmem.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def setUp(self):
7575
'nova.privsep.libvirt.get_pmem_namespaces',
7676
return_value=self.fake_pmem_namespaces))
7777
self.useFixture(nova_fixtures.LibvirtImageBackendFixture())
78+
self.useFixture(nova_fixtures.CGroupsFixture())
7879
self.useFixture(fixtures.MockPatch(
7980
'nova.virt.libvirt.LibvirtDriver._get_local_gb_info',
8081
return_value={'total': 128,

nova/tests/functional/regressions/test_bug_1595962.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def setUp(self):
4747
'nova.virt.libvirt.guest.libvirt',
4848
fakelibvirt))
4949
self.useFixture(nova_fixtures.LibvirtFixture())
50+
self.useFixture(nova_fixtures.CGroupsFixture())
5051

5152
self.admin_api = api_fixture.admin_api
5253
self.api = api_fixture.api

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,7 @@ def setUp(self):
741741
imagebackend.Image._get_driver_format)
742742

743743
self.libvirt = self.useFixture(nova_fixtures.LibvirtFixture())
744+
self.cgroups = self.useFixture(nova_fixtures.CGroupsFixture())
744745

745746
# ensure tests perform the same on all host architectures; this is
746747
# already done by the fakelibvirt fixture but we want to change the
@@ -2956,9 +2957,7 @@ def test_get_live_migrate_numa_info_empty(self, _):
29562957
'fake-instance-numa-topology',
29572958
'fake-flavor', 'fake-image-meta').obj_to_primitive())
29582959

2959-
@mock.patch.object(
2960-
host.Host, "is_cpu_control_policy_capable", return_value=True)
2961-
def test_get_guest_config_numa_host_instance_fits(self, is_able):
2960+
def test_get_guest_config_numa_host_instance_fits(self):
29622961
self.flags(cpu_shared_set=None, cpu_dedicated_set=None,
29632962
group='compute')
29642963
instance_ref = objects.Instance(**self.test_instance)
@@ -2995,9 +2994,7 @@ def test_get_guest_config_numa_host_instance_fits(self, is_able):
29952994

29962995
@mock.patch('nova.privsep.utils.supports_direct_io',
29972996
new=mock.Mock(return_value=True))
2998-
@mock.patch.object(
2999-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3000-
def test_get_guest_config_numa_host_instance_no_fit(self, is_able):
2997+
def test_get_guest_config_numa_host_instance_no_fit(self):
30012998
instance_ref = objects.Instance(**self.test_instance)
30022999
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
30033000
flavor = objects.Flavor(memory_mb=4096, vcpus=4, root_gb=496,
@@ -3388,10 +3385,7 @@ def test_get_guest_memory_backing_config_file_backed_hugepages(self):
33883385
self._test_get_guest_memory_backing_config,
33893386
host_topology, inst_topology, numa_tune)
33903387

3391-
@mock.patch.object(
3392-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3393-
def test_get_guest_config_numa_host_instance_pci_no_numa_info(
3394-
self, is_able):
3388+
def test_get_guest_config_numa_host_instance_pci_no_numa_info(self):
33953389
self.flags(cpu_shared_set='3', cpu_dedicated_set=None,
33963390
group='compute')
33973391

@@ -3440,9 +3434,7 @@ def test_get_guest_config_numa_host_instance_pci_no_numa_info(
34403434

34413435
@mock.patch('nova.privsep.utils.supports_direct_io',
34423436
new=mock.Mock(return_value=True))
3443-
@mock.patch.object(
3444-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3445-
def test_get_guest_config_numa_host_instance_2pci_no_fit(self, is_able):
3437+
def test_get_guest_config_numa_host_instance_2pci_no_fit(self):
34463438
self.flags(cpu_shared_set='3', cpu_dedicated_set=None,
34473439
group='compute')
34483440
instance_ref = objects.Instance(**self.test_instance)
@@ -3550,10 +3542,7 @@ def test_get_guest_config_numa_other_arch_qemu(self):
35503542
exception.NUMATopologyUnsupported,
35513543
None)
35523544

3553-
@mock.patch.object(
3554-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3555-
def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(
3556-
self, is_able):
3545+
def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(self):
35573546
self.flags(cpu_shared_set='2-3', cpu_dedicated_set=None,
35583547
group='compute')
35593548

@@ -3591,9 +3580,7 @@ def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(
35913580
self.assertEqual(0, len(cfg.cputune.vcpupin))
35923581
self.assertIsNone(cfg.cpu.numa)
35933582

3594-
@mock.patch.object(
3595-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3596-
def test_get_guest_config_non_numa_host_instance_topo(self, is_able):
3583+
def test_get_guest_config_non_numa_host_instance_topo(self):
35973584
instance_topology = objects.InstanceNUMATopology(cells=[
35983585
objects.InstanceNUMACell(
35993586
id=0, cpuset=set([0]), pcpuset=set(), memory=1024),
@@ -3640,9 +3627,7 @@ def test_get_guest_config_non_numa_host_instance_topo(self, is_able):
36403627
self.assertEqual(instance_cell.memory * units.Ki,
36413628
numa_cfg_cell.memory)
36423629

3643-
@mock.patch.object(
3644-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3645-
def test_get_guest_config_numa_host_instance_topo(self, is_able):
3630+
def test_get_guest_config_numa_host_instance_topo(self):
36463631
self.flags(cpu_shared_set='0-5', cpu_dedicated_set=None,
36473632
group='compute')
36483633

@@ -7035,9 +7020,7 @@ def test_get_guest_config_with_rng_dev_not_present(self, mock_path):
70357020
[],
70367021
image_meta, disk_info)
70377022

7038-
@mock.patch.object(
7039-
host.Host, "is_cpu_control_policy_capable", return_value=True)
7040-
def test_guest_cpu_shares_with_multi_vcpu(self, is_able):
7023+
def test_guest_cpu_shares_with_multi_vcpu(self):
70417024
self.flags(virt_type='kvm', group='libvirt')
70427025

70437026
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
@@ -7055,9 +7038,7 @@ def test_guest_cpu_shares_with_multi_vcpu(self, is_able):
70557038

70567039
self.assertEqual(4096, cfg.cputune.shares)
70577040

7058-
@mock.patch.object(
7059-
host.Host, "is_cpu_control_policy_capable", return_value=True)
7060-
def test_get_guest_config_with_cpu_quota(self, is_able):
7041+
def test_get_guest_config_with_cpu_quota(self):
70617042
self.flags(virt_type='kvm', group='libvirt')
70627043

70637044
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
@@ -7393,9 +7374,7 @@ def test_get_guest_config_disk_cachemodes_network(
73937374
self.flags(images_type='rbd', group='libvirt')
73947375
self._test_get_guest_config_disk_cachemodes('rbd')
73957376

7396-
@mock.patch.object(
7397-
host.Host, "is_cpu_control_policy_capable", return_value=True)
7398-
def test_get_guest_config_with_bogus_cpu_quota(self, is_able):
7377+
def test_get_guest_config_with_bogus_cpu_quota(self):
73997378
self.flags(virt_type='kvm', group='libvirt')
74007379

74017380
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
@@ -7413,9 +7392,10 @@ def test_get_guest_config_with_bogus_cpu_quota(self, is_able):
74137392
drvr._get_guest_config,
74147393
instance_ref, [], image_meta, disk_info)
74157394

7416-
@mock.patch.object(
7417-
host.Host, "is_cpu_control_policy_capable", return_value=False)
7418-
def test_get_update_guest_cputune(self, is_able):
7395+
def test_get_update_guest_cputune(self):
7396+
# No CPU controller on the host
7397+
self.cgroups.version = 0
7398+
74197399
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
74207400
instance_ref = objects.Instance(**self.test_instance)
74217401
instance_ref.flavor.extra_specs = {'quota:cpu_shares': '10000',
@@ -21662,6 +21642,7 @@ def setUp(self):
2166221642
self.flags(sysinfo_serial="none", group="libvirt")
2166321643
self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
2166421644
self.useFixture(nova_fixtures.LibvirtFixture())
21645+
self.useFixture(nova_fixtures.CGroupsFixture())
2166521646
os_vif.initialize()
2166621647

2166721648
self.drvr = libvirt_driver.LibvirtDriver(

nova/tests/unit/virt/libvirt/test_host.py

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,25 +1556,59 @@ def test_compare_cpu(self, mock_compareCPU):
15561556
self.host.compare_cpu("cpuxml")
15571557
mock_compareCPU.assert_called_once_with("cpuxml", 0)
15581558

1559-
def test_is_cpu_control_policy_capable_ok(self):
1559+
def test_is_cpu_control_policy_capable_via_neither(self):
1560+
self.useFixture(nova_fixtures.CGroupsFixture(version=0))
1561+
self.assertFalse(self.host.is_cpu_control_policy_capable())
1562+
1563+
def test_is_cpu_control_policy_capable_via_cgroupsv1(self):
1564+
self.useFixture(nova_fixtures.CGroupsFixture(version=1))
1565+
self.assertTrue(self.host.is_cpu_control_policy_capable())
1566+
1567+
def test_is_cpu_control_policy_capable_via_cgroupsv2(self):
1568+
self.useFixture(nova_fixtures.CGroupsFixture(version=2))
1569+
self.assertTrue(self.host.is_cpu_control_policy_capable())
1570+
1571+
def test_has_cgroupsv1_cpu_controller_ok(self):
15601572
m = mock.mock_open(
1561-
read_data="""cg /cgroup/cpu,cpuacct cg opt1,cpu,opt3 0 0
1562-
cg /cgroup/memory cg opt1,opt2 0 0
1563-
""")
1564-
with mock.patch('builtins.open', m, create=True):
1565-
self.assertTrue(self.host.is_cpu_control_policy_capable())
1573+
read_data=(
1574+
"cg /cgroup/cpu,cpuacct cg opt1,cpu,opt3 0 0"
1575+
"cg /cgroup/memory cg opt1,opt2 0 0"
1576+
)
1577+
)
1578+
with mock.patch("builtins.open", m, create=True):
1579+
self.assertTrue(self.host._has_cgroupsv1_cpu_controller())
15661580

1567-
def test_is_cpu_control_policy_capable_ko(self):
1581+
def test_has_cgroupsv1_cpu_controller_ko(self):
15681582
m = mock.mock_open(
1569-
read_data="""cg /cgroup/cpu,cpuacct cg opt1,opt2,opt3 0 0
1570-
cg /cgroup/memory cg opt1,opt2 0 0
1571-
""")
1572-
with mock.patch('builtins.open', m, create=True):
1573-
self.assertFalse(self.host.is_cpu_control_policy_capable())
1583+
read_data=(
1584+
"cg /cgroup/cpu,cpuacct cg opt1,opt2,opt3 0 0"
1585+
"cg /cgroup/memory cg opt1,opt2 0 0"
1586+
)
1587+
)
1588+
with mock.patch("builtins.open", m, create=True):
1589+
self.assertFalse(self.host._has_cgroupsv1_cpu_controller())
15741590

1575-
@mock.patch('builtins.open', side_effect=IOError)
1576-
def test_is_cpu_control_policy_capable_ioerror(self, mock_open):
1577-
self.assertFalse(self.host.is_cpu_control_policy_capable())
1591+
@mock.patch("builtins.open", side_effect=IOError)
1592+
def test_has_cgroupsv1_cpu_controller_ioerror(self, _):
1593+
self.assertFalse(self.host._has_cgroupsv1_cpu_controller())
1594+
1595+
def test_has_cgroupsv2_cpu_controller_ok(self):
1596+
m = mock.mock_open(
1597+
read_data="cpuset cpu io memory hugetlb pids rdma misc"
1598+
)
1599+
with mock.patch("builtins.open", m, create=True):
1600+
self.assertTrue(self.host._has_cgroupsv2_cpu_controller())
1601+
1602+
def test_has_cgroupsv2_cpu_controller_ko(self):
1603+
m = mock.mock_open(
1604+
read_data="memory pids"
1605+
)
1606+
with mock.patch("builtins.open", m, create=True):
1607+
self.assertFalse(self.host._has_cgroupsv2_cpu_controller())
1608+
1609+
@mock.patch("builtins.open", side_effect=IOError)
1610+
def test_has_cgroupsv2_cpu_controller_ioerror(self, _):
1611+
self.assertFalse(self.host._has_cgroupsv2_cpu_controller())
15781612

15791613
def test_get_canonical_machine_type(self):
15801614
# this test relies on configuration from the FakeLibvirtFixture

nova/tests/unit/virt/test_virt_drivers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,7 @@ def setUp(self):
832832
# This is needed for the live migration tests which spawn off the
833833
# operation for monitoring.
834834
self.useFixture(nova_fixtures.SpawnIsSynchronousFixture())
835+
self.useFixture(nova_fixtures.CGroupsFixture())
835836
# When destroying an instance, os-vif will try to execute some commands
836837
# which hang tests so let's just stub out the unplug call to os-vif
837838
# since we don't care about it.

nova/virt/libvirt/host.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1548,15 +1548,44 @@ def is_cpu_control_policy_capable(self):
15481548
CONFIG_CGROUP_SCHED may be disabled in some kernel configs to
15491549
improve scheduler latency.
15501550
"""
1551+
return self._has_cgroupsv1_cpu_controller() or \
1552+
self._has_cgroupsv2_cpu_controller()
1553+
1554+
def _has_cgroupsv1_cpu_controller(self):
1555+
LOG.debug(f"Searching host: '{self.get_hostname()}' "
1556+
"for CPU controller through CGroups V1...")
15511557
try:
15521558
with open("/proc/self/mounts", "r") as fd:
15531559
for line in fd.readlines():
15541560
# mount options and split options
15551561
bits = line.split()[3].split(",")
15561562
if "cpu" in bits:
1563+
LOG.debug("CPU controller found on host.")
1564+
return True
1565+
LOG.debug("CPU controller missing on host.")
1566+
return False
1567+
except IOError as ex:
1568+
LOG.debug(f"Search failed due to: '{ex}'. "
1569+
"Maybe the host is not running under CGroups V1. "
1570+
"Deemed host to be missing controller by this approach.")
1571+
return False
1572+
1573+
def _has_cgroupsv2_cpu_controller(self):
1574+
LOG.debug(f"Searching host: '{self.get_hostname()}' "
1575+
"for CPU controller through CGroups V2...")
1576+
try:
1577+
with open("/sys/fs/cgroup/cgroup.controllers", "r") as fd:
1578+
for line in fd.readlines():
1579+
bits = line.split()
1580+
if "cpu" in bits:
1581+
LOG.debug("CPU controller found on host.")
15571582
return True
1583+
LOG.debug("CPU controller missing on host.")
15581584
return False
1559-
except IOError:
1585+
except IOError as ex:
1586+
LOG.debug(f"Search failed due to: '{ex}'. "
1587+
"Maybe the host is not running under CGroups V2. "
1588+
"Deemed host to be missing controller by this approach.")
15601589
return False
15611590

15621591
def get_canonical_machine_type(self, arch, machine) -> str:

0 commit comments

Comments
 (0)