Update ImageMetaProp object to expose traits
The image meta data object now parses the un-numbered traits fields defined as part of image properties. These traits are intended to be passed to the scheduler -> placement api to pick the resources matching the traits. Added unit tests for the same. Change-Id: I86872e482d9a646204e4c3a3e5126fea474b574b Implements: blueprint glance-image-traits
This commit is contained in:
parent
c5fd4c7612
commit
124a7d8cc6
|
@ -15,6 +15,7 @@
|
|||
import copy
|
||||
|
||||
from oslo_utils import versionutils
|
||||
import six
|
||||
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
|
@ -168,12 +169,15 @@ class ImageMetaProps(base.NovaObject):
|
|||
# Version 1.17: Add lan9118 as valid nic for hw_vif_model property for qemu
|
||||
# Version 1.18: Pull signature properties from cursive library
|
||||
# Version 1.19: Added 'img_hide_hypervisor_id' type field
|
||||
VERSION = '1.19'
|
||||
# Version 1.20: Added 'traits_required' list field
|
||||
VERSION = '1.20'
|
||||
|
||||
def obj_make_compatible(self, primitive, target_version):
|
||||
super(ImageMetaProps, self).obj_make_compatible(primitive,
|
||||
target_version)
|
||||
target_version = versionutils.convert_version_to_tuple(target_version)
|
||||
if target_version < (1, 20):
|
||||
primitive.pop('traits_required', None)
|
||||
if target_version < (1, 19):
|
||||
primitive.pop('img_hide_hypervisor_id', None)
|
||||
if target_version < (1, 16) and 'hw_watchdog_action' in primitive:
|
||||
|
@ -221,6 +225,7 @@ class ImageMetaProps(base.NovaObject):
|
|||
# 'hw_' - settings affecting the guest virtual machine hardware
|
||||
# 'img_' - settings affecting the use of images by the compute node
|
||||
# 'os_' - settings affecting the guest operating system setup
|
||||
# 'traits_required' - The required traits associated with the image
|
||||
|
||||
fields = {
|
||||
# name of guest hardware architecture eg i686, x86_64, ppc64
|
||||
|
@ -457,6 +462,13 @@ class ImageMetaProps(base.NovaObject):
|
|||
# is a fairly generic type. For a detailed type consider os_distro
|
||||
# instead
|
||||
'os_type': fields.OSTypeField(),
|
||||
|
||||
# The required traits associated with the image. Traits are expected to
|
||||
# be defined as starting with `trait:` like below:
|
||||
# trait:HW_CPU_X86_AVX2=required
|
||||
# for trait in image_meta.traits_required:
|
||||
# will yield trait strings such as 'HW_CPU_X86_AVX2'
|
||||
'traits_required': fields.ListOfStringsField(),
|
||||
}
|
||||
|
||||
# The keys are the legacy property names and
|
||||
|
@ -543,16 +555,26 @@ class ImageMetaProps(base.NovaObject):
|
|||
elif key == "hw_numa_cpus":
|
||||
self._set_numa_cpus(image_props)
|
||||
else:
|
||||
if key not in image_props:
|
||||
# traits_required will be populated by
|
||||
# _set_attr_from_trait_names
|
||||
if key not in image_props or key == "traits_required":
|
||||
continue
|
||||
|
||||
setattr(self, key, image_props[key])
|
||||
|
||||
def _set_attr_from_trait_names(self, image_props):
|
||||
for trait in [six.text_type(k[6:]) for k, v in image_props.items()
|
||||
if six.text_type(k).startswith("trait:")
|
||||
and six.text_type(v) == six.text_type('required')]:
|
||||
if 'traits_required' not in self:
|
||||
self.traits_required = []
|
||||
self.traits_required.append(trait)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, image_props):
|
||||
"""Create instance from image properties dict
|
||||
|
||||
:param image_props: dictionary of image metdata properties
|
||||
:param image_props: dictionary of image metadata properties
|
||||
|
||||
Creates a new object instance, initializing from a
|
||||
dictionary of image metadata properties
|
||||
|
@ -567,6 +589,8 @@ class ImageMetaProps(base.NovaObject):
|
|||
# associated with the current name takes priority
|
||||
obj._set_attr_from_legacy_names(image_props)
|
||||
obj._set_attr_from_current_names(image_props)
|
||||
obj._set_attr_from_trait_names(image_props)
|
||||
|
||||
return obj
|
||||
|
||||
def get(self, name, defvalue=None):
|
||||
|
|
|
@ -108,6 +108,7 @@ class TestImageMetaProps(test.NoDBTestCase):
|
|||
'hw_video_model': 'vga',
|
||||
'hw_video_ram': '512',
|
||||
'hw_qemu_guest_agent': 'yes',
|
||||
'trait:CUSTOM_TRUSTED': 'required',
|
||||
# Fill sane values for the rest here
|
||||
}
|
||||
virtprops = objects.ImageMetaProps.from_dict(props)
|
||||
|
@ -115,6 +116,8 @@ class TestImageMetaProps(test.NoDBTestCase):
|
|||
self.assertEqual('vga', virtprops.hw_video_model)
|
||||
self.assertEqual(512, virtprops.hw_video_ram)
|
||||
self.assertTrue(virtprops.hw_qemu_guest_agent)
|
||||
self.assertIsNotNone(virtprops.traits_required)
|
||||
self.assertIn('CUSTOM_TRUSTED', virtprops.traits_required)
|
||||
|
||||
def test_default_props(self):
|
||||
props = {}
|
||||
|
@ -128,6 +131,7 @@ class TestImageMetaProps(test.NoDBTestCase):
|
|||
virtprops = objects.ImageMetaProps.from_dict(props)
|
||||
|
||||
self.assertEqual("hvm", virtprops.get("hw_vm_mode", "hvm"))
|
||||
self.assertIsNone(virtprops.get("traits_required"))
|
||||
|
||||
def test_non_existent_prop(self):
|
||||
props = {}
|
||||
|
@ -281,6 +285,43 @@ class TestImageMetaProps(test.NoDBTestCase):
|
|||
self.assertEqual([set([0, 1, 2, 3])],
|
||||
virtprops.hw_numa_cpus)
|
||||
|
||||
def test_get_unnumbered_trait_fields(self):
|
||||
"""Tests that only valid un-numbered required traits are parsed from
|
||||
the properties.
|
||||
"""
|
||||
props = {'trait:HW_CPU_X86_AVX2': 'required',
|
||||
'trait:CUSTOM_TRUSTED': 'required',
|
||||
'trait1:CUSTOM_FPGA': 'required',
|
||||
'trai:CUSTOM_FOO': 'required',
|
||||
'trait:CUSTOM_XYZ': 'xyz'}
|
||||
|
||||
virtprops = objects.ImageMetaProps.from_dict(props)
|
||||
|
||||
self.assertIn('CUSTOM_TRUSTED', virtprops.traits_required)
|
||||
self.assertIn('HW_CPU_X86_AVX2', virtprops.traits_required)
|
||||
|
||||
# numbered traits are ignored
|
||||
self.assertNotIn('CUSTOM_FPGA', virtprops.traits_required)
|
||||
# property key does not start with `trait:` exactly
|
||||
self.assertNotIn('CUSTOM_FOO', virtprops.traits_required)
|
||||
# property value is not required
|
||||
self.assertNotIn('CUSTOM_XYZ', virtprops.traits_required)
|
||||
|
||||
def test_traits_required_initialized_as_list(self):
|
||||
"""Tests that traits_required field is set as a list even if the same
|
||||
property is set on the image metadata.
|
||||
"""
|
||||
props = {'trait:HW_CPU_X86_AVX2': 'required',
|
||||
'trait:CUSTOM_TRUSTED': 'required',
|
||||
'traits_required': 'foo'}
|
||||
|
||||
virtprops = objects.ImageMetaProps.from_dict(props)
|
||||
|
||||
self.assertIsInstance(virtprops.traits_required, list)
|
||||
self.assertIn('CUSTOM_TRUSTED', virtprops.traits_required)
|
||||
self.assertIn('HW_CPU_X86_AVX2', virtprops.traits_required)
|
||||
self.assertEqual(2, len(virtprops.traits_required))
|
||||
|
||||
def test_obj_make_compatible(self):
|
||||
props = {
|
||||
'hw_firmware_type': 'uefi',
|
||||
|
@ -330,3 +371,9 @@ class TestImageMetaProps(test.NoDBTestCase):
|
|||
primitive = obj.obj_to_primitive('1.0')
|
||||
self.assertNotIn('img_hide_hypervisor_id',
|
||||
primitive['nova_object.data'])
|
||||
|
||||
def test_obj_make_compatible_trait_fields(self):
|
||||
"""Tests that checks if we pop traits_required."""
|
||||
obj = objects.ImageMetaProps(traits_required=['CUSTOM_TRUSTED'])
|
||||
primitive = obj.obj_to_primitive('1.19')
|
||||
self.assertNotIn('traits_required', primitive['nova_object.data'])
|
||||
|
|
|
@ -1094,7 +1094,7 @@ object_data = {
|
|||
'HVSpec': '1.2-de06bcec472a2f04966b855a49c46b41',
|
||||
'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502',
|
||||
'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d',
|
||||
'ImageMetaProps': '1.19-dc9581ff2b80d8c33462889916b82df0',
|
||||
'ImageMetaProps': '1.20-ffd686cde289814695d5f89522aa5aef',
|
||||
'Instance': '2.4-4437eb8b2737c3054ea579b8efe31dc5',
|
||||
'InstanceAction': '1.1-f9f293e526b66fca0d05c3b3a2d13914',
|
||||
'InstanceActionEvent': '1.1-e56a64fa4710e43ef7af2ad9d6028b33',
|
||||
|
|
Loading…
Reference in New Issue