libvirt: Support maxphysaddr.

With Libvirt v8.7.0+, the <maxphysaddr> sub-element
of the <cpu> element specifies the number of vCPU
physical address bits [1].

[1] https://libvirt.org/news.html#v8-7-0-2022-09-01

New flavor extra_specs and image properties are added to
control the physical address bits of vCPUs in Libvirt guests.
The nova-scheduler requests COMPUTE_ADDRESS_SPACE_* traits
based on them. The traits are already defined in os-traits
v2.10.0. Also numerical comparisons are performed at
both compute capabilities filter and image props filter.

blueprint: libvirt-maxphysaddr-support-caracal
Change-Id: I98968f6ef1621c9fb4f682c119038e26d62ce381
Signed-off-by: Nobuhiro MIKI <nmiki@yahoo-corp.jp>
This commit is contained in:
Nobuhiro MIKI 2023-09-15 16:48:18 +09:00
parent 9a9ab2128b
commit 1038a63387
18 changed files with 473 additions and 7 deletions

View File

@ -560,6 +560,13 @@ class TPMVersion(BaseNovaEnum):
ALL = (v1_2, v2_0) ALL = (v1_2, v2_0)
class MaxPhyAddrMode(BaseNovaEnum):
PASSTHROUGH = "passthrough"
EMULATE = "emulate"
ALL = (PASSTHROUGH, EMULATE)
class SCSIModel(BaseNovaEnum): class SCSIModel(BaseNovaEnum):
BUSLOGIC = "buslogic" BUSLOGIC = "buslogic"
@ -1294,6 +1301,10 @@ class InputBusField(BaseEnumField):
AUTO_TYPE = InputBus() AUTO_TYPE = InputBus()
class MaxPhysAddrModeField(BaseEnumField):
AUTO_TYPE = MaxPhyAddrMode()
class MigrationTypeField(BaseEnumField): class MigrationTypeField(BaseEnumField):
AUTO_TYPE = MigrationType() AUTO_TYPE = MigrationType()

View File

@ -193,14 +193,19 @@ class ImageMetaProps(base.NovaObject):
# Version 1.33: Added 'hw_locked_memory' field # Version 1.33: Added 'hw_locked_memory' field
# Version 1.34: Added 'hw_viommu_model' field # Version 1.34: Added 'hw_viommu_model' field
# Version 1.35: Added 'hw_virtio_packed_ring' field # Version 1.35: Added 'hw_virtio_packed_ring' field
# Version 1.36: Added 'hw_maxphysaddr_mode' and
# 'hw_maxphysaddr_bits' field
# NOTE(efried): When bumping this version, the version of # NOTE(efried): When bumping this version, the version of
# ImageMetaPropsPayload must also be bumped. See its docstring for details. # ImageMetaPropsPayload must also be bumped. See its docstring for details.
VERSION = '1.35' VERSION = '1.36'
def obj_make_compatible(self, primitive, target_version): def obj_make_compatible(self, primitive, target_version):
super(ImageMetaProps, self).obj_make_compatible(primitive, super(ImageMetaProps, self).obj_make_compatible(primitive,
target_version) target_version)
target_version = versionutils.convert_version_to_tuple(target_version) target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 36):
primitive.pop('hw_maxphysaddr_mode', None)
primitive.pop('hw_maxphysaddr_bits', None)
if target_version < (1, 35): if target_version < (1, 35):
primitive.pop('hw_virtio_packed_ring', None) primitive.pop('hw_virtio_packed_ring', None)
if target_version < (1, 34): if target_version < (1, 34):
@ -479,6 +484,12 @@ class ImageMetaProps(base.NovaObject):
# boolean - If true, this will enable the virtio packed ring feature # boolean - If true, this will enable the virtio packed ring feature
'hw_virtio_packed_ring': fields.FlexibleBooleanField(), 'hw_virtio_packed_ring': fields.FlexibleBooleanField(),
# Control mode for the physical memory address bit of Libvirt guests.
'hw_maxphysaddr_mode': fields.MaxPhysAddrModeField(),
# Control bits for the physical memory address bit of Libvirt guests.
'hw_maxphysaddr_bits': fields.IntegerField(),
# if true download using bittorrent # if true download using bittorrent
'img_bittorrent': fields.FlexibleBooleanField(), 'img_bittorrent': fields.FlexibleBooleanField(),

View File

