@patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._clone_image')
def test_clone_image_pub(self, mock_exec):
- self.driver.clone_image('', '', {'id': 1})
+ self.driver.clone_image('', '', '', {'id': 1}, '')
@patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._is_gpfs_path')
def test_is_cloneable_ok(self, mock_is_gpfs_path):
drv._post_clone_image(volume)
mox.ReplayAll()
- drv.clone_image(volume, ('image_location', None), {'id': 'image_id'})
+ drv.clone_image('',
+ volume,
+ ('image_location', None),
+ {'id': 'image_id'}, '')
mox.VerifyAll()
def get_img_info(self, format):
drv._is_share_vol_compatible(IgnoreArg(), IgnoreArg()).AndReturn(False)
mox.ReplayAll()
- (prop, cloned) = drv. clone_image(
+ (prop, cloned) = drv.clone_image(
+ '',
volume,
('nfs://127.0.0.1:/share/img-id', None),
- {'id': 'image_id'})
+ {'id': 'image_id'},
+ '')
mox.VerifyAll()
if not cloned and not prop['provider_location']:
pass
drv._resize_image_file({'name': 'vol'}, IgnoreArg())
mox.ReplayAll()
- drv. clone_image(
+ drv.clone_image(
+ '',
volume,
('nfs://127.0.0.1:/share/img-id', None),
- {'id': 'image_id'})
+ {'id': 'image_id'},
+ '')
mox.VerifyAll()
def test_clone_image_cloneableshare_notraw(self):
mox.ReplayAll()
drv.clone_image(
- volume, ('nfs://127.0.0.1/share/img-id', None), {'id': 'image_id'})
+ '',
+ volume,
+ ('nfs://127.0.0.1/share/img-id', None),
+ {'id': 'image_id'},
+ '')
mox.VerifyAll()
def test_clone_image_file_not_discovered(self):
drv._delete_file('/mnt/vol')
mox.ReplayAll()
- vol_dict, result = drv. clone_image(
- volume, ('nfs://127.0.0.1/share/img-id', None), {'id': 'image_id'})
+ vol_dict, result = drv.clone_image(
+ '',
+ volume,
+ ('nfs://127.0.0.1/share/img-id', None),
+ {'id': 'image_id'},
+ '')
mox.VerifyAll()
self.assertFalse(result)
self.assertFalse(vol_dict['bootable'])
drv._delete_file('/mnt/vol')
mox.ReplayAll()
- vol_dict, result = drv. clone_image(
- volume, ('nfs://127.0.0.1/share/img-id', None), {'id': 'image_id'})
+ vol_dict, result = drv.clone_image(
+ '',
+ volume,
+ ('nfs://127.0.0.1/share/img-id', None),
+ {'id': 'image_id'},
+ '')
mox.VerifyAll()
self.assertFalse(result)
self.assertFalse(vol_dict['bootable'])
self.driver.manage_existing,
self.volume, existing_ref)
- #make sure the exception was raised
+ # Make sure the exception was raised
self.assertEqual(RAISED_EXCEPTIONS,
[self.mock_rbd.ImageExists])
def test_create_vol_from_image_status_available(self):
"""Clone raw image then verify volume is in available state."""
- def _mock_clone_image(volume, image_location, image_meta):
+ def _mock_clone_image(context, volume, image_location,
+ image_meta, image_service):
return {'provider_location': None}, True
with mock.patch.object(self.volume.driver, 'clone_image') as \
def test_create_vol_from_non_raw_image_status_available(self):
"""Clone non-raw image then verify volume is in available state."""
- def _mock_clone_image(volume, image_location, image_meta):
+ def _mock_clone_image(context, volume, image_location,
+ image_meta, image_service):
return {'provider_location': None}, False
with mock.patch.object(self.volume.driver, 'clone_image') as \
with mock.patch.object(driver, '_is_cloneable', lambda *args: False):
image_loc = (mock.Mock(), mock.Mock())
- actual = driver.clone_image(mock.Mock(), image_loc, {})
+ actual = driver.clone_image(mock.Mock(),
+ mock.Mock(),
+ image_loc,
+ {},
+ mock.Mock())
self.assertEqual(({}, False), actual)
self.assertEqual(({}, False),
- driver.clone_image(object(), None, {}))
+ driver.clone_image('', object(), None, {}, ''))
def test_clone_success(self):
expected = ({'provider_location': None}, True)
image_loc = ('rbd://fee/fi/fo/fum', None)
volume = {'name': 'vol1'}
- actual = driver.clone_image(volume, image_loc,
+ actual = driver.clone_image(mock.Mock(),
+ volume,
+ image_loc,
{'disk_format': 'raw',
- 'id': 'id.foo'})
+ 'id': 'id.foo'},
+ mock.Mock())
self.assertEqual(expected, actual)
mock_clone.assert_called_once_with(volume,
# License for the specific language governing permissions and limitations
# under the License.
+import datetime
+
import mock
import mox
from oslo.utils import timeutils
self.configuration.sf_account_prefix = 'cinder'
self.configuration.reserved_percentage = 25
self.configuration.iscsi_helper = None
+ self.configuration.sf_template_account_name = 'openstack-vtemplate'
+ self.configuration.sf_allow_template_caching = False
super(SolidFireVolumeTestCase, self).setUp()
self.stubs.Set(SolidFireDriver, '_issue_api_request',
self.expected_qos_results = {'minIOPS': 1000,
'maxIOPS': 10000,
'burstIOPS': 20000}
+ self.mock_stats_data =\
+ {'result':
+ {'clusterCapacity': {'maxProvisionedSpace': 107374182400,
+ 'usedSpace': 1073741824,
+ 'compressionPercent': 100,
+ 'deDuplicationPercent': 100,
+ 'thinProvisioningPercent': 100}}}
+ self.mock_volume = {'project_id': 'testprjid',
+ 'name': 'testvol',
+ 'size': 1,
+ 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
+ 'volume_type_id': 'fast',
+ 'created_at': timeutils.utcnow()}
+ self.fake_image_meta = {'id': '17c550bb-a411-44c0-9aaf-0d96dd47f501',
+ 'updated_at': datetime.datetime(2013, 9,
+ 28, 15,
+ 27, 36,
+ 325355),
+ 'is_public': True,
+ 'owner': 'testprjid'}
+ self.fake_image_service = 'null'
def fake_build_endpoint_info(obj, **kwargs):
endpoint = {}
sfv.create_cloned_volume(testvol_b, testvol)
def test_initialize_connector_with_blocksizes(self):
- expected_iqn = 'iqn.2010-01.com.solidfire:'\
- '87hg.uuid-2cc06226-cc74-4cb7-bd55-14aed659a0cc.4060'
- expected_properties = \
- {'driver_volume_type': 'iscsi',
- 'data': {'target_discovered': False,
- 'encrypted': False,
- 'logical_block_size': '4096',
- 'physical_block_size': '4096',
- 'target_iqn': expected_iqn,
- 'target_portal': '10.10.7.1:3260',
- 'volume_id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
- 'target_lun': 0,
- 'auth_password': '2FE0CQ8J196R',
- 'auth_username':
- 'stack-1-a60e2611875f40199931f2c76370d66b',
- 'auth_method': 'CHAP'}}
-
connector = {'initiator': 'iqn.2012-07.org.fake:01'}
testvol = {'project_id': 'testprjid',
'name': 'testvol',
}
sfv = SolidFireDriver(configuration=self.configuration)
- self.assertEqual(sfv.initialize_connection(testvol, connector),
- expected_properties)
-
- @mock.patch('cinder.volume.driver.CONF')
- def test_iscsi_helpers_not_in_base_iscsi_driver(self, mock_conf):
- # This test is added to check for bug: 1400804
- # The base iscsi driver should be clean from specifics
- # regarding tgtadm or LVM driver, this check is here
- # to make sure nothing regarding specific iscsi_helpers
- # sneak back in
- expected_iqn = 'iqn.2010-01.com.solidfire:'\
- '87hg.uuid-2cc06226-cc74-4cb7-bd55-14aed659a0cc.4060'
- expected_properties = \
- {'driver_volume_type': 'iscsi',
- 'data': {'target_discovered': False,
- 'encrypted': False,
- 'logical_block_size': '4096',
- 'physical_block_size': '4096',
- 'target_iqn': expected_iqn,
- 'target_portal': '10.10.7.1:3260',
- 'volume_id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
- 'target_lun': 0,
- 'auth_password': '2FE0CQ8J196R',
- 'auth_username':
- 'stack-1-a60e2611875f40199931f2c76370d66b',
- 'auth_method': 'CHAP'}}
-
- connector = {'initiator': 'iqn.2012-07.org.fake:01'}
- testvol = {'project_id': 'testprjid',
- 'name': 'testvol',
- 'size': 1,
- 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
- 'volume_type_id': None,
- 'provider_location': '10.10.7.1:3260 iqn.2010-01.com.'
- 'solidfire:87hg.uuid-2cc06226-cc'
- '74-4cb7-bd55-14aed659a0cc.4060 0',
- 'provider_auth': 'CHAP stack-1-a60e2611875f40199931f2'
- 'c76370d66b 2FE0CQ8J196R',
- 'provider_geometry': '4096 4096',
- 'created_at': timeutils.utcnow(),
- }
-
- mock_conf.iscsi_helper = 'lioadm'
- sfv = SolidFireDriver(configuration=self.configuration)
- self.assertEqual(sfv.initialize_connection(testvol, connector),
- expected_properties)
-
- mock_conf.iscsi_helper = 'iseradm'
- sfv = SolidFireDriver(configuration=self.configuration)
- self.assertEqual(sfv.initialize_connection(testvol, connector),
- expected_properties)
-
- mock_conf.iscsi_helper = 'tgtadm'
- sfv = SolidFireDriver(configuration=self.configuration)
- self.assertEqual(sfv.initialize_connection(testvol, connector),
- expected_properties)
+ properties = sfv.initialize_connection(testvol, connector)
+ self.assertEqual('4096', properties['data']['physical_block_size'])
+ self.assertEqual('4096', properties['data']['logical_block_size'])
def test_create_volume_with_qos(self):
preset_qos = {}
sf_vol_object['attributes']['migration_uuid'])
self.assertEqual('UUID-a720b3c0-d1f0-11e1-9b23-0800200c9a66',
sf_vol_object['name'])
+
+ @mock.patch.object(SolidFireDriver, '_issue_api_request')
+ @mock.patch.object(SolidFireDriver, '_get_sfaccount')
+ @mock.patch.object(SolidFireDriver, '_get_sf_volume')
+ @mock.patch.object(SolidFireDriver, '_create_image_volume')
+ def test_verify_image_volume_out_of_date(self,
+ _mock_create_image_volume,
+ _mock_get_sf_volume,
+ _mock_get_sfaccount,
+ _mock_issue_api_request):
+ fake_sf_vref = {
+ 'status': 'active', 'volumeID': 1,
+ 'attributes': {
+ 'image_info':
+ {'image_updated_at': '2014-12-17T00:16:23+00:00',
+ 'image_id': '17c550bb-a411-44c0-9aaf-0d96dd47f501',
+ 'image_name': 'fake-image',
+ 'image_created_at': '2014-12-17T00:16:23+00:00'}}}
+
+ stats_data =\
+ {'result':
+ {'clusterCapacity': {'maxProvisionedSpace': 107374182400,
+ 'usedSpace': 1073741824,
+ 'compressionPercent': 100,
+ 'deDuplicationPercent': 100,
+ 'thinProvisioningPercent': 100}}}
+
+ _mock_issue_api_request.return_value = stats_data
+ _mock_get_sfaccount.return_value = {'username': 'openstack-vtemplate',
+ 'accountID': 7777}
+ _mock_get_sf_volume.return_value = fake_sf_vref
+ _mock_create_image_volume.return_value = fake_sf_vref
+
+ image_meta = {'id': '17c550bb-a411-44c0-9aaf-0d96dd47f501',
+ 'updated_at': datetime.datetime(2013, 9, 28,
+ 15, 27, 36,
+ 325355)}
+ image_service = 'null'
+
+ sfv = SolidFireDriver(configuration=self.configuration)
+ _mock_issue_api_request.return_value = {'result': 'ok'}
+ sfv._verify_image_volume(self.ctxt, image_meta, image_service)
+ self.assertTrue(_mock_create_image_volume.called)
+
+ @mock.patch.object(SolidFireDriver, '_issue_api_request')
+ @mock.patch.object(SolidFireDriver, '_get_sfaccount')
+ @mock.patch.object(SolidFireDriver, '_get_sf_volume')
+ @mock.patch.object(SolidFireDriver, '_create_image_volume')
+ def test_verify_image_volume_ok(self,
+ _mock_create_image_volume,
+ _mock_get_sf_volume,
+ _mock_get_sfaccount,
+ _mock_issue_api_request):
+
+ _mock_issue_api_request.return_value = self.mock_stats_data
+ _mock_get_sfaccount.return_value = {'username': 'openstack-vtemplate',
+ 'accountID': 7777}
+ _mock_get_sf_volume.return_value =\
+ {'status': 'active', 'volumeID': 1,
+ 'attributes': {
+ 'image_info':
+ {'image_updated_at': '2013-09-28T15:27:36.325355',
+ 'image_id': '17c550bb-a411-44c0-9aaf-0d96dd47f501',
+ 'image_name': 'fake-image',
+ 'image_created_at': '2014-12-17T00:16:23+00:00'}}}
+ _mock_create_image_volume.return_value = None
+
+ image_meta = {'id': '17c550bb-a411-44c0-9aaf-0d96dd47f501',
+ 'updated_at': datetime.datetime(2013, 9, 28,
+ 15, 27, 36,
+ 325355)}
+ image_service = 'null'
+
+ sfv = SolidFireDriver(configuration=self.configuration)
+ _mock_issue_api_request.return_value = {'result': 'ok'}
+
+ sfv._verify_image_volume(self.ctxt, image_meta, image_service)
+ self.assertFalse(_mock_create_image_volume.called)
+
+ @mock.patch.object(SolidFireDriver, '_issue_api_request')
+ def test_clone_image_not_configured(self, _mock_issue_api_request):
+ _mock_issue_api_request.return_value = self.mock_stats_data
+
+ sfv = SolidFireDriver(configuration=self.configuration)
+ self.assertEqual((None, False),
+ sfv.clone_image(self.ctxt,
+ self.mock_volume,
+ 'fake',
+ self.fake_image_meta,
+ 'fake'))
+
+ @mock.patch.object(SolidFireDriver, '_issue_api_request')
+ def test_clone_image_authorization(self, _mock_issue_api_request):
+ _mock_issue_api_request.return_value = self.mock_stats_data
+ self.configuration.sf_allow_template_caching = True
+ sfv = SolidFireDriver(configuration=self.configuration)
+
+ # Make sure if it's NOT public and we're NOT the owner it
+ # doesn't try and cache
+ _fake_image_meta = {'id': '17c550bb-a411-44c0-9aaf-0d96dd47f501',
+ 'updated_at': datetime.datetime(2013, 9,
+ 28, 15,
+ 27, 36,
+ 325355),
+ 'properties': {'virtual_size': 1},
+ 'is_public': False,
+ 'owner': 'wrong-owner'}
+ self.assertEqual((None, False),
+ sfv.clone_image(self.ctxt,
+ self.mock_volume,
+ 'fake',
+ _fake_image_meta,
+ 'fake'))
+
+ # And is_public False, but the correct owner does work
+ # expect raise AccountNotFound as that's the next call after
+ # auth checks
+ _fake_image_meta['owner'] = 'testprjid'
+ self.assertRaises(exception.SolidFireAccountNotFound,
+ sfv.clone_image, self.ctxt,
+ self.mock_volume, 'fake',
+ _fake_image_meta, 'fake')
+
+ # And is_public True, even if not the correct owner
+ _fake_image_meta['is_public'] = True
+ _fake_image_meta['owner'] = 'wrong-owner'
+ self.assertRaises(exception.SolidFireAccountNotFound,
+ sfv.clone_image, self.ctxt,
+ self.mock_volume, 'fake',
+ _fake_image_meta, 'fake')
+
+ @mock.patch.object(SolidFireDriver, '_issue_api_request')
+ def test_clone_image_virt_size_not_set(self, _mock_issue_api_request):
+ _mock_issue_api_request.return_value = self.mock_stats_data
+ self.configuration.sf_allow_template_caching = True
+ sfv = SolidFireDriver(configuration=self.configuration)
+
+ # Don't run clone_image if virtual_size property not on image
+ _fake_image_meta = {'id': '17c550bb-a411-44c0-9aaf-0d96dd47f501',
+ 'updated_at': datetime.datetime(2013, 9,
+ 28, 15,
+ 27, 36,
+ 325355),
+ 'is_public': True,
+ 'owner': 'testprjid'}
+
+ self.assertEqual((None, False),
+ sfv.clone_image(self.ctxt,
+ self.mock_volume,
+ 'fake',
+ _fake_image_meta,
+ 'fake'))
mock_trace_cls.return_value = mock_decorator
self.volume = importutils.import_object(CONF.volume_manager)
self.configuration = mock.Mock(conf.Configuration)
- #self.configuration = conf.Configuration(fake_opts, 'fake_group')
self.context = context.get_admin_context()
self.context.user_id = 'fake'
self.context.project_id = 'fake'
size=None):
pass
- def fake_clone_image(volume_ref, image_location, image_meta):
+ def fake_clone_image(ctx, volume_ref,
+ image_location, image_meta,
+ image_service):
return {'provider_location': None}, True
dst_fd, dst_path = tempfile.mkstemp()
{'path': host_device}))
return {'conn': conn, 'device': device, 'connector': connector}
- def clone_image(self, volume, image_location, image_meta):
+ def clone_image(self, context, volume,
+ image_location, image_meta,
+ image_service):
"""Create a volume efficiently from an existing image.
image_location is a string whose format depends on the
image service backend in use. The driver should use it
to determine whether cloning is possible.
- image_id is a string which represents id of the image.
- It can be used by the driver to introspect internal
- stores or registry to do an efficient image clone.
-
image_meta is a dictionary that includes 'disk_format' (e.g.
raw, qcow2) and other image attributes that allow drivers to
decide whether they can clone the image without first requiring
conversion.
+ image_service is the reference of the image_service to use.
+ Note that this is needed to be passed here for drivers that
+ will want to fetch images from the image service directly.
+
Returns a dict of volume properties eg. provider_location,
boolean indicating whether cloning occurred
"""
data['reserved_percentage'] = 0
self._stats = data
- def clone_image(self, volume, image_location, image_meta):
+ def clone_image(self, context, volume,
+ image_location, image_meta,
+ image_service):
"""Create a volume from the specified image."""
return self._clone_image(volume, image_location, image_meta['id'])
finally:
self.delete_snapshot(temp_snapshot)
- def clone_image(self, volume, image_location, image_meta):
+ def clone_image(self, context, volume,
+ image_location, image_meta,
+ image_service):
return None, False
def backup_volume(self, context, backup, backup_service):
LOG.warning(_LW('Exception during deleting %s'), ex.__str__())
return False
- def clone_image(self, volume, image_location, image_meta):
+ def clone_image(self, context, volume,
+ image_location, image_meta,
+ image_service):
"""Create a volume efficiently from an existing image.
image_location is a string whose format depends on the
image service backend in use. The driver should use it
to determine whether cloning is possible.
- image_id is a string which represents id of the image.
- It can be used by the driver to introspect internal
- stores or registry to do an efficient image clone.
-
Returns a dict of volume properties eg. provider_location,
boolean indicating whether cloning occurred.
"""
dict(loc=image_location, err=e))
return False
- def clone_image(self, volume, image_location, image_meta):
+ def clone_image(self, context, volume,
+ image_location, image_meta,
+ image_service):
image_location = image_location[0] if image_location else None
if image_location is None or not self._is_cloneable(
image_location, image_meta):
image_meta,
self.local_path(volume))
- def clone_image(self, volume, image_location, image_meta):
+ def clone_image(self, context, volume,
+ image_location, image_meta,
+ image_service):
"""Create a volume efficiently from an existing image.
image_location is a string whose format depends on the
image service backend in use. The driver should use it
to determine whether cloning is possible.
- image_id is a string which represents id of the image.
- It can be used by the driver to introspect internal
- stores or registry to do an efficient image clone.
+ image_meta is the metadata associated with the image and
+ includes properties like the image id, size, virtual-size
+ etc.
+
+ image_service is the reference of the image_service to use.
+ Note that this is needed to be passed here for drivers that
+ will want to fetch images from the image service directly.
Returns a dict of volume properties eg. provider_location,
boolean indicating whether cloning occurred
# under the License.
import json
+import math
import random
import socket
import string
from cinder import context
from cinder import exception
-from cinder.i18n import _, _LE, _LW
+from cinder.i18n import _, _LE, _LI, _LW
+from cinder.image import image_utils
from cinder.openstack.common import log as logging
from cinder.volume.drivers.san.san import SanISCSIDriver
from cinder.volume import qos_specs
'and will create a prefix using the cinder node hostsname '
'(previous default behavior). The default is NO prefix.'),
+ cfg.StrOpt('sf_template_account_name',
+ default='openstack-vtemplate',
+ help='Account name on the SolidFire Cluster to use as owner of '
+ 'template/cache volumes (created if doesnt exist).'),
+
+ cfg.BoolOpt('sf_allow_template_caching',
+ default=True,
+ help='Create an internal cache of copy of images when '
+ 'a bootable volume is created to eliminate fetch from '
+ 'glance and qemu-conversion on subsequent calls.'),
+
cfg.IntOpt('sf_api_port',
default=443,
help='SolidFire API port. Useful if the device api is behind '
params = {'accountID': sfaccount['accountID']}
sf_vol = self._get_sf_volume(src_uuid, params)
+
if sf_vol is None:
raise exception.VolumeNotFound(volume_id=src_uuid)
mesg = _('Failed to get model update from clone')
raise exception.SolidFireAPIException(mesg)
+ # Increment the usage count, just for data collection
+ cloned_count = sf_vol['attributes'].get('cloned_count', 0)
+ cloned_count += 1
+ attributes = sf_vol['attributes']
+ attributes['cloned_count'] = cloned_count
+
+ params = {'volumeID': int(sf_vol['volumeID'])}
+ params['attributes'] = attributes
+ data = self._issue_api_request('ModifyVolume', params)
return (data, sfaccount, model_update)
def _do_volume_create(self, project_id, params):
return sf_volref
+ def _create_image_volume(self, context,
+ image_meta, image_service,
+ image_id):
+ # NOTE(jdg): It's callers responsibility to ensure that
+ # the optional properties.virtual_size is set on the image
+ # before we get here
+ virt_size = int(image_meta['properties'].get('virtual_size'))
+ min_sz_in_bytes =\
+ math.ceil(virt_size / float(units.Gi)) * float(units.Gi)
+ min_sz_in_gig = math.ceil(min_sz_in_bytes / float(units.Gi))
+
+ attributes = {}
+ attributes['image_info'] = {}
+ attributes['image_info']['image_updated_at'] =\
+ image_meta['updated_at'].isoformat()
+ attributes['image_info']['image_name'] =\
+ image_meta['name']
+ attributes['image_info']['image_created_at'] =\
+ image_meta['created_at'].isoformat()
+ attributes['image_info']['image_id'] = image_meta['id']
+
+ params = {'name': 'OpenStackIMG-%s' % image_id,
+ 'accountID': None,
+ 'sliceCount': 1,
+ 'totalSize': int(min_sz_in_bytes),
+ 'enable512e': self.configuration.sf_emulate_512,
+ 'attributes': attributes,
+ 'qos': {}}
+
+ account = self.configuration.sf_template_account_name
+ template_vol = self._do_volume_create(account, params)
+ tvol = {}
+ tvol['id'] = image_id
+ tvol['provider_location'] = template_vol['provider_location']
+ tvol['provider_auth'] = template_vol['provider_auth']
+
+ connector = 'na'
+ conn = self.initialize_connection(tvol, connector)
+ attach_info = super(SolidFireDriver, self)._connect_device(conn)
+
+ sfaccount = self._get_sfaccount(account)
+ params = {'accountID': sfaccount['accountID']}
+ properties = 'na'
+
+ try:
+ image_utils.fetch_to_raw(context,
+ image_service,
+ image_id,
+ attach_info['device']['path'],
+ self.configuration.volume_dd_blocksize,
+ size=min_sz_in_gig)
+ except Exception as exc:
+ params['volumeID'] = template_vol['volumeID']
+ LOG.error(_LE('Failed image conversion during cache creation: %s'),
+ exc)
+ LOG.debug('Removing SolidFire Cache Volume (SF ID): %s',
+ template_vol['volumeID'])
+
+ self._detach_volume(context, attach_info, tvol, properties)
+ self._issue_api_request('DeleteVolume', params)
+ return
+
+ self._detach_volume(context, attach_info, tvol, properties)
+ sf_vol = self._get_sf_volume(image_id, params)
+ LOG.debug('Successfully created SolidFire Image Template ',
+ 'for image-id: %s', image_id)
+ return sf_vol
+
+ def _verify_image_volume(self, context, image_meta, image_service):
+ # This method just verifies that IF we have a cache volume that
+ # it's still up to date and current WRT the image in Glance
+ # ie an image-update hasn't occured since we grabbed it
+
+ # If it's out of date, just delete it and we'll create a new one
+ # Any other case we don't care and just return without doing anything
+
+ account = self.configuration.sf_template_account_name
+ sfaccount = self._get_sfaccount(account)
+ params = {'accountID': sfaccount['accountID']}
+ sf_vol = self._get_sf_volume(image_meta['id'], params)
+ if sf_vol is None:
+ return
+
+ # Check updated_at field, delete copy and update if needed
+ if sf_vol['attributes']['image_info']['image_updated_at'] ==\
+ image_meta['updated_at'].isoformat():
+ return
+ else:
+ # Bummer, it's been updated, delete it
+ params = {'accountID': sfaccount['accountID']}
+ params = {'volumeID': sf_vol['volumeID']}
+ data = self._issue_api_request('DeleteVolume', params)
+ if 'result' not in data:
+ msg = _("Failed to delete SolidFire Image-Volume: %s") % data
+ raise exception.SolidFireAPIException(msg)
+
+ if not self._create_image_volume(context,
+ image_meta,
+ image_service,
+ image_meta['id']):
+ msg = _("Failed to create SolidFire Image-Volume")
+ raise exception.SolidFireAPIException(msg)
+
+ def clone_image(self, context,
+ volume, image_location,
+ image_meta, image_service):
+
+ # Check out pre-requisites:
+ # Is template caching enabled?
+ if not self.configuration.sf_allow_template_caching:
+ return None, False
+
+ # Is the image owned by this tenant or public?
+ if ((not image_meta.get('is_public', False)) and
+ (image_meta['owner'] != volume['project_id'])):
+ LOG.warning(_LW("Requested image is not "
+ "accesible by current Tenant."))
+ return None, False
+
+ # Is virtual_size property set on the image?
+ if ((not image_meta.get('properties', None)) or
+ (not image_meta['properties'].get('virtual_size', None))):
+ LOG.info(_LI('Unable to create cache volume because image: %s '
+ 'does not include properties.virtual_size'),
+ image_meta['id'])
+ return None, False
+
+ try:
+ self._verify_image_volume(context,
+ image_meta,
+ image_service)
+ except exception.SolidFireAPIException:
+ return None, False
+
+ account = self.configuration.sf_template_account_name
+ try:
+ (data, sfaccount, model) = self._do_clone_volume(image_meta['id'],
+ account,
+ volume)
+ except exception.VolumeNotFound:
+ if self._create_image_volume(context,
+ image_meta,
+ image_service,
+ image_meta['id']) is None:
+ # We failed, dump out
+ return None, False
+
+ # Ok, should be good to go now, try it again
+ (data, sfaccount, model) = self._do_clone_volume(image_meta['id'],
+ account,
+ volume)
+
+ return model, True
+
def create_volume(self, volume):
"""Create volume on SolidFire device.
# NOTE (singn): two params need to be returned
# dict containing provider_location for cloned volume
# and clone status.
- model_update, cloned = self.driver.clone_image(
- volume_ref, image_location, image_meta)
+ model_update, cloned = self.driver.clone_image(context,
+ volume_ref,
+ image_location,
+ image_meta,
+ image_service)
if not cloned:
# TODO(harlowja): what needs to be rolled back in the clone if this
# volume create fails?? Likely this should be a subflow or broken