]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add ability to create volume from image by image name.
authorSean McGinnis <sean_mcginnis@dell.com>
Tue, 4 Nov 2014 11:15:26 +0000 (12:15 +0100)
committerSean McGinnis <sean_mcginnis@dell.com>
Mon, 24 Nov 2014 15:33:19 +0000 (09:33 -0600)
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

cinder/api/v2/volumes.py
cinder/tests/api/v2/stubs.py
cinder/tests/api/v2/test_volumes.py

index aaa7b1fe09ede6ac52b221ea14c42742821a1d66..0f30eea02ac40383da78743ab2d2a73823c3b4bd 100644 (file)
@@ -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)
index 242ac72425bbb7244c912309f46a6ecea80318dc..f0c1e36a4198950fc224f0a2d93fdec5c2063af4 100644 (file)
@@ -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):
index 490245a93a798b713de3baf943410cc4b7505bf6..96aa90d48065e92ab8fafe8ba0221348f6a03d57 100644 (file)
@@ -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)