[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:
parent
c6fcdc9e04
commit
59af73e47b
|
@ -13,9 +13,11 @@ limitations under the License.
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder.exception import APITimeout
|
from cinder.exception import APITimeout
|
||||||
|
from cinder.exception import ImageNotFound
|
||||||
from cinder.exception import NotFound
|
from cinder.exception import NotFound
|
||||||
from cinder.exception import VolumeNotFound
|
from cinder.exception import VolumeNotFound
|
||||||
from cinder import test
|
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 import ebs
|
||||||
from cinder.volume.drivers.aws.exception import AvailabilityZoneNotFound
|
from cinder.volume.drivers.aws.exception import AvailabilityZoneNotFound
|
||||||
import mock
|
import mock
|
||||||
|
@ -32,8 +34,8 @@ class EBSVolumeTestCase(test.TestCase):
|
||||||
ebs.CONF.AWS.secret_key = 'fake-secret'
|
ebs.CONF.AWS.secret_key = 'fake-secret'
|
||||||
ebs.CONF.AWS.az = 'us-east-1a'
|
ebs.CONF.AWS.az = 'us-east-1a'
|
||||||
self._driver = ebs.EBSDriver()
|
self._driver = ebs.EBSDriver()
|
||||||
ctxt = context.get_admin_context()
|
self.ctxt = context.get_admin_context()
|
||||||
self._driver.do_setup(ctxt)
|
self._driver.do_setup(self.ctxt)
|
||||||
|
|
||||||
def _stub_volume(self, **kwargs):
|
def _stub_volume(self, **kwargs):
|
||||||
uuid = u'c20aba21-6ef6-446b-b374-45733b4883ba'
|
uuid = u'c20aba21-6ef6-446b-b374-45733b4883ba'
|
||||||
|
@ -62,12 +64,23 @@ class EBSVolumeTestCase(test.TestCase):
|
||||||
ss['display_name'] = kwargs.get('display_name', 'snapshot_007')
|
ss['display_name'] = kwargs.get('display_name', 'snapshot_007')
|
||||||
return ss
|
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
|
@mock_ec2_deprecated
|
||||||
def test_availability_zone_config(self):
|
def test_availability_zone_config(self):
|
||||||
ebs.CONF.AWS.az = 'hgkjhgkd'
|
ebs.CONF.AWS.az = 'hgkjhgkd'
|
||||||
driver = ebs.EBSDriver()
|
driver = ebs.EBSDriver()
|
||||||
ctxt = context.get_admin_context()
|
self.assertRaises(AvailabilityZoneNotFound, driver.do_setup, self.ctxt)
|
||||||
self.assertRaises(AvailabilityZoneNotFound, driver.do_setup, ctxt)
|
|
||||||
ebs.CONF.AWS.az = 'us-east-1a'
|
ebs.CONF.AWS.az = 'us-east-1a'
|
||||||
|
|
||||||
@mock_ec2_deprecated
|
@mock_ec2_deprecated
|
||||||
|
@ -143,3 +156,44 @@ class EBSVolumeTestCase(test.TestCase):
|
||||||
def test_volume_from_non_existing_snapshot(self):
|
def test_volume_from_non_existing_snapshot(self):
|
||||||
self.assertRaises(NotFound, self._driver.create_volume_from_snapshot,
|
self.assertRaises(NotFound, self._driver.create_volume_from_snapshot,
|
||||||
self._stub_volume(), self._stub_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))
|
||||||
|
|
|
@ -14,10 +14,13 @@ limitations under the License.
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from boto import ec2
|
from boto import ec2
|
||||||
|
from boto.exception import EC2ResponseError
|
||||||
from boto.regioninfo import RegionInfo
|
from boto.regioninfo import RegionInfo
|
||||||
from cinder.exception import APITimeout
|
from cinder.exception import APITimeout
|
||||||
|
from cinder.exception import ImageNotFound
|
||||||
from cinder.exception import InvalidConfigurationValue
|
from cinder.exception import InvalidConfigurationValue
|
||||||
from cinder.exception import NotFound
|
from cinder.exception import NotFound
|
||||||
|
from cinder.exception import VolumeBackendAPIException
|
||||||
from cinder.exception import VolumeNotFound
|
from cinder.exception import VolumeNotFound
|
||||||
from cinder.volume.driver import BaseVD
|
from cinder.volume.driver import BaseVD
|
||||||
from cinder.volume.drivers.aws.exception import AvailabilityZoneNotFound
|
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_log import log as logging
|
||||||
from oslo_service import loopingcall
|
from oslo_service import loopingcall
|
||||||
|
|
||||||
|
|
||||||
aws_group = cfg.OptGroup(name='AWS',
|
aws_group = cfg.OptGroup(name='AWS',
|
||||||
title='Options to connect to an AWS environment')
|
title='Options to connect to an AWS environment')
|
||||||
aws_opts = [
|
aws_opts = [
|
||||||
|
@ -117,6 +119,23 @@ class EBSDriver(BaseVD):
|
||||||
time.time())
|
time.time())
|
||||||
return timer.start(interval=5).wait()
|
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):
|
def create_volume(self, volume):
|
||||||
size = volume['size']
|
size = volume['size']
|
||||||
ebs_vol = self._conn.create_volume(size, self._zone)
|
ebs_vol = self._conn.create_volume(size, self._zone)
|
||||||
|
@ -236,8 +255,64 @@ class EBSDriver(BaseVD):
|
||||||
'created_at': volume['created_at'],
|
'created_at': volume['created_at'],
|
||||||
'Name': volume['display_name']})
|
'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):
|
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):
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||||
raise NotImplemented()
|
raise NotImplemented()
|
||||||
|
@ -246,4 +321,7 @@ class EBSDriver(BaseVD):
|
||||||
raise NotImplemented()
|
raise NotImplemented()
|
||||||
|
|
||||||
def copy_volume_data(self, context, src_vol, dest_vol, remote=None):
|
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
|
||||||
|
|
|
@ -107,7 +107,7 @@ class Store(glance_store.driver.Store):
|
||||||
:param location `glance_store.location.Location` object, supplied
|
:param location `glance_store.location.Location` object, supplied
|
||||||
from glance_store.location.get_location_from_uri()
|
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
|
@capabilities.check
|
||||||
def delete(self, location, context=None):
|
def delete(self, location, context=None):
|
||||||
|
|
Loading…
Reference in New Issue