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 commit973ff4fc1a) (cherry picked from commiteb3fe4ddc6)
This commit is contained in:
committed by
Amit Uniyal
parent
f1009524f2
commit
9e86be5a53
71
nova/tests/fixtures/nova.py
vendored
71
nova/tests/fixtures/nova.py
vendored
@@ -1316,6 +1316,77 @@ class PrivsepFixture(fixtures.Fixture):
|
||||
nova.privsep.sys_admin_pctxt, 'client_mode', False))
|
||||
|
||||
|
||||
class CGroupsFixture(fixtures.Fixture):
|
||||
"""Mocks checks made for available subsystems on the host's control group.
|
||||
|
||||
The fixture mocks all calls made on the host to verify the capabilities
|
||||
provided by its kernel. Through this, one can simulate the underlying
|
||||
system hosts work on top of and have tests react to expected outcomes from
|
||||
such.
|
||||
|
||||
Use sample:
|
||||
>>> cgroups = self.useFixture(CGroupsFixture())
|
||||
>>> cgroups = self.useFixture(CGroupsFixture(version=2))
|
||||
>>> cgroups = self.useFixture(CGroupsFixture())
|
||||
... cgroups.version = 2
|
||||
|
||||
:attr version: Arranges mocks to simulate the host interact with nova
|
||||
following the given version of cgroups.
|
||||
Available values are:
|
||||
- 0: All checks related to cgroups will return False.
|
||||
- 1: Checks related to cgroups v1 will return True.
|
||||
- 2: Checks related to cgroups v2 will return True.
|
||||
Defaults to 1.
|
||||
"""
|
||||
|
||||
def __init__(self, version=1):
|
||||
self._cpuv1 = None
|
||||
self._cpuv2 = None
|
||||
|
||||
self._version = version
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
@version.setter
|
||||
def version(self, value):
|
||||
self._version = value
|
||||
self._update_mocks()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._cpuv1 = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.libvirt.host.Host._has_cgroupsv1_cpu_controller')).mock
|
||||
self._cpuv2 = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.libvirt.host.Host._has_cgroupsv2_cpu_controller')).mock
|
||||
self._update_mocks()
|
||||
|
||||
def _update_mocks(self):
|
||||
if not self._cpuv1:
|
||||
return
|
||||
|
||||
if not self._cpuv2:
|
||||
return
|
||||
|
||||
if self.version == 0:
|
||||
self._cpuv1.return_value = False
|
||||
self._cpuv2.return_value = False
|
||||
return
|
||||
|
||||
if self.version == 1:
|
||||
self._cpuv1.return_value = True
|
||||
self._cpuv2.return_value = False
|
||||
return
|
||||
|
||||
if self.version == 2:
|
||||
self._cpuv1.return_value = False
|
||||
self._cpuv2.return_value = True
|
||||
return
|
||||
|
||||
raise ValueError(f"Unknown cgroups version: '{self.version}'.")
|
||||
|
||||
|
||||
class NoopQuotaDriverFixture(fixtures.Fixture):
|
||||
"""A fixture to run tests using the NoopQuotaDriver.
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ class ServersTestBase(integrated_helpers._IntegratedTestBase):
|
||||
super(ServersTestBase, self).setUp()
|
||||
|
||||
self.useFixture(nova_fixtures.LibvirtImageBackendFixture())
|
||||
self.useFixture(nova_fixtures.CGroupsFixture())
|
||||
self.libvirt = self.useFixture(nova_fixtures.LibvirtFixture())
|
||||
self.useFixture(nova_fixtures.OSBrickFixture())
|
||||
|
||||
|
||||
@@ -427,6 +427,7 @@ class _LibvirtEvacuateTest(integrated_helpers.InstanceHelperMixin):
|
||||
self.useFixture(nova_fixtures.NeutronFixture(self))
|
||||
self.useFixture(nova_fixtures.GlanceFixture(self))
|
||||
self.useFixture(func_fixtures.PlacementFixture())
|
||||
self.useFixture(nova_fixtures.CGroupsFixture())
|
||||
fake_network.set_stub_network_methods(self)
|
||||
|
||||
api_fixture = self.useFixture(
|
||||
|
||||
@@ -75,6 +75,7 @@ class VPMEMTestBase(integrated_helpers.LibvirtProviderUsageBaseTestCase):
|
||||
'nova.privsep.libvirt.get_pmem_namespaces',
|
||||
return_value=self.fake_pmem_namespaces))
|
||||
self.useFixture(nova_fixtures.LibvirtImageBackendFixture())
|
||||
self.useFixture(nova_fixtures.CGroupsFixture())
|
||||
self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.libvirt.LibvirtDriver._get_local_gb_info',
|
||||
return_value={'total': 128,
|
||||
|
||||
@@ -47,6 +47,7 @@ class TestSerialConsoleLiveMigrate(test.TestCase):
|
||||
'nova.virt.libvirt.guest.libvirt',
|
||||
fakelibvirt))
|
||||
self.useFixture(nova_fixtures.LibvirtFixture())
|
||||
self.useFixture(nova_fixtures.CGroupsFixture())
|
||||
|
||||
self.admin_api = api_fixture.admin_api
|
||||
self.api = api_fixture.api
|
||||
|
||||
@@ -740,6 +740,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
imagebackend.Image._get_driver_format)
|
||||
|
||||
self.libvirt = self.useFixture(nova_fixtures.LibvirtFixture())
|
||||
self.cgroups = self.useFixture(nova_fixtures.CGroupsFixture())
|
||||
|
||||
# ensure tests perform the same on all host architectures; this is
|
||||
# already done by the fakelibvirt fixture but we want to change the
|
||||
@@ -3047,9 +3048,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
'fake-flavor', 'fake-image-meta').obj_to_primitive())
|
||||
|
||||
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=True)
|
||||
def test_get_guest_config_numa_host_instance_fits(self, is_able):
|
||||
def test_get_guest_config_numa_host_instance_fits(self):
|
||||
self.flags(cpu_shared_set=None, cpu_dedicated_set=None,
|
||||
group='compute')
|
||||
instance_ref = objects.Instance(**self.test_instance)
|
||||
@@ -3087,9 +3086,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
|
||||
@mock.patch('nova.privsep.utils.supports_direct_io',
|
||||
new=mock.Mock(return_value=True))
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=True)
|
||||
def test_get_guest_config_numa_host_instance_no_fit(self, is_able):
|
||||
def test_get_guest_config_numa_host_instance_no_fit(self):
|
||||
instance_ref = objects.Instance(**self.test_instance)
|
||||
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
|
||||
flavor = objects.Flavor(memory_mb=4096, vcpus=4, root_gb=496,
|
||||
@@ -3516,10 +3513,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
host_topology, inst_topology, numa_tune)
|
||||
|
||||
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=True)
|
||||
def test_get_guest_config_numa_host_instance_pci_no_numa_info(
|
||||
self, is_able):
|
||||
def test_get_guest_config_numa_host_instance_pci_no_numa_info(self):
|
||||
self.flags(cpu_shared_set='3', cpu_dedicated_set=None,
|
||||
group='compute')
|
||||
|
||||
@@ -3573,10 +3567,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
|
||||
@mock.patch('nova.privsep.utils.supports_direct_io',
|
||||
new=mock.Mock(return_value=True))
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=True)
|
||||
def test_get_guest_config_numa_host_instance_2pci_no_fit(
|
||||
self, is_able):
|
||||
def test_get_guest_config_numa_host_instance_2pci_no_fit(self):
|
||||
self.flags(cpu_shared_set='3', cpu_dedicated_set=None,
|
||||
group='compute')
|
||||
instance_ref = objects.Instance(**self.test_instance)
|
||||
@@ -3693,10 +3684,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
None)
|
||||
|
||||
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=True)
|
||||
def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(
|
||||
self, is_able):
|
||||
def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(self):
|
||||
self.flags(cpu_shared_set='2-3', cpu_dedicated_set=None,
|
||||
group='compute')
|
||||
|
||||
@@ -3735,10 +3723,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
self.assertIsNone(cfg.cpu.numa)
|
||||
|
||||
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=True)
|
||||
def test_get_guest_config_non_numa_host_instance_topo(
|
||||
self, is_able):
|
||||
def test_get_guest_config_non_numa_host_instance_topo(self):
|
||||
instance_topology = objects.InstanceNUMATopology(cells=[
|
||||
objects.InstanceNUMACell(
|
||||
id=0, cpuset=set([0]), pcpuset=set(), memory=1024),
|
||||
@@ -3786,10 +3771,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
numa_cfg_cell.memory)
|
||||
|
||||
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=True)
|
||||
def test_get_guest_config_numa_host_instance_topo(
|
||||
self, is_able):
|
||||
def test_get_guest_config_numa_host_instance_topo(self):
|
||||
self.flags(cpu_shared_set='0-5', cpu_dedicated_set=None,
|
||||
group='compute')
|
||||
|
||||
@@ -7199,9 +7181,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
[],
|
||||
image_meta, disk_info)
|
||||
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=True)
|
||||
def test_get_guest_config_with_cpu_quota(self, is_able):
|
||||
def test_get_guest_config_with_cpu_quota(self):
|
||||
self.flags(virt_type='kvm', group='libvirt')
|
||||
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
@@ -7537,9 +7517,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
self.flags(images_type='rbd', group='libvirt')
|
||||
self._test_get_guest_config_disk_cachemodes('rbd')
|
||||
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=True)
|
||||
def test_get_guest_config_with_bogus_cpu_quota(self, is_able):
|
||||
def test_get_guest_config_with_bogus_cpu_quota(self):
|
||||
self.flags(virt_type='kvm', group='libvirt')
|
||||
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
@@ -7557,9 +7535,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
drvr._get_guest_config,
|
||||
instance_ref, [], image_meta, disk_info)
|
||||
|
||||
@mock.patch.object(
|
||||
host.Host, "is_cpu_control_policy_capable", return_value=False)
|
||||
def test_get_update_guest_cputune(self, is_able):
|
||||
def test_get_update_guest_cputune(self):
|
||||
# No CPU controller on the host
|
||||
self.cgroups.version = 0
|
||||
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
instance_ref = objects.Instance(**self.test_instance)
|
||||
instance_ref.flavor.extra_specs = {'quota:cpu_shares': '10000',
|
||||
@@ -22110,6 +22089,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
|
||||
self.flags(sysinfo_serial="none", group="libvirt")
|
||||
self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
|
||||
self.useFixture(nova_fixtures.LibvirtFixture())
|
||||
self.useFixture(nova_fixtures.CGroupsFixture())
|
||||
os_vif.initialize()
|
||||
|
||||
self.drvr = libvirt_driver.LibvirtDriver(
|
||||
|
||||
@@ -1613,26 +1613,60 @@ Active: 8381604 kB
|
||||
self.host.compare_cpu("cpuxml")
|
||||
mock_compareCPU.assert_called_once_with("cpuxml", 0)
|
||||
|
||||
def test_is_cpu_control_policy_capable_ok(self):
|
||||
m = mock.mock_open(
|
||||
read_data="""cg /cgroup/cpu,cpuacct cg opt1,cpu,opt3 0 0
|
||||
cg /cgroup/memory cg opt1,opt2 0 0
|
||||
""")
|
||||
with mock.patch('builtins.open', m, create=True):
|
||||
self.assertTrue(self.host.is_cpu_control_policy_capable())
|
||||
|
||||
def test_is_cpu_control_policy_capable_ko(self):
|
||||
m = mock.mock_open(
|
||||
read_data="""cg /cgroup/cpu,cpuacct cg opt1,opt2,opt3 0 0
|
||||
cg /cgroup/memory cg opt1,opt2 0 0
|
||||
""")
|
||||
with mock.patch('builtins.open', m, create=True):
|
||||
self.assertFalse(self.host.is_cpu_control_policy_capable())
|
||||
|
||||
@mock.patch('builtins.open', side_effect=IOError)
|
||||
def test_is_cpu_control_policy_capable_ioerror(self, mock_open):
|
||||
def test_is_cpu_control_policy_capable_via_neither(self):
|
||||
self.useFixture(nova_fixtures.CGroupsFixture(version=0))
|
||||
self.assertFalse(self.host.is_cpu_control_policy_capable())
|
||||
|
||||
def test_is_cpu_control_policy_capable_via_cgroupsv1(self):
|
||||
self.useFixture(nova_fixtures.CGroupsFixture(version=1))
|
||||
self.assertTrue(self.host.is_cpu_control_policy_capable())
|
||||
|
||||
def test_is_cpu_control_policy_capable_via_cgroupsv2(self):
|
||||
self.useFixture(nova_fixtures.CGroupsFixture(version=2))
|
||||
self.assertTrue(self.host.is_cpu_control_policy_capable())
|
||||
|
||||
def test_has_cgroupsv1_cpu_controller_ok(self):
|
||||
m = mock.mock_open(
|
||||
read_data=(
|
||||
"cg /cgroup/cpu,cpuacct cg opt1,cpu,opt3 0 0"
|
||||
"cg /cgroup/memory cg opt1,opt2 0 0"
|
||||
)
|
||||
)
|
||||
with mock.patch("builtins.open", m, create=True):
|
||||
self.assertTrue(self.host._has_cgroupsv1_cpu_controller())
|
||||
|
||||
def test_has_cgroupsv1_cpu_controller_ko(self):
|
||||
m = mock.mock_open(
|
||||
read_data=(
|
||||
"cg /cgroup/cpu,cpuacct cg opt1,opt2,opt3 0 0"
|
||||
"cg /cgroup/memory cg opt1,opt2 0 0"
|
||||
)
|
||||
)
|
||||
with mock.patch("builtins.open", m, create=True):
|
||||
self.assertFalse(self.host._has_cgroupsv1_cpu_controller())
|
||||
|
||||
@mock.patch("builtins.open", side_effect=IOError)
|
||||
def test_has_cgroupsv1_cpu_controller_ioerror(self, _):
|
||||
self.assertFalse(self.host._has_cgroupsv1_cpu_controller())
|
||||
|
||||
def test_has_cgroupsv2_cpu_controller_ok(self):
|
||||
m = mock.mock_open(
|
||||
read_data="cpuset cpu io memory hugetlb pids rdma misc"
|
||||
)
|
||||
with mock.patch("builtins.open", m, create=True):
|
||||
self.assertTrue(self.host._has_cgroupsv2_cpu_controller())
|
||||
|
||||
def test_has_cgroupsv2_cpu_controller_ko(self):
|
||||
m = mock.mock_open(
|
||||
read_data="memory pids"
|
||||
)
|
||||
with mock.patch("builtins.open", m, create=True):
|
||||
self.assertFalse(self.host._has_cgroupsv2_cpu_controller())
|
||||
|
||||
@mock.patch("builtins.open", side_effect=IOError)
|
||||
def test_has_cgroupsv2_cpu_controller_ioerror(self, _):
|
||||
self.assertFalse(self.host._has_cgroupsv2_cpu_controller())
|
||||
|
||||
def test_get_canonical_machine_type(self):
|
||||
# this test relies on configuration from the FakeLibvirtFixture
|
||||
|
||||
|
||||
@@ -832,6 +832,7 @@ class LibvirtConnTestCase(_VirtDriverTestCase, test.TestCase):
|
||||
# This is needed for the live migration tests which spawn off the
|
||||
# operation for monitoring.
|
||||
self.useFixture(nova_fixtures.SpawnIsSynchronousFixture())
|
||||
self.useFixture(nova_fixtures.CGroupsFixture())
|
||||
# When destroying an instance, os-vif will try to execute some commands
|
||||
# which hang tests so let's just stub out the unplug call to os-vif
|
||||
# since we don't care about it.
|
||||
|
||||
@@ -1611,15 +1611,44 @@ class Host(object):
|
||||
CONFIG_CGROUP_SCHED may be disabled in some kernel configs to
|
||||
improve scheduler latency.
|
||||
"""
|
||||
return self._has_cgroupsv1_cpu_controller() or \
|
||||
self._has_cgroupsv2_cpu_controller()
|
||||
|
||||
def _has_cgroupsv1_cpu_controller(self):
|
||||
LOG.debug(f"Searching host: '{self.get_hostname()}' "
|
||||
"for CPU controller through CGroups V1...")
|
||||
try:
|
||||
with open("/proc/self/mounts", "r") as fd:
|
||||
for line in fd.readlines():
|
||||
# mount options and split options
|
||||
bits = line.split()[3].split(",")
|
||||
if "cpu" in bits:
|
||||
LOG.debug("CPU controller found on host.")
|
||||
return True
|
||||
LOG.debug("CPU controller missing on host.")
|
||||
return False
|
||||
except IOError:
|
||||
except IOError as ex:
|
||||
LOG.debug(f"Search failed due to: '{ex}'. "
|
||||
"Maybe the host is not running under CGroups V1. "
|
||||
"Deemed host to be missing controller by this approach.")
|
||||
return False
|
||||
|
||||
def _has_cgroupsv2_cpu_controller(self):
|
||||
LOG.debug(f"Searching host: '{self.get_hostname()}' "
|
||||
"for CPU controller through CGroups V2...")
|
||||
try:
|
||||
with open("/sys/fs/cgroup/cgroup.controllers", "r") as fd:
|
||||
for line in fd.readlines():
|
||||
bits = line.split()
|
||||
if "cpu" in bits:
|
||||
LOG.debug("CPU controller found on host.")
|
||||
return True
|
||||
LOG.debug("CPU controller missing on host.")
|
||||
return False
|
||||
except IOError as ex:
|
||||
LOG.debug(f"Search failed due to: '{ex}'. "
|
||||
"Maybe the host is not running under CGroups V2. "
|
||||
"Deemed host to be missing controller by this approach.")
|
||||
return False
|
||||
|
||||
def get_canonical_machine_type(self, arch, machine) -> str:
|
||||
|
||||
Reference in New Issue
Block a user