[AWS] Added a support to create volume from image and volume from volume

Description:
- Changes in 'glance/glance_store/_drivers/aws.py' are required because
when glanceclient tries to retrieve image details, it expects 2 values.
But in the current case, it was yielding a tuple which was throwing
exception as it was getting only 1 value when 'get()' was called

- Added a function "clone_image()" which takes volume details, image
metadata, object of glance service, image location as arguments.

- Steps:
1. get image details from AWS using image id provided in image metadata
and get snapshot id from response. If snapshot id is None, then raise an
exception
2. create a dict object which contains size, zone and snapshot id as
fields
3. call create_volume() with dict created as argument
4. create tags for volume created

Closes-Bug: #1710046

Change-Id: I241e55f45d27e14b7328ae276ec544e5360e1ebd
This commit is contained in:
Pratik Shah 2017-08-19 15:37:30 +05:30
parent c6fcdc9e04
commit 59af73e47b
3 changed files with 140 additions and 8 deletions

View File

@ -13,9 +13,11 @@ limitations under the License.
from cinder import context
from cinder.exception import APITimeout
from cinder.exception import ImageNotFound
from cinder.exception import NotFound
from cinder.exception import VolumeNotFound
from cinder import test
from cinder.tests.unit.fake_volume import fake_volume_obj
from cinder.volume.drivers.aws import ebs
from cinder.volume.drivers.aws.exception import AvailabilityZoneNotFound
import mock
@ -32,8 +34,8 @@ class EBSVolumeTestCase(test.TestCase):
ebs.CONF.AWS.secret_key = 'fake-secret'
ebs.CONF.AWS.az = 'us-east-1a'
self._driver = ebs.EBSDriver()
ctxt = context.get_admin_context()
self._driver.do_setup(ctxt)
self.ctxt = context.get_admin_context()
self._driver.do_setup(self.ctxt)
def _stub_volume(self, **kwargs):
uuid = u'c20aba21-6ef6-446b-b374-45733b4883ba'
@ -62,12 +64,23 @@ class EBSVolumeTestCase(test.TestCase):
ss['display_name'] = kwargs.get('display_name', 'snapshot_007')
return ss
def _fake_image_meta(self):
image_meta = dict()
image_meta['properties'] = {}
image_meta['status'] = 'active'
image_meta['name'] = 'fake_image_name'
image_meta['container_format'] = 'ami'
image_meta['created_at'] = '2016-10-19 23:22:33'
image_meta['disk_format'] = 'ami'
image_meta['id'] = 'b2a55a41-7f8b-5ad8-7de9-84309d5108a2'
image_meta['properties']['aws_image_id'] = 'ami-00001'
return image_meta
@mock_ec2_deprecated
def test_availability_zone_config(self):
ebs.CONF.AWS.az = 'hgkjhgkd'
driver = ebs.EBSDriver()
ctxt = context.get_admin_context()
self.assertRaises(AvailabilityZoneNotFound, driver.do_setup, ctxt)
self.assertRaises(AvailabilityZoneNotFound, driver.do_setup, self.ctxt)
ebs.CONF.AWS.az = 'us-east-1a'
@mock_ec2_deprecated
@ -143,3 +156,44 @@ class EBSVolumeTestCase(test.TestCase):
def test_volume_from_non_existing_snapshot(self):
self.assertRaises(NotFound, self._driver.create_volume_from_snapshot,
self._stub_volume(), self._stub_snapshot())
def test_clone_image_with_invalid_image(self):
image_meta = self._fake_image_meta()
volume = fake_volume_obj(self.ctxt)
self.assertRaises(ImageNotFound, self._driver.clone_image,
self.ctxt, volume, '', image_meta, '')
@mock_ec2_deprecated
@mock.patch('cinder.volume.drivers.aws.ebs.EBSDriver._get_snapshot_id')
def test_clone_image(self, mock_get):
snapshot = self._stub_snapshot()
image_meta = self._fake_image_meta()
volume = fake_volume_obj(self.ctxt)
volume.id = 'd30aba21-6ef6-446b-b374-45733b4883ba'
volume.display_name = 'volume-00000001'
volume.project_id = 'fake_project_id'
volume.created_at = '2016-10-19 23:22:33'
self._driver.create_volume(snapshot['volume'])
self._driver.create_snapshot(snapshot)
ebs_snap = self._driver._find(snapshot['id'],
self._driver._conn.get_all_snapshots)
mock_get.return_value = ebs_snap.id
metadata, cloned = self._driver.clone_image(self.ctxt, volume, '',
image_meta, '')
self.assertEqual(True, cloned)
self.assertTrue(isinstance(metadata, dict))
@mock_ec2_deprecated
def test_create_cloned_volume(self):
src_volume = fake_volume_obj(self.ctxt)
src_volume.display_name = 'volume-00000001'
src_volume.created_at = '2016-10-19 23:22:33'
src_volume.project_id = 'fake_project_id'
volume = fake_volume_obj(self.ctxt)
volume.id = 'd30aba21-6ef6-446b-b374-45733b4883ba'
volume.display_name = 'volume-00000002'
volume.project_id = 'fake_project_id'
volume.created_at = '2016-10-19 23:23:33'
self._driver.create_volume(src_volume)
self.assertIsNone(self._driver.create_cloned_volume(volume,
src_volume))

