From: Sean McGinnis Date: Tue, 4 Nov 2014 11:15:26 +0000 (+0100) Subject: Add ability to create volume from image by image name. X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=01ac8436761d5d1297cfd2555e573c1bfd576e72;p=openstack-build%2Fcinder-build.git Add ability to create volume from image by image name. Some cli commands allow specifying an image by either its name or its ID. Creating a volume from an image currently requires knowing the image ID. The request was made to add the ability to create a volume using the image name instead. This patch adds the ability to accept either name or ID. It will attempt to locate the image based on this input and fail if neither can be matched. If found by name, it will also verify there are not multiple images of the same name. If this is the case it will require using the ID to be explicit. Will update python-cinderclient in a separate patch once this has been merged to allow ID or name. Change-Id: I8b522ccff3ead644b738499e67fa85d4ab92af36 Partial-Bug: #1377823 --- diff --git a/cinder/api/v2/volumes.py b/cinder/api/v2/volumes.py index aaa7b1fe0..0f30eea02 100644 --- a/cinder/api/v2/volumes.py +++ b/cinder/api/v2/volumes.py @@ -28,6 +28,7 @@ from cinder.api import xmlutil from cinder import consistencygroup as consistencygroupAPI from cinder import exception from cinder.i18n import _, _LI +from cinder.image import glance from cinder.openstack.common import log as logging from cinder.openstack.common import uuidutils from cinder import utils @@ -250,20 +251,45 @@ class VolumeController(wsgi.Controller): volumes = self._view_builder.summary_list(req, limited_list) return volumes - def _image_uuid_from_href(self, image_href): - # If the image href was generated by nova api, strip image_href + def _image_uuid_from_ref(self, image_ref, context): + # If the image ref was generated by nova api, strip image_ref # down to an id. + image_uuid = None try: - image_uuid = image_href.split('/').pop() - except (TypeError, AttributeError): + image_uuid = image_ref.split('/').pop() + except AttributeError: msg = _("Invalid imageRef provided.") raise exc.HTTPBadRequest(explanation=msg) - if not uuidutils.is_uuid_like(image_uuid): - msg = _("Invalid imageRef provided.") - raise exc.HTTPBadRequest(explanation=msg) + image_service = glance.get_default_image_service() - return image_uuid + # First see if this is an actual image ID + if uuidutils.is_uuid_like(image_uuid): + try: + image = image_service.show(context, image_uuid) + if 'id' in image: + return image['id'] + except Exception: + # Pass and see if there is a matching image name + pass + + # Could not find by ID, check if it is an image name + try: + params = {'filters': {'name': image_ref}} + images = list(image_service.detail(context, **params)) + if len(images) > 1: + msg = _("Multiple matches found for '%s', use an ID to be more" + " specific.") % image_ref + raise exc.HTTPConflict(msg) + for img in images: + return img['id'] + except Exception: + # Pass and let default not found error handling take care of it + pass + + msg = _("Invalid image identifier or unable to " + "access requested image.") + raise exc.HTTPBadRequest(explanation=msg) @wsgi.response(202) @wsgi.serializers(xml=VolumeTemplate) @@ -375,9 +401,9 @@ class VolumeController(wsgi.Controller): LOG.info(_LI("Create volume of %s GB"), size, context=context) if self.ext_mgr.is_loaded('os-image-create'): - image_href = volume.get('imageRef') - if image_href is not None: - image_uuid = self._image_uuid_from_href(image_href) + image_ref = volume.get('imageRef') + if image_ref is not None: + image_uuid = self._image_uuid_from_ref(image_ref, context) kwargs['image_id'] = image_uuid kwargs['availability_zone'] = volume.get('availability_zone', None) diff --git a/cinder/tests/api/v2/stubs.py b/cinder/tests/api/v2/stubs.py index 242ac7242..f0c1e36a4 100644 --- a/cinder/tests/api/v2/stubs.py +++ b/cinder/tests/api/v2/stubs.py @@ -79,6 +79,16 @@ def stub_volume_create(self, context, size, name, description, snapshot, return vol +def stub_image_service_detail(self, context, **kwargs): + filters = kwargs.get('filters', {'name': ''}) + if filters['name'] == "Fedora-x86_64-20-20140618-sda": + return [{'id': "c905cedb-7281-47e4-8a62-f26bc5fc4c77"}] + elif filters['name'] == "multi": + return [{'id': "c905cedb-7281-47e4-8a62-f26bc5fc4c77"}, + {'id': "c905cedb-abcd-47e4-8a62-f26bc5fc4c77"}] + return [] + + def stub_volume_create_from_image(self, context, size, name, description, snapshot, volume_type, metadata, availability_zone): diff --git a/cinder/tests/api/v2/test_volumes.py b/cinder/tests/api/v2/test_volumes.py index 490245a93..96aa90d48 100644 --- a/cinder/tests/api/v2/test_volumes.py +++ b/cinder/tests/api/v2/test_volumes.py @@ -392,6 +392,93 @@ class VolumeApiTest(test.TestCase): req, body) + def test_volume_create_with_image_name(self): + self.stubs.Set(db, 'volume_get', stubs.stub_volume_get_db) + self.stubs.Set(volume_api.API, "create", stubs.stub_volume_create) + self.stubs.Set(fake_image._FakeImageService, + "detail", + stubs.stub_image_service_detail) + + test_id = "Fedora-x86_64-20-20140618-sda" + self.ext_mgr.extensions = {'os-image-create': 'fake'} + vol = {"size": '1', + "name": "Volume Test Name", + "description": "Volume Test Desc", + "availability_zone": "nova", + "imageRef": test_id} + ex = {'volume': {'attachments': [{'device': '/', + 'host_name': None, + 'id': '1', + 'server_id': 'fakeuuid', + 'volume_id': '1'}], + 'availability_zone': 'nova', + 'bootable': 'false', + 'consistencygroup_id': None, + 'created_at': datetime.datetime(1900, 1, 1, 1, 1, 1), + 'description': 'Volume Test Desc', + 'encrypted': False, + 'id': '1', + 'links': + [{'href': 'http://localhost/v2/fakeproject/volumes/1', + 'rel': 'self'}, + {'href': 'http://localhost/fakeproject/volumes/1', + 'rel': 'bookmark'}], + 'metadata': {}, + 'name': 'Volume Test Name', + 'replication_status': 'disabled', + 'size': '1', + 'snapshot_id': None, + 'source_volid': None, + 'status': 'fakestatus', + 'user_id': 'fakeuser', + 'volume_type': 'vol_type_name'}} + body = {"volume": vol} + req = fakes.HTTPRequest.blank('/v2/volumes') + res_dict = self.controller.create(req, body) + self.assertEqual(ex, res_dict) + + def test_volume_create_with_image_name_has_multiple(self): + self.stubs.Set(db, 'volume_get', stubs.stub_volume_get_db) + self.stubs.Set(volume_api.API, "create", stubs.stub_volume_create) + self.stubs.Set(fake_image._FakeImageService, + "detail", + stubs.stub_image_service_detail) + + test_id = "multi" + self.ext_mgr.extensions = {'os-image-create': 'fake'} + vol = {"size": '1', + "name": "Volume Test Name", + "description": "Volume Test Desc", + "availability_zone": "nova", + "imageRef": test_id} + body = {"volume": vol} + req = fakes.HTTPRequest.blank('/v2/volumes') + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.create, + req, + body) + + def test_volume_create_with_image_name_no_match(self): + self.stubs.Set(db, 'volume_get', stubs.stub_volume_get_db) + self.stubs.Set(volume_api.API, "create", stubs.stub_volume_create) + self.stubs.Set(fake_image._FakeImageService, + "detail", + stubs.stub_image_service_detail) + + test_id = "MissingName" + self.ext_mgr.extensions = {'os-image-create': 'fake'} + vol = {"size": '1', + "name": "Volume Test Name", + "description": "Volume Test Desc", + "availability_zone": "nova", + "imageRef": test_id} + body = {"volume": vol} + req = fakes.HTTPRequest.blank('/v2/volumes') + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.create, + req, + body) + def test_volume_update(self): self.stubs.Set(volume_api.API, 'get', stubs.stub_volume_get) self.stubs.Set(volume_api.API, "update", stubs.stub_volume_update)