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_COUNT_INFO = '3.45'
|
||||||
|
|
||||||
|
SUPPORT_NOVA_IMAGE = '3.46'
|
||||||
|
|
||||||
|
|
||||||
def get_mv_header(version):
|
def get_mv_header(version):
|
||||||
"""Gets a formatted HTTP microversion header.
|
"""Gets a formatted HTTP microversion header.
|
||||||
|
@ -109,6 +109,7 @@ REST_API_VERSION_HISTORY = """
|
|||||||
* 3.44 - Add attachment-complete.
|
* 3.44 - Add attachment-complete.
|
||||||
* 3.45 - Add ``count`` field to volume, backup and snapshot list and
|
* 3.45 - Add ``count`` field to volume, backup and snapshot list and
|
||||||
detail APIs.
|
detail APIs.
|
||||||
|
* 3.46 - Support create volume by Nova specific image (0 size image).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The minimum and maximum versions of the API supported
|
# The minimum and maximum versions of the API supported
|
||||||
@ -116,7 +117,7 @@ REST_API_VERSION_HISTORY = """
|
|||||||
# minimum version of the API supported.
|
# minimum version of the API supported.
|
||||||
# Explicitly using /v2 endpoints will still work
|
# Explicitly using /v2 endpoints will still work
|
||||||
_MIN_API_VERSION = "3.0"
|
_MIN_API_VERSION = "3.0"
|
||||||
_MAX_API_VERSION = "3.45"
|
_MAX_API_VERSION = "3.46"
|
||||||
_LEGACY_API_VERSION2 = "2.0"
|
_LEGACY_API_VERSION2 = "2.0"
|
||||||
UPDATED = "2017-09-19T20:18:14Z"
|
UPDATED = "2017-09-19T20:18:14Z"
|
||||||
|
|
||||||
|
@ -377,3 +377,7 @@ user documentation.
|
|||||||
3.45
|
3.45
|
||||||
----
|
----
|
||||||
Add ``count`` field to volume, backup and snapshot list and detail APIs.
|
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 exception
|
||||||
from cinder import group as group_api
|
from cinder import group as group_api
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
|
from cinder.image import glance
|
||||||
from cinder import objects
|
from cinder import objects
|
||||||
from cinder.policies import volumes as policy
|
from cinder.policies import volumes as policy
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
@ -193,6 +194,31 @@ class VolumeController(volumes_v2.VolumeController):
|
|||||||
except exception.VolumeSizeExceedsAvailableQuota as e:
|
except exception.VolumeSizeExceedsAvailableQuota as e:
|
||||||
raise exc.HTTPForbidden(explanation=six.text_type(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)
|
@wsgi.response(http_client.ACCEPTED)
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Creates a new volume.
|
"""Creates a new volume.
|
||||||
@ -297,6 +323,17 @@ class VolumeController(volumes_v2.VolumeController):
|
|||||||
# Not found exception will be handled at the wsgi level
|
# Not found exception will be handled at the wsgi level
|
||||||
kwargs['group'] = self.group_api.get(context, group_id)
|
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)
|
size = volume.get('size', None)
|
||||||
if size is None and kwargs['snapshot'] is not None:
|
if size is None and kwargs['snapshot'] is not None:
|
||||||
size = kwargs['snapshot']['volume_size']
|
size = kwargs['snapshot']['volume_size']
|
||||||
@ -305,12 +342,6 @@ class VolumeController(volumes_v2.VolumeController):
|
|||||||
|
|
||||||
LOG.info("Create volume of %s GB", size)
|
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['availability_zone'] = volume.get('availability_zone', None)
|
||||||
kwargs['scheduler_hints'] = volume.get('scheduler_hints', None)
|
kwargs['scheduler_hints'] = volume.get('scheduler_hints', None)
|
||||||
multiattach = volume.get('multiattach', False)
|
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 fakes as v2_fakes
|
||||||
from cinder.tests.unit.api.v2 import test_volumes as v2_test_volumes
|
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 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.tests.unit import utils as test_utils
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder.volume import api as volume_api
|
from cinder.volume import api as volume_api
|
||||||
@ -48,6 +49,7 @@ class VolumeApiTest(test.TestCase):
|
|||||||
super(VolumeApiTest, self).setUp()
|
super(VolumeApiTest, self).setUp()
|
||||||
self.ext_mgr = extensions.ExtensionManager()
|
self.ext_mgr = extensions.ExtensionManager()
|
||||||
self.ext_mgr.extensions = {}
|
self.ext_mgr.extensions = {}
|
||||||
|
fake_image.mock_image_service(self)
|
||||||
self.controller = volumes.VolumeController(self.ext_mgr)
|
self.controller = volumes.VolumeController(self.ext_mgr)
|
||||||
|
|
||||||
self.flags(host='fake')
|
self.flags(host='fake')
|
||||||
@ -259,6 +261,40 @@ class VolumeApiTest(test.TestCase):
|
|||||||
req.api_version_request = mv.get_api_version(version)
|
req.api_version_request = mv.get_api_version(version)
|
||||||
return req
|
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):
|
def test_volumes_summary_in_unsupport_version(self):
|
||||||
"""Function call to test summary volumes API in unsupported version"""
|
"""Function call to test summary volumes API in unsupported version"""
|
||||||
req = self._fake_volumes_summary_request(
|
req = self._fake_volumes_summary_request(
|
||||||
|
@ -147,6 +147,24 @@ class _FakeImageService(object):
|
|||||||
'auto_disk_config': 'True'},
|
'auto_disk_config': 'True'},
|
||||||
'size': 1234000000}
|
'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, image1)
|
||||||
self.create(None, image2)
|
self.create(None, image2)
|
||||||
self.create(None, image3)
|
self.create(None, image3)
|
||||||
@ -154,6 +172,7 @@ class _FakeImageService(object):
|
|||||||
self.create(None, image5)
|
self.create(None, image5)
|
||||||
self.create(None, image6)
|
self.create(None, image6)
|
||||||
self.create(None, image7)
|
self.create(None, image7)
|
||||||
|
self.create(None, image8)
|
||||||
self._imagedata = {}
|
self._imagedata = {}
|
||||||
self.temp_images = mock.MagicMock()
|
self.temp_images = mock.MagicMock()
|
||||||
super(_FakeImageService, self).__init__()
|
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