View File

@ -14,10 +14,13 @@ limitations under the License.
import time
from boto import ec2
from boto.exception import EC2ResponseError
from boto.regioninfo import RegionInfo
from cinder.exception import APITimeout
from cinder.exception import ImageNotFound
from cinder.exception import InvalidConfigurationValue
from cinder.exception import NotFound
from cinder.exception import VolumeBackendAPIException
from cinder.exception import VolumeNotFound
from cinder.volume.driver import BaseVD
from cinder.volume.drivers.aws.exception import AvailabilityZoneNotFound
@ -25,7 +28,6 @@ from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import loopingcall
aws_group = cfg.OptGroup(name='AWS',
title='Options to connect to an AWS environment')
aws_opts = [
@ -117,6 +119,23 @@ class EBSDriver(BaseVD):
time.time())
return timer.start(interval=5).wait()
def _wait_for_tags_creation(self, id, volume):
def _wait_for_completion(start_time):
if time.time() - start_time > self._wait_time_sec:
raise loopingcall.LoopingCallDone(False)
self._conn.create_tags([id],
{'project_id': volume['project_id'],
'uuid': volume['id'],
'is_clone': True,
'created_at': volume['created_at'],
'Name': volume['display_name']})
obj = self._conn.get_all_volumes([id])[0]
if obj.tags:
raise loopingcall.LoopingCallDone(True)
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_completion,
time.time())
return timer.start(interval=5).wait()
def create_volume(self, volume):
size = volume['size']
ebs_vol = self._conn.create_volume(size, self._zone)
@ -236,8 +255,64 @@ class EBSDriver(BaseVD):
'created_at': volume['created_at'],
'Name': volume['display_name']})
def create_cloned_volume(self, volume, srcvol_ref):
ebs_snap = None
ebs_vol = None
try:
src_vol = self._find(srcvol_ref['id'], self._conn.get_all_volumes)
ebs_snap = self._conn.create_snapshot(src_vol.id)
if self._wait_for_snapshot(ebs_snap.id, 'completed') is False:
raise APITimeout(service='EC2')
ebs_vol = self._conn.create_volume(
size=volume.size, zone=self._zone, snapshot=ebs_snap.id)
if self._wait_for_create(ebs_vol.id, 'available') is False:
raise APITimeout(service='EC2')
if self._wait_for_tags_creation(ebs_vol.id, volume) is False:
raise APITimeout(service='EC2')
except NotFound:
raise VolumeNotFound(srcvol_ref['id'])
except Exception as ex:
message = "create_cloned_volume failed! volume: {0}, reason: {1}"
LOG.error(message.format(volume.id, ex))
if ebs_vol:
self._conn.delete_volume(ebs_vol.id)
raise VolumeBackendAPIException(data=message.format(volume.id, ex))
finally:
if ebs_snap:
self._conn.delete_snapshot(ebs_snap.id)
def clone_image(self, context, volume, image_location, image_meta,
image_service):
image_id = image_meta['properties']['aws_image_id']
snapshot_id = self._get_snapshot_id(image_id)
ebs_vol = self._conn.create_volume(size=volume.size, zone=self._zone,
snapshot=snapshot_id)
if self._wait_for_create(ebs_vol.id, 'available') is False:
raise APITimeout(service='EC2')
if self._wait_for_tags_creation(ebs_vol.id, volume) is False:
raise APITimeout(service='EC2')
metadata = volume['metadata']
metadata['new_volume_id'] = ebs_vol.id
return dict(metadata=metadata), True
def _get_snapshot_id(self, image_id):
try:
response = self._conn.get_all_images(image_ids=[image_id])[0]
snapshot_id = response.block_device_mapping[
'/dev/sda1'].snapshot_id
return snapshot_id
except EC2ResponseError:
message = "Getting image {0} failed.".format(image_id)
LOG.error(message)
raise ImageNotFound(message)
def copy_image_to_volume(self, context, volume, image_service, image_id):
raise NotImplemented()
"""Nothing need to do here since we create volume from image in
clone_image.
"""
pass
def copy_volume_to_image(self, context, volume, image_service, image_meta):
raise NotImplemented()
@ -246,4 +321,7 @@ class EBSDriver(BaseVD):
raise NotImplemented()
def copy_volume_data(self, context, src_vol, dest_vol, remote=None):
raise NotImplemented()
"""Nothing need to do here since we create volume from another
volume in create_cloned_volume.
"""
pass

View File

@ -107,7 +107,7 @@ class Store(glance_store.driver.Store):
:param location `glance_store.location.Location` object, supplied
from glance_store.location.get_location_from_uri()
"""
yield ('aws://generic', self.get_size(location, context))
return 'aws://generic', self.get_size(location, context)
@capabilities.check
def delete(self, location, context=None):