Skip to content

Commit f361ce2

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Have host look for CPU controller of cgroupsv2 location." into stable/2023.1
2 parents 4be4b88 + eb3fe4d commit f361ce2

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',
@@ -22401,6 +22380,7 @@ def setUp(self):
2240122380
self.flags(sysinfo_serial="none", group="libvirt")
2240222381
self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
2240322382
self.useFixture(nova_fixtures.LibvirtFixture())
22383+
self.useFixture(nova_fixtures.CGroupsFixture())
2240422384
os_vif.initialize()
2240522385

2240622386
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)