Skip to content

Commit 9e86be5

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) (cherry picked from commit eb3fe4d)
1 parent f100952 commit 9e86be5

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
@@ -1316,6 +1316,77 @@ def setUp(self):
13161316
nova.privsep.sys_admin_pctxt, 'client_mode', False))
13171317

13181318

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

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: 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
@@ -3047,9 +3048,7 @@ def test_get_live_migrate_numa_info_empty(self, _):
30473048
'fake-flavor', 'fake-image-meta').obj_to_primitive())
30483049

30493050
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3050-
@mock.patch.object(
3051-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3052-
def test_get_guest_config_numa_host_instance_fits(self, is_able):
3051+
def test_get_guest_config_numa_host_instance_fits(self):
30533052
self.flags(cpu_shared_set=None, cpu_dedicated_set=None,
30543053
group='compute')
30553054
instance_ref = objects.Instance(**self.test_instance)
@@ -3087,9 +3086,7 @@ def test_get_guest_config_numa_host_instance_fits(self, is_able):
30873086
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
30883087
@mock.patch('nova.privsep.utils.supports_direct_io',
30893088
new=mock.Mock(return_value=True))
3090-
@mock.patch.object(
3091-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3092-
def test_get_guest_config_numa_host_instance_no_fit(self, is_able):
3089+
def test_get_guest_config_numa_host_instance_no_fit(self):
30933090
instance_ref = objects.Instance(**self.test_instance)
30943091
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
30953092
flavor = objects.Flavor(memory_mb=4096, vcpus=4, root_gb=496,
@@ -3516,10 +3513,7 @@ def test_get_guest_memory_backing_config_file_backed_hugepages(self):
35163513
host_topology, inst_topology, numa_tune)
35173514

35183515
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3519-
@mock.patch.object(
3520-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3521-
def test_get_guest_config_numa_host_instance_pci_no_numa_info(
3522-
self, is_able):
3516+
def test_get_guest_config_numa_host_instance_pci_no_numa_info(self):
35233517
self.flags(cpu_shared_set='3', cpu_dedicated_set=None,
35243518
group='compute')
35253519

@@ -3573,10 +3567,7 @@ def test_get_guest_config_numa_host_instance_pci_no_numa_info(
35733567
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
35743568
@mock.patch('nova.privsep.utils.supports_direct_io',
35753569
new=mock.Mock(return_value=True))
3576-
@mock.patch.object(
3577-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3578-
def test_get_guest_config_numa_host_instance_2pci_no_fit(
3579-
self, is_able):
3570+
def test_get_guest_config_numa_host_instance_2pci_no_fit(self):
35803571
self.flags(cpu_shared_set='3', cpu_dedicated_set=None,
35813572
group='compute')
35823573
instance_ref = objects.Instance(**self.test_instance)
@@ -3693,10 +3684,7 @@ def test_get_guest_config_numa_other_arch_qemu(self):
36933684
None)
36943685

36953686
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3696-
@mock.patch.object(
3697-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3698-
def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(
3699-
self, is_able):
3687+
def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(self):
37003688
self.flags(cpu_shared_set='2-3', cpu_dedicated_set=None,
37013689
group='compute')
37023690

@@ -3735,10 +3723,7 @@ def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(
37353723
self.assertIsNone(cfg.cpu.numa)
37363724

37373725
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3738-
@mock.patch.object(
3739-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3740-
def test_get_guest_config_non_numa_host_instance_topo(
3741-
self, is_able):
3726+
def test_get_guest_config_non_numa_host_instance_topo(self):
37423727
instance_topology = objects.InstanceNUMATopology(cells=[
37433728
objects.InstanceNUMACell(
37443729
id=0, cpuset=set([0]), pcpuset=set(), memory=1024),
@@ -3786,10 +3771,7 @@ def test_get_guest_config_non_numa_host_instance_topo(
37863771
numa_cfg_cell.memory)
37873772

37883773
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
3789-
@mock.patch.object(
3790-
host.Host, "is_cpu_control_policy_capable", return_value=True)
3791-
def test_get_guest_config_numa_host_instance_topo(
3792-
self, is_able):
3774+
def test_get_guest_config_numa_host_instance_topo(self):
37933775
self.flags(cpu_shared_set='0-5', cpu_dedicated_set=None,
37943776
group='compute')
37953777

@@ -7199,9 +7181,7 @@ def test_get_guest_config_with_rng_dev_not_present(self, mock_path):
71997181
[],
72007182
image_meta, disk_info)
72017183

7202-
@mock.patch.object(
7203-
host.Host, "is_cpu_control_policy_capable", return_value=True)
7204-
def test_get_guest_config_with_cpu_quota(self, is_able):
7184+
def test_get_guest_config_with_cpu_quota(self):
72057185
self.flags(virt_type='kvm', group='libvirt')
72067186

72077187
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
@@ -7537,9 +7517,7 @@ def test_get_guest_config_disk_cachemodes_network(
75377517
self.flags(images_type='rbd', group='libvirt')
75387518
self._test_get_guest_config_disk_cachemodes('rbd')
75397519

7540-
@mock.patch.object(
7541-
host.Host, "is_cpu_control_policy_capable", return_value=True)
7542-
def test_get_guest_config_with_bogus_cpu_quota(self, is_able):
7520+
def test_get_guest_config_with_bogus_cpu_quota(self):
75437521
self.flags(virt_type='kvm', group='libvirt')
75447522

75457523
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
@@ -7557,9 +7535,10 @@ def test_get_guest_config_with_bogus_cpu_quota(self, is_able):
75577535
drvr._get_guest_config,
75587536
instance_ref, [], image_meta, disk_info)
75597537

7560-
@mock.patch.object(
7561-
host.Host, "is_cpu_control_policy_capable", return_value=False)
7562-
def test_get_update_guest_cputune(self, is_able):
7538+
def test_get_update_guest_cputune(self):
7539+
# No CPU controller on the host
7540+
self.cgroups.version = 0
7541+
75637542
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
75647543
instance_ref = objects.Instance(**self.test_instance)
75657544
instance_ref.flavor.extra_specs = {'quota:cpu_shares': '10000',
@@ -22110,6 +22089,7 @@ def setUp(self):
2211022089
self.flags(sysinfo_serial="none", group="libvirt")
2211122090
self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
2211222091
self.useFixture(nova_fixtures.LibvirtFixture())
22092+
self.useFixture(nova_fixtures.CGroupsFixture())
2211322093
os_vif.initialize()
2211422094

2211522095
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
@@ -1613,25 +1613,59 @@ def test_compare_cpu(self, mock_compareCPU):
16131613
self.host.compare_cpu("cpuxml")
16141614
mock_compareCPU.assert_called_once_with("cpuxml", 0)
16151615

1616-
def test_is_cpu_control_policy_capable_ok(self):
1616+
def test_is_cpu_control_policy_capable_via_neither(self):
1617+
self.useFixture(nova_fixtures.CGroupsFixture(version=0))
1618+
self.assertFalse(self.host.is_cpu_control_policy_capable())
1619+
1620+
def test_is_cpu_control_policy_capable_via_cgroupsv1(self):
1621+
self.useFixture(nova_fixtures.CGroupsFixture(version=1))
1622+
self.assertTrue(self.host.is_cpu_control_policy_capable())
1623+
1624+
def test_is_cpu_control_policy_capable_via_cgroupsv2(self):
1625+
self.useFixture(nova_fixtures.CGroupsFixture(version=2))
1626+
self.assertTrue(self.host.is_cpu_control_policy_capable())
1627+
1628+
def test_has_cgroupsv1_cpu_controller_ok(self):
16171629
m = mock.mock_open(
1618-
read_data="""cg /cgroup/cpu,cpuacct cg opt1,cpu,opt3 0 0
1619-
cg /cgroup/memory cg opt1,opt2 0 0
1620-
""")
1621-
with mock.patch('builtins.open', m, create=True):
1622-
self.assertTrue(self.host.is_cpu_control_policy_capable())
1630+
read_data=(
1631+
"cg /cgroup/cpu,cpuacct cg opt1,cpu,opt3 0 0"
1632+
"cg /cgroup/memory cg opt1,opt2 0 0"
1633+
)
1634+
)
1635+
with mock.patch("builtins.open", m, create=True):
1636+
self.assertTrue(self.host._has_cgroupsv1_cpu_controller())
16231637

1624-
def test_is_cpu_control_policy_capable_ko(self):
1638+
def test_has_cgroupsv1_cpu_controller_ko(self):
16251639
m = mock.mock_open(
1626-
read_data="""cg /cgroup/cpu,cpuacct cg opt1,opt2,opt3 0 0
1627-
cg /cgroup/memory cg opt1,opt2 0 0
1628-
""")
1629-
with mock.patch('builtins.open', m, create=True):
1630-
self.assertFalse(self.host.is_cpu_control_policy_capable())
1640+
read_data=(
1641+
"cg /cgroup/cpu,cpuacct cg opt1,opt2,opt3 0 0"
1642+
"cg /cgroup/memory cg opt1,opt2 0 0"
1643+
)
1644+
)
1645+
with mock.patch("builtins.open", m, create=True):
1646+
self.assertFalse(self.host._has_cgroupsv1_cpu_controller())
16311647

1632-
@mock.patch('builtins.open', side_effect=IOError)
1633-
def test_is_cpu_control_policy_capable_ioerror(self, mock_open):
1634-
self.assertFalse(self.host.is_cpu_control_policy_capable())
1648+
@mock.patch("builtins.open", side_effect=IOError)
1649+
def test_has_cgroupsv1_cpu_controller_ioerror(self, _):
1650+
self.assertFalse(self.host._has_cgroupsv1_cpu_controller())
1651+
1652+
def test_has_cgroupsv2_cpu_controller_ok(self):
1653+
m = mock.mock_open(
1654+
read_data="cpuset cpu io memory hugetlb pids rdma misc"
1655+
)
1656+
with mock.patch("builtins.open", m, create=True):
1657+
self.assertTrue(self.host._has_cgroupsv2_cpu_controller())
1658+
1659+
def test_has_cgroupsv2_cpu_controller_ko(self):
1660+
m = mock.mock_open(
1661+
read_data="memory pids"
1662+
)
1663+
with mock.patch("builtins.open", m, create=True):
1664+
self.assertFalse(self.host._has_cgroupsv2_cpu_controller())
1665+
1666+
@mock.patch("builtins.open", side_effect=IOError)
1667+
def test_has_cgroupsv2_cpu_controller_ioerror(self, _):
1668+
self.assertFalse(self.host._has_cgroupsv2_cpu_controller())
16351669

16361670
def test_get_canonical_machine_type(self):
16371671
# 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
@@ -1611,15 +1611,44 @@ def is_cpu_control_policy_capable(self):
16111611
CONFIG_CGROUP_SCHED may be disabled in some kernel configs to
16121612
improve scheduler latency.
16131613
"""
1614+
return self._has_cgroupsv1_cpu_controller() or \
1615+
self._has_cgroupsv2_cpu_controller()
1616+
1617+
def _has_cgroupsv1_cpu_controller(self):
1618+
LOG.debug(f"Searching host: '{self.get_hostname()}' "
1619+
"for CPU controller through CGroups V1...")
16141620
try:
16151621
with open("/proc/self/mounts", "r") as fd:
16161622
for line in fd.readlines():
16171623
# mount options and split options
16181624
bits = line.split()[3].split(",")
16191625
if "cpu" in bits:
1626+
LOG.debug("CPU controller found on host.")
1627+
return True
1628+
LOG.debug("CPU controller missing on host.")
1629+
return False
1630+
except IOError as ex:
1631+
LOG.debug(f"Search failed due to: '{ex}'. "
1632+
"Maybe the host is not running under CGroups V1. "
1633+
"Deemed host to be missing controller by this approach.")
1634+
return False
1635+
1636+
def _has_cgroupsv2_cpu_controller(self):
1637+
LOG.debug(f"Searching host: '{self.get_hostname()}' "
1638+
"for CPU controller through CGroups V2...")
1639+
try:
1640+
with open("/sys/fs/cgroup/cgroup.controllers", "r") as fd:
1641+
for line in fd.readlines():
1642+
bits = line.split()
1643+
if "cpu" in bits:
1644+
LOG.debug("CPU controller found on host.")
16201645
return True
1646+
LOG.debug("CPU controller missing on host.")
16211647
return False
1622-
except IOError:
1648+
except IOError as ex:
1649+
LOG.debug(f"Search failed due to: '{ex}'. "
1650+
"Maybe the host is not running under CGroups V2. "
1651+
"Deemed host to be missing controller by this approach.")
16231652
return False
16241653

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

0 commit comments

Comments
 (0)