@ -72,7 +72,15 @@ class ComputeCapabilitiesFilter(filters.BaseHostFilter):
if 'extra_specs' not in flavor: if 'extra_specs' not in flavor:
return True return True
for key, req in flavor.extra_specs.items(): especs = flavor.extra_specs.copy()
# Replace it with a capabilities filter specially.
bits = especs.get('hw:maxphysaddr_bits')
if bits is not None:
especs['capabilities:cpu_info:maxphysaddr:bits'] = '>= ' + bits
del especs['hw:maxphysaddr_bits']
for key, req in especs.items():
# Either not scope format, or in capabilities scope # Either not scope format, or in capabilities scope
scope = key.split(':') scope = key.split(':')
# If key does not have a namespace, the scope's size is 1, check # If key does not have a namespace, the scope's size is 1, check

View File

@ -89,9 +89,21 @@ class ImagePropertiesFilter(filters.BaseHostFilter):
hyper_ver_str = versionutils.convert_version_to_str(hyper_version) hyper_ver_str = versionutils.convert_version_to_str(hyper_version)
return img_prop_predicate.satisfied_by(hyper_ver_str) return img_prop_predicate.satisfied_by(hyper_ver_str)
def _compare_maxphysaddr_bits(host_state, image_props):
bits_required = image_props.get('hw_maxphysaddr_bits')
if not bits_required:
return True
bits = host_state.cpu_info.get('maxphysaddr', {}).get('bits')
if not bits:
return True
return bits >= bits_required
for supp_inst in supp_instances: for supp_inst in supp_instances:
if _compare_props(checked_img_props, supp_inst): if _compare_props(checked_img_props, supp_inst):
if _compare_product_version(hypervisor_version, image_props): if _compare_product_version(hypervisor_version, image_props):
if _compare_maxphysaddr_bits(host_state, image_props):
return True return True
LOG.debug("Instance contains properties %(image_props)s " LOG.debug("Instance contains properties %(image_props)s "

View File

@ -194,6 +194,8 @@ class ResourceRequest(object):
res_req._translate_secure_boot_request(request_spec.flavor, image) res_req._translate_secure_boot_request(request_spec.flavor, image)
res_req._translate_maxphysaddr_request(request_spec.flavor, image)
res_req.strip_zeros() res_req.strip_zeros()
return res_req return res_req
@ -271,6 +273,23 @@ class ResourceRequest(object):
self._add_trait(trait, 'required') self._add_trait(trait, 'required')
LOG.debug("Requiring secure boot support via trait %s.", trait) LOG.debug("Requiring secure boot support via trait %s.", trait)
def _translate_maxphysaddr_request(self, flavor, image):
mode = hardware.get_maxphysaddr_mode(flavor, image)
if mode is None:
return
trait = None
if mode == obj_fields.MaxPhyAddrMode.PASSTHROUGH:
trait = os_traits.COMPUTE_ADDRESS_SPACE_PASSTHROUGH
elif mode == obj_fields.MaxPhyAddrMode.EMULATE:
trait = os_traits.COMPUTE_ADDRESS_SPACE_EMULATED
if trait:
self._add_trait(trait, 'required')
LOG.debug("Requiring maxphysaddr support via trait %s.", trait)
def _translate_vtpm_request(self, flavor, image): def _translate_vtpm_request(self, flavor, image):
vtpm_config = hardware.get_vtpm_constraint(flavor, image) vtpm_config = hardware.get_vtpm_constraint(flavor, image)
if not vtpm_config: if not vtpm_config:

View File

@ -386,7 +386,7 @@ notification_object_data = {
# ImageMetaProps, so when you see a fail here for that reason, you must # ImageMetaProps, so when you see a fail here for that reason, you must
# *also* bump the version of ImageMetaPropsPayload. See its docstring for # *also* bump the version of ImageMetaPropsPayload. See its docstring for
# more information. # more information.
'ImageMetaPropsPayload': '1.13-24345c28a6463e85e12902d43af0ecf2', 'ImageMetaPropsPayload': '1.13-2859fbb81af5ad2f83a0e9be0b30ea60',
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'InstanceActionPayload': '1.8-4fa3da9cbf0761f1f700ae578f36dc2f', 'InstanceActionPayload': '1.8-4fa3da9cbf0761f1f700ae578f36dc2f',
'InstanceActionRebuildNotification': 'InstanceActionRebuildNotification':

View File

@ -358,6 +358,8 @@ class TestImageMetaProps(test.NoDBTestCase):
'img_hv_requested_version': '>= 1.0', 'img_hv_requested_version': '>= 1.0',
'os_require_quiesce': True, 'os_require_quiesce': True,
'os_secure_boot': 'required', 'os_secure_boot': 'required',
'hw_maxphysaddr_mode': 'passthrough',
'hw_maxphysaddr_bits': 42,
'hw_rescue_bus': 'ide', 'hw_rescue_bus': 'ide',
'hw_rescue_device': 'disk', 'hw_rescue_device': 'disk',
'hw_watchdog_action': fields.WatchdogAction.DISABLED, 'hw_watchdog_action': fields.WatchdogAction.DISABLED,
@ -499,6 +501,20 @@ class TestImageMetaProps(test.NoDBTestCase):
secure_props = objects.ImageMetaProps.from_dict(props) secure_props = objects.ImageMetaProps.from_dict(props)
self.assertEqual("required", secure_props.os_secure_boot) self.assertEqual("required", secure_props.os_secure_boot)
def test_set_hw_maxphysaddr_mode(self):
props = {'hw_maxphysaddr_mode': "passthrough"}
obj = objects.ImageMetaProps.from_dict(props)
self.assertEqual("passthrough", obj.hw_maxphysaddr_mode)
def test_set_hw_maxphysaddr_mode_negative(self):
props = {'hw_maxphysaddr_mode': "blah"}
self.assertRaises(ValueError, objects.ImageMetaProps.from_dict, props)
def test_set_hw_maxphysaddr_bits(self):
props = {'hw_maxphysaddr_bits': 42}
obj = objects.ImageMetaProps.from_dict(props)
self.assertEqual(42, obj.hw_maxphysaddr_bits)
def test_obj_make_compatible_img_hide_hypervisor_id(self): def test_obj_make_compatible_img_hide_hypervisor_id(self):
"""Tests that checks if we pop img_hide_hypervisor_id.""" """Tests that checks if we pop img_hide_hypervisor_id."""
obj = objects.ImageMetaProps(img_hide_hypervisor_id=True) obj = objects.ImageMetaProps(img_hide_hypervisor_id=True)

View File

@ -1105,7 +1105,7 @@ object_data = {
'HyperVLiveMigrateData': '1.4-e265780e6acfa631476c8170e8d6fce0', 'HyperVLiveMigrateData': '1.4-e265780e6acfa631476c8170e8d6fce0',
'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502', 'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502',
'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d', 'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d',
'ImageMetaProps': '1.35-66ec4135a4c08d6e67e39cb0400b059e', 'ImageMetaProps': '1.36-5d06cd527d8d32e6e2b457ce3b9369e0',
'Instance': '2.8-2727dba5e4a078e6cc848c1f94f7eb24', 'Instance': '2.8-2727dba5e4a078e6cc848c1f94f7eb24',
'InstanceAction': '1.2-9a5abc87fdd3af46f45731960651efb5', 'InstanceAction': '1.2-9a5abc87fdd3af46f45731960651efb5',
'InstanceActionEvent': '1.4-5b1f361bd81989f8bb2c20bb7e8a4cb4', 'InstanceActionEvent': '1.4-5b1f361bd81989f8bb2c20bb7e8a4cb4',

View File

@ -102,6 +102,30 @@ class TestComputeCapabilitiesFilter(test.NoDBTestCase):
especs={'capabilities:cpu_info:vendor': 'Intel'}, especs={'capabilities:cpu_info:vendor': 'Intel'},
passes=False) passes=False)
def test_compute_filter_pass_maxphysaddr_bits(self):
cpu_info = '{"maxphysaddr": {"mode": "emulate", "bits": 42}}'
self._do_test_compute_filter_extra_specs(
ecaps={'cpu_info': cpu_info},
especs={'hw:maxphysaddr_bits': '42'},
passes=True)
def test_compute_filter_fails_maxphysaddr_bits(self):
cpu_info = '{"maxphysaddr": {"mode": "emulate", "bits": 20}}'
self._do_test_compute_filter_extra_specs(
ecaps={'cpu_info': cpu_info},
especs={'hw:maxphysaddr_bits': '42'},
passes=False)
def test_compute_filter_fails_without_maxphysaddr_bits(self):
cpu_info = '{ }'
self._do_test_compute_filter_extra_specs(
ecaps={'cpu_info': cpu_info},
especs={'hw:maxphysaddr_bits': '42'},
passes=False)
def test_compute_filter_passes_extra_specs_simple(self): def test_compute_filter_passes_extra_specs_simple(self):
self._do_test_compute_filter_extra_specs( self._do_test_compute_filter_extra_specs(
ecaps={'stats': {'opt1': 1, 'opt2': 2}}, ecaps={'stats': {'opt1': 1, 'opt2': 2}},

View File

@ -267,3 +267,37 @@ class TestImagePropsFilter(test.NoDBTestCase):
'hypervisor_version': hypervisor_version} 'hypervisor_version': hypervisor_version}
host = fakes.FakeHostState('host1', 'node1', capabilities) host = fakes.FakeHostState('host1', 'node1', capabilities)
self.assertTrue(self.filt_cls.host_passes(host, spec_obj)) self.assertTrue(self.filt_cls.host_passes(host, spec_obj))
def test_image_properties_filter_pass_maxphysaddr_bits(self):
img_props = objects.ImageMeta(
properties=objects.ImageMetaProps(
hw_architecture=obj_fields.Architecture.X86_64,
img_hv_type=obj_fields.HVType.KVM,
hw_vm_mode=obj_fields.VMMode.HVM,
hw_maxphysaddr_bits=42))
spec_obj = objects.RequestSpec(image=img_props)
capabilities = {
'supported_instances': [(
obj_fields.Architecture.X86_64,
obj_fields.HVType.KVM,
obj_fields.VMMode.HVM)],
'cpu_info': {"maxphysaddr": {"mode": "emulate", "bits": 42}}}
host = fakes.FakeHostState('host1', 'node1', capabilities)
self.assertTrue(self.filt_cls.host_passes(host, spec_obj))
def test_image_properties_filter_fails_maxphysaddr_bits(self):
img_props = objects.ImageMeta(
properties=objects.ImageMetaProps(
hw_architecture=obj_fields.Architecture.X86_64,
img_hv_type=obj_fields.HVType.KVM,
hw_vm_mode=obj_fields.VMMode.HVM,
hw_maxphysaddr_bits=42))
spec_obj = objects.RequestSpec(image=img_props)
capabilities = {
'supported_instances': [(
obj_fields.Architecture.X86_64,
obj_fields.HVType.KVM,
obj_fields.VMMode.HVM)],
'cpu_info': {"maxphysaddr": {"mode": "emulate", "bits": 20}}}
host = fakes.FakeHostState('host1', 'node1', capabilities)
self.assertFalse(self.filt_cls.host_passes(host, spec_obj))

View File

@ -1358,6 +1358,47 @@ class TestUtils(TestUtilsBase):
rr = utils.ResourceRequest.from_request_spec(rs) rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr) self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_from_request_spec_with_maxphysaddr_passthrough(
self
):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs={'hw:maxphysaddr_mode': 'passthrough'}
)
expected = FakeResourceRequest()
expected._rg_by_id[None] = objects.RequestGroup(
use_same_provider=False,
required_traits={'COMPUTE_ADDRESS_SPACE_PASSTHROUGH'},
resources={
'VCPU': 1,
'MEMORY_MB': 1024,
'DISK_GB': 15,
},
)
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_from_request_spec_with_maxphysaddr_emulate(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs={'hw:maxphysaddr_mode': 'emulate',
'hw_maxphysaddr_bits': 42},
)
expected = FakeResourceRequest()
expected._rg_by_id[None] = objects.RequestGroup(
use_same_provider=False,
required_traits={'COMPUTE_ADDRESS_SPACE_EMULATED'},
resources={
'VCPU': 1,
'MEMORY_MB': 1024,
'DISK_GB': 15,
},
)
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_from_request_groups(self): def test_resource_request_from_request_groups(self):
rgs = objects.RequestGroup.from_extended_port_request( rgs = objects.RequestGroup.from_extended_port_request(
self.context, self.context,

View File

@ -520,6 +520,32 @@ class LibvirtConfigCPUTest(LibvirtConfigBaseTest):
</cpu> </cpu>
""") """)
def test_config_maxphysaddr(self):
obj = config.LibvirtConfigCPU()
obj.maxphysaddr = config.LibvirtConfigCPUMaxPhysAddr()
obj.maxphysaddr.mode = "emulate"
obj.maxphysaddr.bits = 42
xml = obj.to_xml()
self.assertXmlEqual(xml, """
<cpu>
<maxphysaddr mode='emulate' bits='42'/>
</cpu>
""")
def test_parse_dom(self):
xml = """
<cpu>
<maxphysaddr mode='emulate' bits='42'/>
</cpu>
"""
xmldoc = etree.fromstring(xml)
obj = config.LibvirtConfigCPU()
obj.parse_dom(xmldoc)
self.assertEqual("emulate", obj.maxphysaddr.mode)
self.assertEqual(42, obj.maxphysaddr.bits)
class LibvirtConfigGuestCPUTest(LibvirtConfigBaseTest): class LibvirtConfigGuestCPUTest(LibvirtConfigBaseTest):
@ -599,6 +625,35 @@ class LibvirtConfigGuestCPUTest(LibvirtConfigBaseTest):
</cpu> </cpu>
""") """)
def test_config_host_with_maxphysaddr_emulate(self):
obj = config.LibvirtConfigGuestCPU()
m = config.LibvirtConfigGuestCPUMaxPhysAddr()
m.mode = "emulate"
m.bits = 42
obj.maxphysaddr = m
xml = obj.to_xml()
self.assertXmlEqual(xml, """
<cpu match="exact">
<maxphysaddr mode="emulate" bits="42"/>
</cpu>
""")
def test_config_host_with_maxphysaddr_passthrough(self):
obj = config.LibvirtConfigGuestCPU()
m = config.LibvirtConfigGuestCPUMaxPhysAddr()
m.mode = "passthrough"
obj.maxphysaddr = m
xml = obj.to_xml()
self.assertXmlEqual(xml, """
<cpu match="exact">
<maxphysaddr mode="passthrough"/>
</cpu>
""")
class LibvirtConfigGuestSMBIOSTest(LibvirtConfigBaseTest): class LibvirtConfigGuestSMBIOSTest(LibvirtConfigBaseTest):

