Support create a volume from image snapshot
The instance boot from volume, snapshot the instance will create a image in glance. The image size is 0, it just a link to the volume snapshot. When the user create volume from the snapshot image, in the current version, it will create a blank volume. so the user is troubled. This patch fix it. The scheme: when the user create volume from snapshot image, the api create interface will parse out the snapshot image corresponds to the volume snapshot id. Then create a volume from the volume snapshot. So, the user can use this way to restore the instance's bootable volume. Change-Id: Ic44dfae8bb187a5aa9c2c1f50f7b5dc0ef0f28f8 Closes-Bug: #1560867
This commit is contained in:
parent
80558687d0
commit
25dd8109df
@ -129,6 +129,8 @@ NEW_ATTACH_COMPLETION = '3.44'
|
||||
|
||||
SUPPORT_COUNT_INFO = '3.45'
|
||||
|
||||
SUPPORT_NOVA_IMAGE = '3.46'
|
||||
|
||||
|
||||
def get_mv_header(version):
|
||||
"""Gets a formatted HTTP microversion header.
|
||||
|
@ -109,6 +109,7 @@ REST_API_VERSION_HISTORY = """
|
||||
* 3.44 - Add attachment-complete.
|
||||
* 3.45 - Add ``count`` field to volume, backup and snapshot list and
|
||||
detail APIs.
|
||||
* 3.46 - Support create volume by Nova specific image (0 size image).
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
@ -116,7 +117,7 @@ REST_API_VERSION_HISTORY = """
|
||||
# minimum version of the API supported.
|
||||
# Explicitly using /v2 endpoints will still work
|
||||
_MIN_API_VERSION = "3.0"
|
||||
_MAX_API_VERSION = "3.45"
|
||||
_MAX_API_VERSION = "3.46"
|
||||
_LEGACY_API_VERSION2 = "2.0"
|
||||
UPDATED = "2017-09-19T20:18:14Z"
|
||||
|
||||
|
@ -377,3 +377,7 @@ user documentation.
|
||||
3.45
|
||||
----
|
||||
Add ``count`` field to volume, backup and snapshot list and detail APIs.
|
||||
|
||||
3.46
|
||||
----
|
||||
Support create volume by Nova specific image (0 size image).
|
||||
|
@ -28,6 +28,7 @@ from cinder.api.v3.views import volumes as volume_views_v3
|
||||
from cinder import exception
|
||||
from cinder import group as group_api
|
||||
from cinder.i18n import _
|
||||
from cinder.image import glance
|
||||
from cinder import objects
|
||||
from cinder.policies import volumes as policy
|
||||
from cinder import utils
|
||||
@ -193,6 +194,31 @@ class VolumeController(volumes_v2.VolumeController):
|
||||
except exception.VolumeSizeExceedsAvailableQuota as e:
|
||||
raise exc.HTTPForbidden(explanation=six.text_type(e))
|
||||
|
||||
def _get_image_snapshot(self, context, image_uuid):
|
||||
image_snapshot = None
|
||||
if image_uuid:
|
||||
image_service = glance.get_default_image_service()
|
||||
image_meta = image_service.show(context, image_uuid)
|
||||
if image_meta is not None:
|
||||
bdms = image_meta.get('properties', {}).get(
|
||||
'block_device_mapping', [])
|
||||
if bdms:
|
||||
boot_bdm = [bdm for bdm in bdms if (
|
||||
bdm.get('source_type') == 'snapshot' and
|
||||
bdm.get('boot_index') == 0)]
|
||||
if boot_bdm:
|
||||
try:
|
||||
image_snapshot = self.volume_api.get_snapshot(
|
||||
context, boot_bdm[0].get('snapshot_id'))
|
||||
return image_snapshot
|
||||
except exception.NotFound:
|
||||
explanation = _(
|
||||
'Nova specific image is found, but boot '
|
||||
'volume snapshot id:%s not found.'
|
||||
) % boot_bdm[0].get('snapshot_id')
|
||||
raise exc.HTTPNotFound(explanation=explanation)
|
||||
return image_snapshot
|
||||
|
||||
@wsgi.response(http_client.ACCEPTED)
|
||||
def create(self, req, body):
|
||||
"""Creates a new volume.
|
||||
@ -297,6 +323,17 @@ class VolumeController(volumes_v2.VolumeController):
|
||||
# Not found exception will be handled at the wsgi level
|
||||
kwargs['group'] = self.group_api.get(context, group_id)
|
||||
|
||||
if self.ext_mgr.is_loaded('os-image-create'):
|
||||
image_ref = volume.get('imageRef')
|
||||
if image_ref is not None:
|
||||
image_uuid = self._image_uuid_from_ref(image_ref, context)
|
||||
image_snapshot = self._get_image_snapshot(context, image_uuid)
|
||||
if (req_version.matches(mv.get_api_version(
|
||||
mv.SUPPORT_NOVA_IMAGE)) and image_snapshot):
|
||||
kwargs['snapshot'] = image_snapshot
|
||||
else:
|
||||
kwargs['image_id'] = image_uuid
|
||||
|
||||
size = volume.get('size', None)
|
||||
if size is None and kwargs['snapshot'] is not None:
|
||||
size = kwargs['snapshot']['volume_size']
|
||||
@ -305,12 +342,6 @@ class VolumeController(volumes_v2.VolumeController):
|
||||
|
||||
LOG.info("Create volume of %s GB", size)
|
||||
|
||||
if self.ext_mgr.is_loaded('os-image-create'):
|
||||
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)
|
||||
kwargs['scheduler_hints'] = volume.get('scheduler_hints', None)
|
||||
multiattach = volume.get('multiattach', False)
|
||||
|
@ -34,6 +34,7 @@ from cinder.tests.unit.api import fakes
|
||||
from cinder.tests.unit.api.v2 import fakes as v2_fakes
|
||||
from cinder.tests.unit.api.v2 import test_volumes as v2_test_volumes
|
||||
from cinder.tests.unit import fake_constants as fake
|
||||
from cinder.tests.unit.image import fake as fake_image
|
||||
from cinder.tests.unit import utils as test_utils
|
||||
from cinder import utils
|
||||
from cinder.volume import api as volume_api
|
||||
@ -48,6 +49,7 @@ class VolumeApiTest(test.TestCase):
|
||||
super(VolumeApiTest, self).setUp()
|
||||
self.ext_mgr = extensions.ExtensionManager()
|
||||
self.ext_mgr.extensions = {}
|
||||
fake_image.mock_image_service(self)
|
||||
self.controller = volumes.VolumeController(self.ext_mgr)
|
||||
|
||||
self.flags(host='fake')
|
||||
@ -259,6 +261,40 @@ class VolumeApiTest(test.TestCase):
|
||||
req.api_version_request = mv.get_api_version(version)
|
||||
return req
|
||||
|
||||
@mock.patch.object(db.sqlalchemy.api, '_volume_type_get_full',
|
||||
autospec=True)
|
||||
@mock.patch.object(volume_api.API, 'get_snapshot', autospec=True)
|
||||
@mock.patch.object(volume_api.API, 'create', autospec=True)
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_volume_create_with_snapshot_image(self, mock_validate, create,
|
||||
get_snapshot, volume_type_get):
|
||||
create.side_effect = v2_fakes.fake_volume_api_create
|
||||
get_snapshot.side_effect = v2_fakes.fake_snapshot_get
|
||||
volume_type_get.side_effect = v2_fakes.fake_volume_type_get
|
||||
|
||||
self.ext_mgr.extensions = {'os-image-create': 'fake'}
|
||||
vol = self._vol_in_request_body(
|
||||
image_id="b0a599e0-41d7-3582-b260-769f443c862a")
|
||||
|
||||
snapshot_id = fake.SNAPSHOT_ID
|
||||
ex = self._expected_vol_from_controller(snapshot_id=snapshot_id)
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_NOVA_IMAGE)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_NOVA_IMAGE)
|
||||
res_dict = self.controller.create(req, body)
|
||||
self.assertEqual(ex, res_dict)
|
||||
context = req.environ['cinder.context']
|
||||
get_snapshot.assert_called_once_with(self.controller.volume_api,
|
||||
context, snapshot_id)
|
||||
kwargs = self._expected_volume_api_create_kwargs(
|
||||
v2_fakes.fake_snapshot(snapshot_id))
|
||||
create.assert_called_once_with(
|
||||
self.controller.volume_api, context,
|
||||
vol['size'], v2_fakes.DEFAULT_VOL_NAME,
|
||||
v2_fakes.DEFAULT_VOL_DESCRIPTION, **kwargs)
|
||||
|
||||
def test_volumes_summary_in_unsupport_version(self):
|
||||
"""Function call to test summary volumes API in unsupported version"""
|
||||
req = self._fake_volumes_summary_request(
|
||||
|
@ -147,6 +147,24 @@ class _FakeImageService(object):
|
||||
'auto_disk_config': 'True'},
|
||||
'size': 1234000000}
|
||||
|
||||
image8 = {'id': 'b0a599e0-41d7-3582-b260-769f443c862a',
|
||||
'name': 'fakeimage8',
|
||||
'created_at': timestamp,
|
||||
'updated_at': timestamp,
|
||||
'deleted_at': None,
|
||||
'deleted': False,
|
||||
'status': 'active',
|
||||
'is_public': False,
|
||||
'container_format': 'bare',
|
||||
'disk_format': 'raw',
|
||||
'properties':
|
||||
{'block_device_mapping': [
|
||||
{'boot_index': 0, 'source_type': 'snapshot',
|
||||
'snapshot_id': fake_constants.SNAPSHOT_ID}],
|
||||
'ramdisk_id': 'nokernel',
|
||||
'architecture': 'x86_64',
|
||||
'auto_disk_config': 'True'}}
|
||||
|
||||
self.create(None, image1)
|
||||
self.create(None, image2)
|
||||
self.create(None, image3)
|
||||
@ -154,6 +172,7 @@ class _FakeImageService(object):
|
||||
self.create(None, image5)
|
||||
self.create(None, image6)
|
||||
self.create(None, image7)
|
||||
self.create(None, image8)
|
||||
self._imagedata = {}
|
||||
self.temp_images = mock.MagicMock()
|
||||
super(_FakeImageService, self).__init__()
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
fixes:
|
||||
- Fix the bug that Cinder can't support creating volume
|
||||
from Nova specific image which only includes ``snapshot-id``
|
||||
metadata (Bug #1560867).
|
Loading…
Reference in New Issue
Block a user