From f6969afa4708d1702321dcd48062591bd400789e Mon Sep 17 00:00:00 2001 From: Sahid Orentino Ferdjaoui Date: Thu, 17 Sep 2015 09:20:42 -0400 Subject: [PATCH] libvirt: verify cpu bw policy capability for host In some circumstances mostly related to latency, Linux kernel can have been build with cgroup configuration CONFIG_CGROUP_SCHED not defined which makes not possible to boot virtual machines. We should verify if that cgroup is well mounted on host Change-Id: Ia0af28d1f251ec5aac761b6b34b7db46de479dcc Closes-Bug: #1496854 --- nova/exception.py | 4 ++ nova/tests/unit/virt/libvirt/test_driver.py | 53 +++++++++++++++++---- nova/tests/unit/virt/libvirt/test_host.py | 18 +++++++ nova/virt/libvirt/driver.py | 30 ++++++++---- nova/virt/libvirt/host.py | 14 ++++++ 5 files changed, 99 insertions(+), 20 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index 110e146bf660..238432bb8cc7 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2021,3 +2021,7 @@ class RequestSpecNotFound(NotFound): class NMINotSupported(Invalid): msg_fmt = _("Injecting NMI is not supported") + + +class UnsupportedHostCPUControlPolicy(Invalid): + msg_fmt = _("Requested CPU control policy not supported by host") diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 110de5e411a9..2135798b26e7 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -1305,7 +1305,9 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertIsInstance(cfg.idmaps[1], vconfig.LibvirtConfigGuestGIDMap) - def test_get_guest_config_numa_host_instance_fits(self): + @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): instance_ref = objects.Instance(**self.test_instance) image_meta = objects.ImageMeta.from_dict(self.test_image_meta) flavor = objects.Flavor(memory_mb=1, vcpus=2, root_gb=496, @@ -1335,7 +1337,9 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertEqual(0, len(cfg.cputune.vcpupin)) self.assertIsNone(cfg.cpu.numa) - def test_get_guest_config_numa_host_instance_no_fit(self): + @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): 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, @@ -1434,7 +1438,10 @@ class LibvirtConnTestCase(test.NoDBTestCase): host_topology, inst_topology, numa_tune) self.assertIsNone(result) - def test_get_guest_config_numa_host_instance_pci_no_numa_info(self): + @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): instance_ref = objects.Instance(**self.test_instance) image_meta = objects.ImageMeta.from_dict(self.test_image_meta) flavor = objects.Flavor(memory_mb=1, vcpus=2, root_gb=496, @@ -1480,7 +1487,9 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertEqual(0, len(cfg.cputune.vcpupin)) self.assertIsNone(cfg.cpu.numa) - def test_get_guest_config_numa_host_instance_2pci_no_fit(self): + @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): 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, @@ -1675,7 +1684,10 @@ class LibvirtConnTestCase(test.NoDBTestCase): exception.NUMATopologyUnsupported, 2048) - def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(self): + @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): instance_ref = objects.Instance(**self.test_instance) image_meta = objects.ImageMeta.from_dict(self.test_image_meta) flavor = objects.Flavor(memory_mb=1024, vcpus=2, root_gb=496, @@ -1713,7 +1725,9 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertEqual(0, len(cfg.cputune.vcpupin)) self.assertIsNone(cfg.cpu.numa) - def test_get_guest_config_non_numa_host_instance_topo(self): + @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): instance_topology = objects.InstanceNUMATopology( cells=[objects.InstanceNUMACell( id=0, cpuset=set([0]), memory=1024), @@ -1759,7 +1773,9 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertEqual(instance_cell.memory * units.Ki, numa_cfg_cell.memory) - def test_get_guest_config_numa_host_instance_topo(self): + @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): instance_topology = objects.InstanceNUMATopology( cells=[objects.InstanceNUMACell( id=1, cpuset=set([0, 1]), memory=1024, pagesize=None), @@ -3804,7 +3820,9 @@ class LibvirtConnTestCase(test.NoDBTestCase): [], image_meta, disk_info) - def test_guest_cpu_shares_with_multi_vcpu(self): + @mock.patch.object( + host.Host, "is_cpu_control_policy_capable", return_value=True) + def test_guest_cpu_shares_with_multi_vcpu(self, is_able): self.flags(virt_type='kvm', group='libvirt') drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) @@ -3822,7 +3840,9 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertEqual(4096, cfg.cputune.shares) - def test_get_guest_config_with_cpu_quota(self): + @mock.patch.object( + host.Host, "is_cpu_control_policy_capable", return_value=True) + def test_get_guest_config_with_cpu_quota(self, is_able): self.flags(virt_type='kvm', group='libvirt') drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) @@ -3842,7 +3862,9 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertEqual(10000, cfg.cputune.shares) self.assertEqual(20000, cfg.cputune.period) - def test_get_guest_config_with_bogus_cpu_quota(self): + @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): self.flags(virt_type='kvm', group='libvirt') drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) @@ -3860,6 +3882,17 @@ 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): + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + instance_ref = objects.Instance(**self.test_instance) + instance_ref.flavor.extra_specs = {'quota:cpu_shares': '10000', + 'quota:cpu_period': '20000'} + self.assertRaises( + exception.UnsupportedHostCPUControlPolicy, + drvr._update_guest_cputune, {}, instance_ref.flavor, "kvm") + def _test_get_guest_config_sysinfo_serial(self, expected_serial): drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) diff --git a/nova/tests/unit/virt/libvirt/test_host.py b/nova/tests/unit/virt/libvirt/test_host.py index 4fa3d6668fe4..893e6c4821b2 100644 --- a/nova/tests/unit/virt/libvirt/test_host.py +++ b/nova/tests/unit/virt/libvirt/test_host.py @@ -874,6 +874,24 @@ 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( + "six.moves.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( + "six.moves.builtins.open", m, create=True): + self.assertFalse(self.host.is_cpu_control_policy_capable()) + class DomainJobInfoTestCase(test.NoDBTestCase): diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index e6a71ed90388..b4de677acdbf 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -3542,19 +3542,29 @@ class LibvirtDriver(driver.ComputeDriver): return id_maps def _update_guest_cputune(self, guest, flavor, virt_type): - if virt_type in ('lxc', 'kvm', 'qemu'): - if guest.cputune is None: - guest.cputune = vconfig.LibvirtConfigGuestCPUTune() + is_able = self._host.is_cpu_control_policy_capable() + + cputuning = ['shares', 'period', 'quota'] + wants_cputune = any([k for k in cputuning + if "quota:cpu_" + k in flavor.extra_specs.keys()]) + + if wants_cputune and not is_able: + raise exception.UnsupportedHostCPUControlPolicy() + + if not is_able or virt_type not in ('lxc', 'kvm', 'qemu'): + return + + if guest.cputune is None: + guest.cputune = vconfig.LibvirtConfigGuestCPUTune() # Setting the default cpu.shares value to be a value # dependent on the number of vcpus - guest.cputune.shares = 1024 * guest.vcpus + guest.cputune.shares = 1024 * guest.vcpus - cputuning = ['shares', 'period', 'quota'] - for name in cputuning: - key = "quota:cpu_" + name - if key in flavor.extra_specs: - setattr(guest.cputune, name, - int(flavor.extra_specs[key])) + for name in cputuning: + key = "quota:cpu_" + name + if key in flavor.extra_specs: + setattr(guest.cputune, name, + int(flavor.extra_specs[key])) def _get_cpu_numa_config_from_instance(self, instance_numa_topology, wants_hugepages): diff --git a/nova/virt/libvirt/host.py b/nova/virt/libvirt/host.py index cc114bc23d9b..b5c8c71832ef 100644 --- a/nova/virt/libvirt/host.py +++ b/nova/virt/libvirt/host.py @@ -995,3 +995,17 @@ class Host(object): def compare_cpu(self, xmlDesc, flags=0): """Compares the given CPU description with the host CPU.""" return self.get_connection().compareCPU(xmlDesc, flags) + + def is_cpu_control_policy_capable(self): + """Returns whether kernel configuration CGROUP_SCHED is enabled + + CONFIG_CGROUP_SCHED may be disabled in some kernel configs to + improve scheduler latency. + """ + 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: + return True + return False