Skip to content

Commit eb3fe4d

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. Partial-Bug: #2008102 Change-Id: I99b57c27c8a4425389bec2b7f05af660bab85610 (cherry picked from commit 973ff4f)
1 parent acb5116 commit eb3fe4d

File tree

9 files changed

+170
-51
lines changed

9 files changed

+170
-51
lines changed

nova/tests/fixtures/nova.py

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

13201320

1321+
class CGroupsFixture(fixtures.Fixture):
1322+
"""Mocks checks made for available subsystems on the host's control group.
1323+
1324+
The fixture mocks all calls made on the host to verify the capabilities
1325+
provided by its kernel. Through this, one can simulate the underlying
1326+
system hosts work on top of and have tests react to expected outcomes from
1327+
such.
1328+
1329+
Use sample:
1330+
>>> cgroups = self.useFixture(CGroupsFixture())
1331+
>>> cgroups = self.useFixture(CGroupsFixture(version=2))
1332+
>>> cgroups = self.useFixture(CGroupsFixture())
1333+
... cgroups.version = 2
1334+
1335+
:attr version: Arranges mocks to simulate the host interact with nova
1336+
following the given version of cgroups.
1337+
Available values are:
1338+
- 0: All checks related to cgroups will return False.
1339+
- 1: Checks related to cgroups v1 will return True.
1340+
- 2: Checks related to cgroups v2 will return True.
1341+
Defaults to 1.
1342+
"""
1343+
1344+
def __init__(self, version=1):
1345+
self._cpuv1 = None
1346+
self._cpuv2 = None
1347+
1348+
self._version = version
1349+
1350+
@property
1351+
def version(self):
1352+
return self._version
1353+
1354+
@version.setter
1355+
def version(self, value):
1356+
self._version = value
1357+
self._update_mocks()
1358+
1359+
def setUp(self):
1360+
super().setUp()
1361+
self._cpuv1 = self.useFixture(fixtures.MockPatch(
1362+
'nova.virt.libvirt.host.Host._has_cgroupsv1_cpu_controller')).mock
1363+
self._cpuv2 = self.useFixture(fixtures.MockPatch(
1364+
'nova.virt.libvirt.host.Host._has_cgroupsv2_cpu_controller')).mock
1365+
self._update_mocks()
1366+
1367+
def _update_mocks(self):
1368+
if not self._cpuv1:
1369+
return
1370+
1371+
if not self._cpuv2:
1372+
return
1373+
1374+
if self.version == 0:
1375+
self._cpuv1.return_value = False
1376+
self._cpuv2.return_value = False
1377+
return
1378+
1379+
if self.version == 1:
1380+
self._cpuv1.return_value = True
1381+
self._cpuv2.return_value = False
1382+
return
1383+
1384+
if self.version == 2:
1385+
self._cpuv1.return_value = False
1386+
self._cpuv2.return_value = True
1387+
return
1388+
1389+
raise ValueError(f"Unknown cgroups version: '{self.version}'.")
1390+
1391+
13211392
class NoopQuotaDriverFixture(fixtures.Fixture):
13221393
"""A fixture to run tests using the NoopQuotaDriver.
13231394

nova/tests/functional/libvirt/base.py

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

4545
self.useFixture(nova_fixtures.LibvirtImageBackendFixture())
46+
self.useFixture(nova_fixtures.CGroupsFixture())
4647
self.libvirt = self.useFixture(nova_fixtures.LibvirtFixture())
4748
self.useFixture(nova_fixtures.OSBrickFixture())
4849

nova/tests/functional/libvirt/test_evacuate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ def setUp(self):
429429
self.useFixture(nova_fixtures.NeutronFixture(self))
430430
self.useFixture(nova_fixtures.GlanceFixture(self))
431431
self.useFixture(func_fixtures.PlacementFixture())
432+
self.useFixture(nova_fixtures.CGroupsFixture())
432433
fake_network.set_stub_network_methods(self)
433434

434435
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
@@ -77,6 +77,7 @@ def setUp(self):
7777
'nova.privsep.libvirt.get_pmem_namespaces',
7878
return_value=self.fake_pmem_namespaces))
7979
self.useFixture(nova_fixtures.LibvirtImageBackendFixture())
80+
self.useFixture(nova_fixtures.CGroupsFixture())
8081
self.useFixture(fixtures.MockPatch(
8182
'nova.virt.libvirt.LibvirtDriver._get_local_gb_info',
8283
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: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,7 @@ def setUp(self):
740740
imagebackend.Image._get_driver_format)
741741

742742
self.libvirt = self.useFixture(nova_fixtures.LibvirtFixture())
743+
self.cgroups = self.useFixture(nova_fixtures.CGroupsFixture())
743744

744745
# ensure tests perform the same on all host architectures; this is
745746
# already done by the fakelibvirt fixture but we want to change the
@@ -3093,9 +3094,7 @@ def test_get_live_migrate_numa_info_empty(self, _):
30933094
'fake-flavor', 'fake-image-meta').obj_to_primitive())
30943095

30953096
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3096-
@mock.patch.object(
3097-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3098-
def test_get_guest_config_numa_host_instance_fits(self, is_able):
3097+
def test_get_guest_config_numa_host_instance_fits(self):
30993098
self.flags(cpu_shared_set=None, cpu_dedicated_set=None,
31003099
group='compute')
31013100
instance_ref = objects.Instance(**self.test_instance)
@@ -3133,9 +3132,7 @@ def test_get_guest_config_numa_host_instance_fits(self, is_able):
31333132
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
31343133
@mock.patch('nova.privsep.utils.supports_direct_io',
31353134
new=mock.Mock(return_value=True))
3136-
@mock.patch.object(
3137-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3138-
def test_get_guest_config_numa_host_instance_no_fit(self, is_able):
3135+
def test_get_guest_config_numa_host_instance_no_fit(self):
31393136
instance_ref = objects.Instance(**self.test_instance)
31403137
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
31413138
flavor = objects.Flavor(memory_mb=4096, vcpus=4, root_gb=496,
@@ -3563,10 +3560,7 @@ def test_get_guest_memory_backing_config_file_backed_hugepages(self):
35633560
host_topology, inst_topology, numa_tune)
35643561

35653562
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3566-
@mock.patch.object(
3567-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3568-
def test_get_guest_config_numa_host_instance_pci_no_numa_info(
3569-
self, is_able):
3563+
def test_get_guest_config_numa_host_instance_pci_no_numa_info(self):
35703564
self.flags(cpu_shared_set='3', cpu_dedicated_set=None,
35713565
group='compute')
35723566

@@ -3620,10 +3614,7 @@ def test_get_guest_config_numa_host_instance_pci_no_numa_info(
36203614
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
36213615
@mock.patch('nova.privsep.utils.supports_direct_io',
36223616
new=mock.Mock(return_value=True))
3623-
@mock.patch.object(
3624-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3625-
def test_get_guest_config_numa_host_instance_2pci_no_fit(
3626-
self, is_able):
3617+
def test_get_guest_config_numa_host_instance_2pci_no_fit(self):
36273618
self.flags(cpu_shared_set='3', cpu_dedicated_set=None,
36283619
group='compute')
36293620
instance_ref = objects.Instance(**self.test_instance)
@@ -3740,10 +3731,7 @@ def test_get_guest_config_numa_other_arch_qemu(self):
37403731
None)
37413732

37423733
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3743-
@mock.patch.object(
3744-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3745-
def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(
3746-
self, is_able):
3734+
def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(self):
37473735
self.flags(cpu_shared_set='2-3', cpu_dedicated_set=None,
37483736
group='compute')
37493737

@@ -3782,10 +3770,7 @@ def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(
37823770
self.assertIsNone(cfg.cpu.numa)
37833771

37843772
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3785-
@mock.patch.object(
3786-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3787-
def test_get_guest_config_non_numa_host_instance_topo(
3788-
self, is_able):
3773+
def test_get_guest_config_non_numa_host_instance_topo(self):
37893774
instance_topology = objects.InstanceNUMATopology(cells=[
37903775
objects.InstanceNUMACell(
37913776
id=0, cpuset=set([0]), pcpuset=set(), memory=1024),
@@ -3833,10 +3818,7 @@ def test_get_guest_config_non_numa_host_instance_topo(
38333818
numa_cfg_cell.memory)
38343819

38353820
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3836-
@mock.patch.object(
3837-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3838-
def test_get_guest_config_numa_host_instance_topo(
3839-
self, is_able):
3821+
def test_get_guest_config_numa_host_instance_topo(self):
38403822
self.flags(cpu_shared_set='0-5', cpu_dedicated_set=None,
38413823
group='compute')
38423824

@@ -7310,9 +7292,7 @@ def test_get_guest_config_with_rng_dev_not_present(self, mock_path):
73107292
[],
73117293
image_meta, disk_info)
73127294

7313-
@mock.patch.object(
7314-
host.Host, "is_cpu_control_policy_capable", return_value=True)
7315-
def test_get_guest_config_with_cpu_quota(self, is_able):
7295+
def test_get_guest_config_with_cpu_quota(self):
73167296
self.flags(virt_type='kvm', group='libvirt')
73177297

73187298
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
@@ -7648,9 +7628,7 @@ def test_get_guest_config_disk_cachemodes_network(
76487628
self.flags(images_type='rbd', group='libvirt')
76497629
self._test_get_guest_config_disk_cachemodes('rbd')
76507630

7651-
@mock.patch.object(
7652-
host.Host, "is_cpu_control_policy_capable", return_value=True)
7653-
def test_get_guest_config_with_bogus_cpu_quota(self, is_able):
7631+
def test_get_guest_config_with_bogus_cpu_quota(self):
76547632
self.flags(virt_type='kvm', group='libvirt')
76557633

76567634
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
@@ -7668,9 +7646,10 @@ def test_get_guest_config_with_bogus_cpu_quota(self, is_able):
76687646
drvr._get_guest_config,
76697647
instance_ref, [], image_meta, disk_info)
76707648

7671-
@mock.patch.object(
7672-
host.Host, "is_cpu_control_policy_capable", return_value=False)
7673-
def test_get_update_guest_cputune(self, is_able):
7649+
def test_get_update_guest_cputune(self):
7650+
# No CPU controller on the host
7651+
self.cgroups.version = 0
7652+
76747653
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
76757654
instance_ref = objects.Instance(**self.test_instance)
76767655
instance_ref.flavor.extra_specs = {'quota:cpu_shares': '10000',
@@ -22348,6 +22327,7 @@ def setUp(self):
2234822327
self.flags(sysinfo_serial="none", group="libvirt")
2234922328
self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
2235022329
self.useFixture(nova_fixtures.LibvirtFixture())
22330+
self.useFixture(nova_fixtures.CGroupsFixture())
2235122331
os_vif.initialize()
2235222332

2235322333
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
@@ -1619,25 +1619,59 @@ def test_compare_cpu(self, mock_compareCPU):
16191619
self.host.compare_cpu("cpuxml")
16201620
mock_compareCPU.assert_called_once_with("cpuxml", 0)
16211621

1622-
def test_is_cpu_control_policy_capable_ok(self):
1622+
def test_is_cpu_control_policy_capable_via_neither(self):
1623+
self.useFixture(nova_fixtures.CGroupsFixture(version=0))
1624+
self.assertFalse(self.host.is_cpu_control_policy_capable())
1625+
1626+
def test_is_cpu_control_policy_capable_via_cgroupsv1(self):
1627+
self.useFixture(nova_fixtures.CGroupsFixture(version=1))
1628+
self.assertTrue(self.host.is_cpu_control_policy_capable())
1629+
1630+
def test_is_cpu_control_policy_capable_via_cgroupsv2(self):
1631+
self.useFixture(nova_fixtures.CGroupsFixture(version=2))
1632+
self.assertTrue(self.host.is_cpu_control_policy_capable())
1633+
1634+
def test_has_cgroupsv1_cpu_controller_ok(self):
16231635
m = mock.mock_open(
1624-
read_data="""cg /cgroup/cpu,cpuacct cg opt1,cpu,opt3 0 0
1625-
cg /cgroup/memory cg opt1,opt2 0 0
1626-
""")
1627-
with mock.patch('builtins.open', m, create=True):
1628-
self.assertTrue(self.host.is_cpu_control_policy_capable())
1636+
read_data=(
1637+
"cg /cgroup/cpu,cpuacct cg opt1,cpu,opt3 0 0"
1638+
"cg /cgroup/memory cg opt1,opt2 0 0"
1639+
)
1640+
)
1641+
with mock.patch("builtins.open", m, create=True):
1642+
self.assertTrue(self.host._has_cgroupsv1_cpu_controller())
16291643

1630-
def test_is_cpu_control_policy_capable_ko(self):
1644+
def test_has_cgroupsv1_cpu_controller_ko(self):
16311645
m = mock.mock_open(
1632-
read_data="""cg /cgroup/cpu,cpuacct cg opt1,opt2,opt3 0 0
1633-
cg /cgroup/memory cg opt1,opt2 0 0
1634-
""")
1635-
with mock.patch('builtins.open', m, create=True):
1636-
self.assertFalse(self.host.is_cpu_control_policy_capable())
1646+
read_data=(
1647+
"cg /cgroup/cpu,cpuacct cg opt1,opt2,opt3 0 0"
1648+
"cg /cgroup/memory cg opt1,opt2 0 0"
1649+
)
1650+
)
1651+
with mock.patch("builtins.open", m, create=True):
1652+
self.assertFalse(self.host._has_cgroupsv1_cpu_controller())
16371653

1638-
@mock.patch('builtins.open', side_effect=IOError)
1639-
def test_is_cpu_control_policy_capable_ioerror(self, mock_open):
1640-
self.assertFalse(self.host.is_cpu_control_policy_capable())
1654+
@mock.patch("builtins.open", side_effect=IOError)
1655+
def test_has_cgroupsv1_cpu_controller_ioerror(self, _):
1656+
self.assertFalse(self.host._has_cgroupsv1_cpu_controller())
1657+
1658+
def test_has_cgroupsv2_cpu_controller_ok(self):
1659+
m = mock.mock_open(
1660+
read_data="cpuset cpu io memory hugetlb pids rdma misc"
1661+
)
1662+
with mock.patch("builtins.open", m, create=True):
1663+
self.assertTrue(self.host._has_cgroupsv2_cpu_controller())
1664+
1665+
def test_has_cgroupsv2_cpu_controller_ko(self):
1666+
m = mock.mock_open(
1667+
read_data="memory pids"
1668+
)
1669+
with mock.patch("builtins.open", m, create=True):
1670+
self.assertFalse(self.host._has_cgroupsv2_cpu_controller())
1671+
1672+
@mock.patch("builtins.open", side_effect=IOError)
1673+
def test_has_cgroupsv2_cpu_controller_ioerror(self, _):
1674+
self.assertFalse(self.host._has_cgroupsv2_cpu_controller())
16411675

16421676
def test_get_canonical_machine_type(self):
16431677
# 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
@@ -1643,15 +1643,44 @@ def is_cpu_control_policy_capable(self):
16431643
CONFIG_CGROUP_SCHED may be disabled in some kernel configs to
16441644
improve scheduler latency.
16451645
"""
1646+
return self._has_cgroupsv1_cpu_controller() or \
1647+
self._has_cgroupsv2_cpu_controller()
1648+
1649+
def _has_cgroupsv1_cpu_controller(self):
1650+
LOG.debug(f"Searching host: '{self.get_hostname()}' "
1651+
"for CPU controller through CGroups V1...")
16461652
try:
16471653
with open("/proc/self/mounts", "r") as fd:
16481654
for line in fd.readlines():
16491655
# mount options and split options
16501656
bits = line.split()[3].split(",")
16511657
if "cpu" in bits:
1658+
LOG.debug("CPU controller found on host.")
1659+
return True
1660+
LOG.debug("CPU controller missing on host.")
1661+
return False
1662+
except IOError as ex:
1663+
LOG.debug(f"Search failed due to: '{ex}'. "
1664+
"Maybe the host is not running under CGroups V1. "
1665+
"Deemed host to be missing controller by this approach.")
1666+
return False
1667+
1668+
def _has_cgroupsv2_cpu_controller(self):
1669+
LOG.debug(f"Searching host: '{self.get_hostname()}' "
1670+
"for CPU controller through CGroups V2...")
1671+
try:
1672+
with open("/sys/fs/cgroup/cgroup.controllers", "r") as fd:
1673+
for line in fd.readlines():
1674+
bits = line.split()
1675+
if "cpu" in bits:
1676+
LOG.debug("CPU controller found on host.")
16521677
return True
1678+
LOG.debug("CPU controller missing on host.")
16531679
return False
1654-
except IOError:
1680+
except IOError as ex:
1681+
LOG.debug(f"Search failed due to: '{ex}'. "
1682+
"Maybe the host is not running under CGroups V2. "
1683+
"Deemed host to be missing controller by this approach.")
16551684
return False
16561685

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

0 commit comments

Comments
 (0)