Add methods to get image metadata from instance

This patch adds a couple of utility functions that enclose all the logic
for getting and parsing the image metadata stored in the instance's
system metadata.

First, this will try to fetch the metadata from the real image and will
prevent it from failing if it is not available. It will be then merged
with the image metadata stored during the instance creation.

Related to bug #1039662

Change-Id: I2130caf19858585571b1199e27f0a98ad5f08701
This commit is contained in:
Xavier Queralt 2013-08-26 22:53:03 +02:00
parent 42efb509d0
commit 8e575be75c
4 changed files with 205 additions and 0 deletions

View File

@ -225,6 +225,29 @@ def _get_unused_letter(used_letters):
return letters[0]
def get_image_metadata(context, image_service, image_id, instance):
# If the base image is still available, get its metadata
try:
image = image_service.show(context, image_id)
except Exception as e:
LOG.warning(_("Can't access image %(image_id)s: %(error)s"),
{"image_id": image_id, "error": e}, instance=instance)
image_system_meta = {}
else:
instance_type = flavors.extract_flavor(instance)
image_system_meta = utils.get_system_metadata_from_image(
image, instance_type)
# Get the system metadata from the instance
system_meta = utils.instance_sys_meta(instance)
# Merge the metadata from the instance with the image's, if any
system_meta.update(image_system_meta)
# Convert the system metadata to image metadata
return utils.get_image_from_system_metadata(system_meta)
def notify_usage_exists(notifier, context, instance_ref, current_period=False,
ignore_missing_network_data=True,
system_metadata=None, extra_usage_info=None):

View File

@ -31,9 +31,11 @@ from nova import exception
from nova.image import glance
from nova.network import api as network_api
from nova import notifier as notify
from nova.objects import instance as instance_obj
from nova.openstack.common import importutils
from nova.openstack.common import jsonutils
from nova import test
from nova.tests import fake_instance
from nova.tests import fake_instance_actions
from nova.tests import fake_network
from nova.tests import fake_notifier
@ -589,3 +591,98 @@ class UsageInfoTestCase(test.TestCase):
"create.start",
aggregate_payload)
self.assertEquals(len(fake_notifier.NOTIFICATIONS), 0)
class ComputeGetImageMetadataTestCase(test.TestCase):
def setUp(self):
super(ComputeGetImageMetadataTestCase, self).setUp()
self.context = context.RequestContext('fake', 'fake')
self.image = {
"min_ram": 10,
"min_disk": 1,
"disk_format": "raw",
"container_format": "bare",
"properties": {},
}
self.image_service = nova.tests.image.fake._FakeImageService()
self.stubs.Set(self.image_service, 'show', self._fake_show)
self.ctx = context.RequestContext('fake', 'fake')
sys_meta = {
'image_min_ram': 10,
'image_min_disk': 1,
'image_disk_format': 'raw',
'image_container_format': 'bare',
'instance_type_id': 0,
'instance_type_name': 'm1.fake',
'instance_type_memory_mb': 10,
'instance_type_vcpus': 1,
'instance_type_root_gb': 1,
'instance_type_ephemeral_gb': 1,
'instance_type_flavorid': '0',
'instance_type_swap': 1,
'instance_type_rxtx_factor': 0.0,
'instance_type_vcpu_weight': None,
}
self.instance = fake_instance.fake_db_instance(
memory_mb=0, root_gb=0,
system_metadata=sys_meta)
@property
def instance_obj(self):
return instance_obj.Instance._from_db_object(
self.ctx, instance_obj.Instance(), self.instance,
expected_attrs=instance_obj.INSTANCE_DEFAULT_FIELDS)
def _fake_show(self, ctx, image_id):
return self.image
def test_get_image_meta(self):
image_meta = compute_utils.get_image_metadata(
self.ctx, self.image_service, 'fake-image', self.instance_obj)
self.image['properties'] = 'DONTCARE'
self.assertThat(self.image, matchers.DictMatches(image_meta))
def test_get_image_meta_no_image(self):
def fake_show(ctx, image_id):
raise exception.ImageNotFound(image_id='fake-image')
self.stubs.Set(self.image_service, 'show', fake_show)
image_meta = compute_utils.get_image_metadata(
self.ctx, self.image_service, 'fake-image', self.instance_obj)
self.image['properties'] = 'DONTCARE'
self.assertThat(self.image, matchers.DictMatches(image_meta))
def test_get_image_meta_no_image_system_meta(self):
for k in self.instance['system_metadata'].keys():
if k.startswith('image_'):
del self.instance['system_metadata'][k]
image_meta = compute_utils.get_image_metadata(
self.ctx, self.image_service, 'fake-image', self.instance_obj)
self.image['properties'] = 'DONTCARE'
self.assertThat(self.image, matchers.DictMatches(image_meta))
def test_get_image_meta_no_image_no_image_system_meta(self):
def fake_show(ctx, image_id):
raise exception.ImageNotFound(image_id='fake-image')
self.stubs.Set(self.image_service, 'show', fake_show)
for k in self.instance['system_metadata'].keys():
if k.startswith('image_'):
del self.instance['system_metadata'][k]
image_meta = compute_utils.get_image_metadata(
self.ctx, self.image_service, 'fake-image', self.instance_obj)
expected = {'properties': 'DONTCARE'}
self.assertThat(expected, matchers.DictMatches(image_meta))

