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:
arvindn05 2018-02-27 21:56:31 -08:00 committed by Arvind Nadendla
parent c5fd4c7612
commit 124a7d8cc6
3 changed files with 75 additions and 4 deletions

View File

@ -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):

View File

@ -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'])

View File

@ -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',