View File

@ -8614,6 +8614,86 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(conf.cpu.cores, 1) self.assertEqual(conf.cpu.cores, 1)
self.assertEqual(conf.cpu.threads, 1) self.assertEqual(conf.cpu.threads, 1)
def test_get_guest_cpu_config_maxphysaddr_passthrough_flavor_espec(self):
self.flags(cpu_mode="none", group='libvirt')
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
instance_ref = objects.Instance(**self.test_instance)
instance_ref.flavor.extra_specs = {
'hw:maxphysaddr_mode': 'passthrough'}
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
instance_ref,
image_meta)
conf = drvr._get_guest_config(instance_ref,
_fake_network_info(self),
image_meta, disk_info)
self.assertIsInstance(conf.cpu.maxphysaddr,
vconfig.LibvirtConfigGuestCPUMaxPhysAddr)
self.assertEqual(conf.cpu.maxphysaddr.mode, 'passthrough')
self.assertIsNone(conf.cpu.maxphysaddr.bits)
def test_get_guest_cpu_config_maxphysaddr_emulate_flavor_espec(self):
self.flags(cpu_mode="none", group='libvirt')
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
instance_ref = objects.Instance(**self.test_instance)
instance_ref.flavor.extra_specs = {
'hw:maxphysaddr_mode': 'emulate',
'hw:maxphysaddr_bits': 42}
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
instance_ref,
image_meta)
conf = drvr._get_guest_config(instance_ref,
_fake_network_info(self),
image_meta, disk_info)
self.assertIsInstance(conf.cpu.maxphysaddr,
vconfig.LibvirtConfigGuestCPUMaxPhysAddr)
self.assertEqual(conf.cpu.maxphysaddr.mode, 'emulate')
self.assertEqual(conf.cpu.maxphysaddr.bits, 42)
def test_get_guest_cpu_config_maxphysaddr_passthrough_image_meta(self):
self.flags(cpu_mode="none", group='libvirt')
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
instance_ref = objects.Instance(**self.test_instance)
image_meta = objects.ImageMeta.from_dict({
"disk_format": "raw",
"properties": {"hw_maxphysaddr_mode": "passthrough"},
})
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
instance_ref,
image_meta)
conf = drvr._get_guest_config(instance_ref,
_fake_network_info(self),
image_meta, disk_info)
self.assertIsInstance(conf.cpu.maxphysaddr,
vconfig.LibvirtConfigGuestCPUMaxPhysAddr)
self.assertEqual(conf.cpu.maxphysaddr.mode, 'passthrough')
self.assertIsNone(conf.cpu.maxphysaddr.bits)
def test_get_guest_cpu_config_maxphysaddr_emulate_image_meta(self):
self.flags(cpu_mode="none", group='libvirt')
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
instance_ref = objects.Instance(**self.test_instance)
image_meta = objects.ImageMeta.from_dict({
"disk_format": "raw",
"properties": {"hw_maxphysaddr_mode": "emulate",
"hw_maxphysaddr_bits": 42},
})
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
instance_ref,
image_meta)
conf = drvr._get_guest_config(instance_ref,
_fake_network_info(self),
image_meta, disk_info)
self.assertIsInstance(conf.cpu.maxphysaddr,
vconfig.LibvirtConfigGuestCPUMaxPhysAddr)
self.assertEqual(conf.cpu.maxphysaddr.mode, 'emulate')
self.assertEqual(conf.cpu.maxphysaddr.bits, 42)
def test_get_guest_cpu_topology(self): def test_get_guest_cpu_topology(self):
instance_ref = objects.Instance(**self.test_instance) instance_ref = objects.Instance(**self.test_instance)
instance_ref.flavor.vcpus = 8 instance_ref.flavor.vcpus = 8
@ -18515,6 +18595,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
cpu.threads = 1 cpu.threads = 1
cpu.sockets = 4 cpu.sockets = 4
cpu.maxphysaddr = vconfig.LibvirtConfigCPUMaxPhysAddr()
cpu.maxphysaddr.mode = "emulate"
cpu.maxphysaddr.bits = 42
cpu.add_feature(vconfig.LibvirtConfigCPUFeature("extapic")) cpu.add_feature(vconfig.LibvirtConfigCPUFeature("extapic"))
cpu.add_feature(vconfig.LibvirtConfigCPUFeature("3dnow")) cpu.add_feature(vconfig.LibvirtConfigCPUFeature("3dnow"))
@ -18541,6 +18625,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
want = {"vendor": "AMD", want = {"vendor": "AMD",
"features": set(["extapic", "3dnow"]), "features": set(["extapic", "3dnow"]),
"maxphysaddr": {"mode": "emulate", "bits": 42},
"model": "Opteron_G4", "model": "Opteron_G4",
"arch": fields.Architecture.X86_64, "arch": fields.Architecture.X86_64,
"topology": {"cells": 1, "cores": 2, "threads": 1, "topology": {"cells": 1, "cores": 2, "threads": 1,
@ -21907,7 +21992,8 @@ class HostStateTestCase(test.NoDBTestCase):
"features": ["ssse3", "monitor", "pni", "sse2", "sse", "features": ["ssse3", "monitor", "pni", "sse2", "sse",
"fxsr", "clflush", "pse36", "pat", "cmov", "mca", "pge", "fxsr", "clflush", "pse36", "pat", "cmov", "mca", "pge",
"mtrr", "sep", "apic"], "mtrr", "sep", "apic"],
"topology": {"cores": "1", "threads": "1", "sockets": "1"}} "topology": {"cores": "1", "threads": "1", "sockets": "1"},
"maxphysaddr": {"mode": "emulate", "bits": "42"}}
instance_caps = [(fields.Architecture.X86_64, "kvm", "hvm"), instance_caps = [(fields.Architecture.X86_64, "kvm", "hvm"),
(fields.Architecture.I686, "kvm", "hvm")] (fields.Architecture.I686, "kvm", "hvm")]
pci_devices = [{ pci_devices = [{
@ -22025,7 +22111,8 @@ class HostStateTestCase(test.NoDBTestCase):
"features": ["ssse3", "monitor", "pni", "sse2", "sse", "features": ["ssse3", "monitor", "pni", "sse2", "sse",
"fxsr", "clflush", "pse36", "pat", "cmov", "fxsr", "clflush", "pse36", "pat", "cmov",
"mca", "pge", "mtrr", "sep", "apic"], "mca", "pge", "mtrr", "sep", "apic"],
"topology": {"cores": "1", "threads": "1", "sockets": "1"} "topology": {"cores": "1", "threads": "1", "sockets": "1"},
"maxphysaddr": {"mode": "emulate", "bits": "42"}
}) })
self.assertEqual(stats["disk_available_least"], 80) self.assertEqual(stats["disk_available_least"], 80)
self.assertEqual(jsonutils.loads(stats["pci_passthrough_devices"]), self.assertEqual(jsonutils.loads(stats["pci_passthrough_devices"]),

View File

@ -5860,6 +5860,52 @@ class SecureBootPolicyTest(test.NoDBTestCase):
) )
@ddt.ddt
class MaxphysaddrModeTest(test.NoDBTestCase):
@ddt.unpack
@ddt.data(
# pass: no configuration
(None, None, None),
# pass: flavor-only configuration
('passthrough', None, 'passthrough'),
# pass: image-only configuration
(None, 'emulate', 'emulate'),
# pass: identical image and flavor configuration
('passthrough', 'passthrough', 'passthrough'),
# fail: mismatched image and flavor configuration
('passthrough', 'emulate', exception.FlavorImageConflict),
# fail: invalid value
('foobar', None, exception.Invalid),
)
def test_get_maxphysaddr_mode(
self, flavor_policy, image_policy, expected,
):
extra_specs = {}
if flavor_policy:
extra_specs['hw:maxphysaddr_mode'] = flavor_policy
image_meta_props = {}
if image_policy:
image_meta_props['hw_maxphysaddr_mode'] = image_policy
flavor = objects.Flavor(
name='foo', vcpus=1, memory_mb=1024, extra_specs=extra_specs)
image_meta = objects.ImageMeta.from_dict(
{'name': 'bar', 'properties': image_meta_props})
if isinstance(expected, type) and issubclass(expected, Exception):
self.assertRaises(
expected, hw.get_maxphysaddr_mode, flavor, image_meta,
)
else:
self.assertEqual(
expected, hw.get_maxphysaddr_mode(flavor, image_meta),
)
@ddt.ddt @ddt.ddt
class RescuePropertyTestCase(test.NoDBTestCase): class RescuePropertyTestCase(test.NoDBTestCase):

View File

@ -2735,6 +2735,33 @@ def get_vpmems(flavor):
return formed_labels return formed_labels
def get_maxphysaddr_mode(
flavor: 'objects.Flavor',
image_meta: 'objects.ImageMeta',
) -> ty.Optional[str]:
"""Return maxphysaddr mode.
:param flavor: a flavor object to read extra specs from
:param image_meta: an objects.ImageMeta object
:raises: nova.exception.Invalid if a value is invalid
:returns: maxphysaddr mode if a value is valid, else None.
"""
mode = _get_unique_flavor_image_meta(
'maxphysaddr_mode', flavor, image_meta, prefix='hw',
)
if mode is None:
return None
if mode not in fields.MaxPhyAddrMode.ALL:
raise exception.Invalid(
"Invalid Maxphyaddr mode %(mode)r. Allowed values: %(valid)s." %
{'mode': mode, 'valid': ', '.join(fields.MaxPhyAddrMode.ALL)}
)
return mode
def check_hw_rescue_props(image_meta): def check_hw_rescue_props(image_meta):
"""Confirm that hw_rescue_* image properties are present. """Confirm that hw_rescue_* image properties are present.
""" """

View File

@ -793,6 +793,36 @@ class LibvirtConfigCPUFeature(LibvirtConfigObject):
return hash(self.name) return hash(self.name)
class LibvirtConfigCPUMaxPhysAddr(LibvirtConfigObject):
def __init__(self, **kwargs):
super(LibvirtConfigCPUMaxPhysAddr, self).__init__(
root_name='maxphysaddr', **kwargs)
self.mode = None
self.bits = None
def parse_dom(self, xmldoc):
super(LibvirtConfigCPUMaxPhysAddr, self).parse_dom(xmldoc)
self.mode = xmldoc.get("mode")
self.bits = int(xmldoc.get("bits"))
def format_dom(self):
m = super(LibvirtConfigCPUMaxPhysAddr, self).format_dom()
m.set("mode", self.mode)
if self.bits:
m.set("bits", str(self.bits))
return m
class LibvirtConfigGuestCPUMaxPhysAddr(LibvirtConfigCPUMaxPhysAddr):
pass
class LibvirtConfigCPU(LibvirtConfigObject): class LibvirtConfigCPU(LibvirtConfigObject):
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -807,6 +837,8 @@ class LibvirtConfigCPU(LibvirtConfigObject):
self.cores = None self.cores = None
self.threads = None self.threads = None
self.maxphysaddr = None
self.features = set() self.features = set()
def parse_dom(self, xmldoc): def parse_dom(self, xmldoc):
@ -823,6 +855,9 @@ class LibvirtConfigCPU(LibvirtConfigObject):
self.sockets = int(c.get("sockets")) self.sockets = int(c.get("sockets"))
self.cores = int(c.get("cores")) self.cores = int(c.get("cores"))
self.threads = int(c.get("threads")) self.threads = int(c.get("threads"))
elif c.tag == "maxphysaddr":
self.maxphysaddr = LibvirtConfigCPUMaxPhysAddr()
self.maxphysaddr.parse_dom(c)
elif c.tag == "feature": elif c.tag == "feature":
f = LibvirtConfigCPUFeature() f = LibvirtConfigCPUFeature()
f.parse_dom(c) f.parse_dom(c)
@ -848,6 +883,9 @@ class LibvirtConfigCPU(LibvirtConfigObject):
top.set("threads", str(self.threads)) top.set("threads", str(self.threads))
cpu.append(top) cpu.append(top)
if self.maxphysaddr is not None:
cpu.append(self.maxphysaddr.format_dom())
# sorting the features to allow more predictable tests # sorting the features to allow more predictable tests
for f in sorted(self.features, key=lambda x: x.name): for f in sorted(self.features, key=lambda x: x.name):
cpu.append(f.format_dom()) cpu.append(f.format_dom())
@ -942,6 +980,7 @@ class LibvirtConfigGuestCPU(LibvirtConfigCPU):
self.mode = None self.mode = None
self.match = "exact" self.match = "exact"
self.numa = None self.numa = None
self.maxphysaddr = None
def parse_dom(self, xmldoc): def parse_dom(self, xmldoc):
super(LibvirtConfigGuestCPU, self).parse_dom(xmldoc) super(LibvirtConfigGuestCPU, self).parse_dom(xmldoc)
@ -952,6 +991,10 @@ class LibvirtConfigGuestCPU(LibvirtConfigCPU):
numa = LibvirtConfigGuestCPUNUMA() numa = LibvirtConfigGuestCPUNUMA()
numa.parse_dom(child) numa.parse_dom(child)
self.numa = numa self.numa = numa
elif child.tag == "maxphysaddr":
m = LibvirtConfigGuestCPUMaxPhysAddr()
m.parse_dom(child)
self.maxphysaddr = m
def format_dom(self): def format_dom(self):
cpu = super(LibvirtConfigGuestCPU, self).format_dom() cpu = super(LibvirtConfigGuestCPU, self).format_dom()

View File

@ -5475,6 +5475,23 @@ class LibvirtDriver(driver.ComputeDriver):
cpu.add_feature(cpu_feature) cpu.add_feature(cpu_feature)
return cpu return cpu
def _get_guest_cpu_config_maxphysaddr(self, flavor, image_meta):
mode = (flavor.extra_specs.get('hw:maxphysaddr_mode') or
image_meta.properties.get('hw_maxphysaddr_mode'))
bits = (flavor.extra_specs.get('hw:maxphysaddr_bits') or
image_meta.properties.get('hw_maxphysaddr_bits'))
if not mode:
return None
maxphysaddr = vconfig.LibvirtConfigGuestCPUMaxPhysAddr()
maxphysaddr.mode = mode
if bits:
maxphysaddr.bits = int(bits)
return maxphysaddr
def _match_cpu_model_by_flags(self, models, flags): def _match_cpu_model_by_flags(self, models, flags):
for model in models: for model in models:
if flags.issubset(self.cpu_model_flag_mapping.get(model, set([]))): if flags.issubset(self.cpu_model_flag_mapping.get(model, set([]))):
@ -5509,6 +5526,9 @@ class LibvirtDriver(driver.ComputeDriver):
cpu.threads = topology.threads cpu.threads = topology.threads
cpu.numa = guest_cpu_numa_config cpu.numa = guest_cpu_numa_config
cpu.maxphysaddr = self._get_guest_cpu_config_maxphysaddr(flavor,
image_meta)
caps = self._host.get_capabilities() caps = self._host.get_capabilities()
if arch != caps.host.cpu.arch: if arch != caps.host.cpu.arch:
# Try emulating. Other arch configs will go here # Try emulating. Other arch configs will go here
@ -8234,6 +8254,12 @@ class LibvirtDriver(driver.ComputeDriver):
topology['threads'] = caps.host.cpu.threads topology['threads'] = caps.host.cpu.threads
cpu_info['topology'] = topology cpu_info['topology'] = topology
if caps.host.cpu.maxphysaddr:
maxphysaddr = dict()
maxphysaddr["mode"] = caps.host.cpu.maxphysaddr.mode
maxphysaddr["bits"] = caps.host.cpu.maxphysaddr.bits
cpu_info["maxphysaddr"] = maxphysaddr
features = set() features = set()
for f in caps.host.cpu.features: for f in caps.host.cpu.features:
features.add(f.name) features.add(f.name)

View File

@ -0,0 +1,6 @@
---
features:
- |
Added new flavor extra_specs and image properties to control the physical
address bits of vCPUs in Libvirt guests. This option is used to boot a
guest with large RAM.