View File

@ -1028,3 +1028,58 @@ class GetSystemMetadataFromImageTestCase(test.TestCase):
for key in utils.SM_INHERITABLE_KEYS:
sys_key = "%s%s" % (utils.SM_IMAGE_PROP_PREFIX, key)
self.assertTrue(sys_key not in sys_meta)
class GetImageFromSystemMetadataTestCase(test.TestCase):
def get_system_metadata(self):
sys_meta = {
"image_min_ram": 1,
"image_min_disk": 1,
"image_disk_format": "raw",
"image_container_format": "bare",
}
return sys_meta
def test_image_from_system_metadata(self):
sys_meta = self.get_system_metadata()
sys_meta["%soo1" % utils.SM_IMAGE_PROP_PREFIX] = "bar"
sys_meta["%soo2" % utils.SM_IMAGE_PROP_PREFIX] = "baz"
image = utils.get_image_from_system_metadata(sys_meta)
# Verify that we inherit all the needed keys
for key in utils.SM_INHERITABLE_KEYS:
sys_key = "%s%s" % (utils.SM_IMAGE_PROP_PREFIX, key)
self.assertEqual(image[key], sys_meta.get(sys_key))
# Verify that we inherit the rest of metadata as properties
self.assertTrue("properties" in image)
for key, value in image["properties"].iteritems():
sys_key = "%s%s" % (utils.SM_IMAGE_PROP_PREFIX, key)
self.assertEqual(image["properties"][key], sys_meta[sys_key])
def test_dont_inherit_empty_values(self):
sys_meta = self.get_system_metadata()
for key in utils.SM_INHERITABLE_KEYS:
sys_key = "%s%s" % (utils.SM_IMAGE_PROP_PREFIX, key)
sys_meta[sys_key] = None
image = utils.get_image_from_system_metadata(sys_meta)
# Verify that the empty properties have not been inherited
for key in utils.SM_INHERITABLE_KEYS:
self.assertTrue(key not in image)
def test_non_inheritable_image_properties(self):
sys_meta = self.get_system_metadata()
sys_meta["%soo1" % utils.SM_IMAGE_PROP_PREFIX] = "bar"
CONF.non_inheritable_image_properties = ["foo1"]
image = utils.get_image_from_system_metadata(sys_meta)
# Verify that the foo1 key has not been inherited
self.assertTrue("foo1" not in image)

View File

@ -1255,3 +1255,33 @@ def get_system_metadata_from_image(image_meta, instance_type=None):
system_meta[prefix_format % key] = value
return system_meta
def get_image_from_system_metadata(system_meta):
image_meta = {}
properties = {}
if not isinstance(system_meta, dict):
system_meta = metadata_to_dict(system_meta)
for key, value in system_meta.iteritems():
if value is None:
continue
# NOTE(xqueralt): Not sure this has to inherit all the properties or
# just the ones we need. Leaving it for now to keep the old behaviour.
if key.startswith(SM_IMAGE_PROP_PREFIX):
key = key[len(SM_IMAGE_PROP_PREFIX):]
if key in SM_INHERITABLE_KEYS:
image_meta[key] = value
else:
# Skip properties that are non-inheritable
if key in CONF.non_inheritable_image_properties:
continue
properties[key] = value
if properties:
image_meta['properties'] = properties
return image_meta