Skip to content

Commit dee89dd

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Have host look for CPU controller of cgroupsv2 location." into stable/yoga
2 parents 0b0da89 + aa295b4 commit dee89dd

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',
@@ -21715,6 +21695,7 @@ def setUp(self):
2171521695
self.flags(sysinfo_serial="none", group="libvirt")
2171621696
self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
2171721697
self.useFixture(nova_fixtures.LibvirtFixture())
21698+
self.useFixture(nova_fixtures.CGroupsFixture())
2171821699
os_vif.initialize()
2171921700

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