Skip to content

Commit 0ff35c5

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Have host look for CPU controller of cgroupsv2 location." into stable/zed
2 parents e5eb65e + 9e86be5 commit 0ff35c5

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',
@@ -22163,6 +22142,7 @@ def setUp(self):
2216322142
self.flags(sysinfo_serial="none", group="libvirt")
2216422143
self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
2216522144
self.useFixture(nova_fixtures.LibvirtFixture())
22145+
self.useFixture(nova_fixtures.CGroupsFixture())
2216622146
os_vif.initialize()
2216722147

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