From 4f4ffc91a8fc28b273660837593d925aa6892efb Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Wed, 13 Jun 2012 17:57:03 +0100 Subject: Allow specification of the libvirt guest CPU model per host Currently Nova does not configure any CPU model for libvirt guests. This is sub-optimal because the default KVM CPU model has changed a number of times, and more importantly VMs are not able to take advantage of many performance features in newer CPUs. To get a consistent CPU model exposed to the guest and maximize performance of VMs in the cloud, explicit specification of CPU models per host is desirable. This change adds a new Nova config flag: libvirt_cpu_mode = host-model|host-passthrough|custom Where * host-model == configure a model that matches the features available in the host CPU * host-passthrough == passthrough the host CPU precisely with no change at all * custom == configure a specific named CPU model If the 'custom' mode is used, then the additional flag is available to choose the model: libvirt_cpu_model = eg libvirt_cpu_model = core2duo If specifying a custom CPU model, it is wise to choose one that is capable of running on all the various different Nova hosts in the cloud. That said, libvirt will enforce compatibility at time of starting or migrating guests & refuse the operation if required. If using either the host-model or host-passthrough modes, and use of live migration is desired, admins should ensure that all hosts have a homogeneous CPU model. If hosts have a hetergeneous CPU models, then a custom named CPU model is a better choice This configuration only works for libvirt >= 0.9.10, due to the use of the 'mode' attribute on the element for configuring host models. Fixes: bug #1003373 Implements: blueprint libvirt-xml-cpu-model Change-Id: I90ce78d7e29bd0d563e3bc547b7cc5d64dd9496e Signed-off-by: Daniel P. Berrange --- nova/tests/test_libvirt.py | 70 +++++++++++++++++++++++++++++++++++++++++++++ nova/virt/libvirt/driver.py | 44 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 3bb377e26..55e8286ad 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -614,6 +614,76 @@ class LibvirtConnTestCase(test.TestCase): config.LibvirtConfigGuestDisk) self.assertEquals(cfg.devices[3].target_dev, 'vdd') + def test_get_guest_cpu_config_none(self): + conn = libvirt_driver.LibvirtDriver(True) + instance_ref = db.instance_create(self.context, self.test_instance) + + conf = conn.get_guest_config(instance_ref, + _fake_network_info(self.stubs, 1), + None, None) + self.assertEquals(conf.cpu, None) + + @test.skip_if(missing_libvirt(), "Test requires libvirt") + def test_get_guest_cpu_config_host_passthrough_new(self): + def get_lib_version_stub(self): + return (0 * 1000 * 1000) + (9 * 1000) + 11 + + self.stubs.Set(libvirt.virConnect, + "getLibVersion", + get_lib_version_stub) + conn = libvirt_driver.LibvirtDriver(True) + instance_ref = db.instance_create(self.context, self.test_instance) + + self.flags(libvirt_cpu_mode="host-passthrough") + conf = conn.get_guest_config(instance_ref, + _fake_network_info(self.stubs, 1), + None, None) + self.assertEquals(type(conf.cpu), + config.LibvirtConfigGuestCPU) + self.assertEquals(conf.cpu.mode, "host-passthrough") + self.assertEquals(conf.cpu.model, None) + + @test.skip_if(missing_libvirt(), "Test requires libvirt") + def test_get_guest_cpu_config_host_model_new(self): + def get_lib_version_stub(self): + return (0 * 1000 * 1000) + (9 * 1000) + 11 + + self.stubs.Set(libvirt.virConnect, + "getLibVersion", + get_lib_version_stub) + conn = libvirt_driver.LibvirtDriver(True) + instance_ref = db.instance_create(self.context, self.test_instance) + + self.flags(libvirt_cpu_mode="host-model") + conf = conn.get_guest_config(instance_ref, + _fake_network_info(self.stubs, 1), + None, None) + self.assertEquals(type(conf.cpu), + config.LibvirtConfigGuestCPU) + self.assertEquals(conf.cpu.mode, "host-model") + self.assertEquals(conf.cpu.model, None) + + @test.skip_if(missing_libvirt(), "Test requires libvirt") + def test_get_guest_cpu_config_custom_new(self): + def get_lib_version_stub(self): + return (0 * 1000 * 1000) + (9 * 1000) + 11 + + self.stubs.Set(libvirt.virConnect, + "getLibVersion", + get_lib_version_stub) + conn = libvirt_driver.LibvirtDriver(True) + instance_ref = db.instance_create(self.context, self.test_instance) + + self.flags(libvirt_cpu_mode="custom") + self.flags(libvirt_cpu_model="Penryn") + conf = conn.get_guest_config(instance_ref, + _fake_network_info(self.stubs, 1), + None, None) + self.assertEquals(type(conf.cpu), + config.LibvirtConfigGuestCPU) + self.assertEquals(conf.cpu.mode, "custom") + self.assertEquals(conf.cpu.model, "Penryn") + def test_xml_and_uri_no_ramdisk_no_kernel(self): instance_data = dict(self.test_instance) self._check_xml_and_uri(instance_data, diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 533597923..96ae61d70 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -165,6 +165,17 @@ libvirt_opts = [ default=None, help='Set to force injection to take place on a config drive ' '(if set, valid options are: always)'), + cfg.StrOpt('libvirt_cpu_mode', + default=None, + help='Set to "host-model" to clone the host CPU feature flags; ' + 'to "host-passthrough" to use the host CPU model ' + 'exactly; or to "custom" to use a named CPU model. Only ' + 'has effect if libvirt_type="kvm|qemu"'), + cfg.StrOpt('libvirt_cpu_model', + default=None, + help='Set to a named libvirt CPU model (see names listed ' + 'in /usr/share/libvirt/cpu_map.xml). Only has effect if ' + 'libvirt_cpu_mode="custom" and libvirt_type="kvm|qemu"'), ] FLAGS = flags.FLAGS @@ -1438,6 +1449,37 @@ class LibvirtDriver(driver.ComputeDriver): caps.parse_str(xmlstr) return caps + def get_guest_cpu_config(self): + mode = FLAGS.libvirt_cpu_mode + model = FLAGS.libvirt_cpu_model + + if mode is None: + return None + + if FLAGS.libvirt_type != "kvm" and FLAGS.libvirt_type != "qemu": + msg = _("Config requested an explicit CPU model, but " + "the current libvirt hypervisor '%s' does not " + "support selecting CPU models") % FLAGS.libvirt_type + raise exception.Invalid(msg) + + if mode == "custom" and model is None: + msg = _("Config requested a custom CPU model, but no " + "model name was provided") + raise exception.Invalid(msg) + elif mode != "custom" and model is not None: + msg = _("A CPU model name should not be set when a " + "host CPU model is requested") + raise exception.Invalid(msg) + + LOG.debug(_("CPU mode '%(mode)s' model '%(model)s' was chosen") + % {'mode': mode, 'model': (model or "")}) + + cpu = config.LibvirtConfigGuestCPU() + cpu.mode = mode + cpu.model = model + + return cpu + def get_guest_config(self, instance, network_info, image_meta, rescue=None, block_device_info=None): """Get config data for parameters. @@ -1462,6 +1504,8 @@ class LibvirtDriver(driver.ComputeDriver): guest.memory = inst_type['memory_mb'] * 1024 guest.vcpus = inst_type['vcpus'] + guest.cpu = self.get_guest_cpu_config() + root_device_name = driver.block_device_info_get_root(block_device_info) if root_device_name: root_device = block_device.strip_dev(root_device_name) -- cgit