From 88f8b83004c49c1fdb042d35e2907b7798648bdd Mon Sep 17 00:00:00 2001 From: Liang Chen Date: Fri, 7 Jun 2013 10:12:30 +0800 Subject: [PATCH] allow using image uuid for instance resource make the instance resource accept image uuid for its ImageId properties in addition to unique image name. Fixes bug #1186410 Change-Id: Iae7705e73fe223d80e85d261a04f88f8c5207487 --- heat/common/exception.py | 4 ++ heat/engine/resources/instance.py | 47 +++++++++--- heat/tests/test_engine_service.py | 2 +- heat/tests/test_instance.py | 114 +++++++++++++++++++++++++++++- 4 files changed, 154 insertions(+), 13 deletions(-) diff --git a/heat/common/exception.py b/heat/common/exception.py index 419685e1..17418367 100644 --- a/heat/common/exception.py +++ b/heat/common/exception.py @@ -213,6 +213,10 @@ class ImageNotFound(OpenstackException): message = _("The Image (%(image_name)s) could not be found.") +class NoUniqueImageFound(OpenstackException): + message = _("Multiple images were found with name (%(image_name)s).") + + class InvalidTenant(OpenstackException): message = _("Searching Tenant %(target)s " "from Tenant %(actual)s forbidden.") diff --git a/heat/engine/resources/instance.py b/heat/engine/resources/instance.py index 31ba372c..4801c786 100644 --- a/heat/engine/resources/instance.py +++ b/heat/engine/resources/instance.py @@ -31,6 +31,7 @@ from heat.common import exception from heat.engine.resources.network_interface import NetworkInterface from heat.openstack.common import log as logging +from heat.openstack.common import uuidutils logger = logging.getLogger(__name__) @@ -293,16 +294,8 @@ class Instance(resource.Resource): raise exception.UserKeyPairMissing(key_name=key_name) image_name = self.properties['ImageId'] - image_id = None - image_list = self.nova().images.list() - for o in image_list: - if o.name == image_name: - image_id = o.id - break - if image_id is None: - logger.info("Image %s was not found in glance" % image_name) - raise exception.ImageNotFound(image_name=image_name) + image_id = self._get_image_id(image_name) flavor_id = None flavor_list = self.nova().flavors.list() @@ -424,6 +417,17 @@ class Instance(resource.Resource): 'Cannot define both SecurityGroups/SecurityGroupIds and ' 'NetworkInterfaces properties.'} + # make sure the image exists. + image_identifier = self.properties['ImageId'] + try: + self._get_image_id(image_identifier) + except exception.ImageNotFound: + return {'Error': 'Image %s was not found in glance' % + image_identifier} + except exception.NoUniqueImageFound: + return {'Error': 'Multiple images were found with name %s' % + image_identifier} + return def _delete_server(self, server): @@ -464,6 +468,31 @@ class Instance(resource.Resource): self.resource_id = None + def _get_image_id(self, image_identifier): + image_id = None + if uuidutils.is_uuid_like(image_identifier): + try: + image_id = self.nova().images.get(image_identifier).id + except clients.novaclient.exceptions.NotFound: + logger.info("Image %s was not found in glance" + % image_identifier) + raise exception.ImageNotFound(image_name=image_identifier) + else: + image_list = self.nova().images.list() + image_names = dict( + (o.id, o.name) + for o in image_list if o.name == image_identifier) + if len(image_names) == 0: + logger.info("Image %s was not found in glance" % + image_identifier) + raise exception.ImageNotFound(image_name=image_identifier) + elif len(image_names) > 1: + logger.info("Mulitple images %s were found in glance with name" + % image_identifier) + raise exception.NoUniqueImageFound(image_name=image_identifier) + image_id = image_names.popitem()[0] + return image_id + def resource_mapping(): return { diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index 3d51cd26..c0a913c5 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -331,7 +331,7 @@ class stackServiceCreateUpdateDeleteTest(HeatTestCase): resource.properties = Properties( resource.properties_schema, { - 'ImageId': 'foo', + 'ImageId': 'CentOS 5.2', 'KeyName': 'test', 'InstanceType': 'm1.large' }) diff --git a/heat/tests/test_instance.py b/heat/tests/test_instance.py index c9aacecb..9fc7e6c6 100644 --- a/heat/tests/test_instance.py +++ b/heat/tests/test_instance.py @@ -19,6 +19,7 @@ import mox from heat.tests.v1_1 import fakes from heat.engine.resources import instance as instances +from heat.common import exception from heat.common import template_format from heat.engine import parser from heat.engine import resource @@ -60,15 +61,20 @@ class instancesTest(HeatTestCase): self.fc = fakes.FakeClient() setup_dummy_db() - def _setup_test_instance(self, return_server, name): - stack_name = '%s_stack' % name + def _setup_test_stack(self, stack_name): t = template_format.parse(wp_template) template = parser.Template(t) params = parser.Parameters(stack_name, template, {'KeyName': 'test'}) stack = parser.Stack(None, stack_name, template, params, stack_id=uuidutils.generate_uuid()) + return (t, stack) - t['Resources']['WebServer']['Properties']['ImageId'] = 'CentOS 5.2' + def _setup_test_instance(self, return_server, name, image_id=None): + stack_name = '%s_stack' % name + (t, stack) = self._setup_test_stack(stack_name) + + t['Resources']['WebServer']['Properties']['ImageId'] = \ + image_id or 'CentOS 5.2' t['Resources']['WebServer']['Properties']['InstanceType'] = \ '256 MB Server' instance = instances.Instance('%s_name' % name, @@ -114,6 +120,108 @@ class instancesTest(HeatTestCase): self.m.VerifyAll() + def test_instance_create_with_image_id(self): + return_server = self.fc.servers.list()[1] + instance = self._setup_test_instance(return_server, + 'test_instance_create_image_id', + image_id='1') + self.m.StubOutWithMock(uuidutils, "is_uuid_like") + uuidutils.is_uuid_like('1').AndReturn(True) + + self.m.ReplayAll() + scheduler.TaskRunner(instance.create)() + + # this makes sure the auto increment worked on instance creation + self.assertTrue(instance.id > 0) + + expected_ip = return_server.networks['public'][0] + self.assertEqual(instance.FnGetAtt('PublicIp'), expected_ip) + self.assertEqual(instance.FnGetAtt('PrivateIp'), expected_ip) + self.assertEqual(instance.FnGetAtt('PrivateDnsName'), expected_ip) + self.assertEqual(instance.FnGetAtt('PrivateDnsName'), expected_ip) + + self.m.VerifyAll() + + def test_instance_create_image_name_err(self): + stack_name = 'test_instance_create_image_name_err_stack' + (t, stack) = self._setup_test_stack(stack_name) + + # create an instance with non exist image name + t['Resources']['WebServer']['Properties']['ImageId'] = 'Slackware' + instance = instances.Instance('instance_create_image_err', + t['Resources']['WebServer'], stack) + + self.m.StubOutWithMock(instance, 'nova') + instance.nova().MultipleTimes().AndReturn(self.fc) + self.m.ReplayAll() + + self.assertRaises(exception.ImageNotFound, instance.handle_create) + + self.m.VerifyAll() + + def test_instance_create_duplicate_image_name_err(self): + stack_name = 'test_instance_create_image_name_err_stack' + (t, stack) = self._setup_test_stack(stack_name) + + # create an instance with a non unique image name + t['Resources']['WebServer']['Properties']['ImageId'] = 'CentOS 5.2' + instance = instances.Instance('instance_create_image_err', + t['Resources']['WebServer'], stack) + + self.m.StubOutWithMock(instance, 'nova') + instance.nova().MultipleTimes().AndReturn(self.fc) + self.m.StubOutWithMock(self.fc.client, "get_images_detail") + self.fc.client.get_images_detail().AndReturn(( + 200, {'images': [{'id': 1, 'name': 'CentOS 5.2'}, + {'id': 4, 'name': 'CentOS 5.2'}]})) + self.m.ReplayAll() + + self.assertRaises(exception.NoUniqueImageFound, instance.handle_create) + + self.m.VerifyAll() + + def test_instance_create_image_id_err(self): + stack_name = 'test_instance_create_image_id_err_stack' + (t, stack) = self._setup_test_stack(stack_name) + + # create an instance with non exist image Id + t['Resources']['WebServer']['Properties']['ImageId'] = '1' + instance = instances.Instance('instance_create_image_err', + t['Resources']['WebServer'], stack) + + self.m.StubOutWithMock(instance, 'nova') + instance.nova().MultipleTimes().AndReturn(self.fc) + self.m.StubOutWithMock(uuidutils, "is_uuid_like") + uuidutils.is_uuid_like('1').AndReturn(True) + self.m.StubOutWithMock(self.fc.client, "get_images_1") + self.fc.client.get_images_1().AndRaise( + instances.clients.novaclient.exceptions.NotFound(404)) + self.m.ReplayAll() + + self.assertRaises(exception.ImageNotFound, instance.handle_create) + + self.m.VerifyAll() + + def test_instance_validate(self): + stack_name = 'test_instance_validate_stack' + (t, stack) = self._setup_test_stack(stack_name) + + # create an instance with non exist image Id + t['Resources']['WebServer']['Properties']['ImageId'] = '1' + instance = instances.Instance('instance_create_image_err', + t['Resources']['WebServer'], stack) + + self.m.StubOutWithMock(instance, 'nova') + instance.nova().MultipleTimes().AndReturn(self.fc) + + self.m.StubOutWithMock(uuidutils, "is_uuid_like") + uuidutils.is_uuid_like('1').AndReturn(True) + self.m.ReplayAll() + + self.assertEqual(instance.validate(), None) + + self.m.VerifyAll() + def test_instance_create_delete(self): return_server = self.fc.servers.list()[1] instance = self._create_test_instance(return_server, -- 2.45.2