import mock
from mox3 import mox
-from oslo_log import log as logging
from oslo_utils import timeutils
from oslo_utils import units
from cinder.volume import qos_specs
from cinder.volume import volume_types
-LOG = logging.getLogger(__name__)
-
def create_configuration():
configuration = mox.MockObject(conf.Configuration)
def fake_issue_api_request(obj, method, params, version='1.0'):
if method is 'GetClusterCapacity' and version == '1.0':
- LOG.info('Called Fake GetClusterCapacity...')
data = {'result':
{'clusterCapacity': {'maxProvisionedSpace': 107374182400,
'usedSpace': 1073741824,
return data
elif method is 'GetClusterInfo' and version == '1.0':
- LOG.info('Called Fake GetClusterInfo...')
results = {'result': {'clusterInfo':
{'name': 'fake-cluster',
'mvip': '1.1.1.1',
return results
elif method is 'AddAccount' and version == '1.0':
- LOG.info('Called Fake AddAccount...')
return {'result': {'accountID': 25}, 'id': 1}
elif method is 'GetAccountByName' and version == '1.0':
- LOG.info('Called Fake GetAccountByName...')
results = {'result': {'account':
{'accountID': 25,
'username': params['username'],
return results
elif method is 'CreateVolume' and version == '1.0':
- LOG.info('Called Fake CreateVolume...')
return {'result': {'volumeID': 5}, 'id': 1}
+ elif method is 'CreateSnapshot' and version == '6.0':
+ return {'result': {'snapshotID': 5}, 'id': 1}
+
elif method is 'DeleteVolume' and version == '1.0':
- LOG.info('Called Fake DeleteVolume...')
return {'result': {}, 'id': 1}
elif method is 'ModifyVolume' and version == '5.0':
- LOG.info('Called Fake ModifyVolume...')
return {'result': {}, 'id': 1}
elif method is 'CloneVolume':
elif method is 'ListVolumesForAccount' and version == '1.0':
test_name = 'OS-VOLID-a720b3c0-d1f0-11e1-9b23-0800200c9a66'
- LOG.info('Called Fake ListVolumesForAccount...')
result = {'result': {
'volumes': [{'volumeID': 5,
'name': test_name,
'iqn': test_name}]}}
return result
else:
- LOG.error('Crap, unimplemented API call in Fake:%s' % method)
+ # Crap, unimplemented API call in Fake
+ return None
def fake_issue_api_request_fails(obj, method,
params, version='1.0',
'4096 4096')
self.configuration.sf_emulate_512 = True
- def test_create_snapshot(self):
- self.stubs.Set(solidfire.SolidFireDriver,
- '_issue_api_request',
- self.fake_issue_api_request)
- self.stubs.Set(solidfire.SolidFireDriver,
- '_get_model_info',
- self.fake_get_model_info)
+ def test_create_delete_snapshot(self):
testvol = {'project_id': 'testprjid',
'name': 'testvol',
'size': 1,
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
sfv.create_volume(testvol)
sfv.create_snapshot(testsnap)
+ with mock.patch.object(solidfire.SolidFireDriver,
+ '_get_sf_snapshots',
+ return_value=[{'snapshotID': '1',
+ 'name': 'testvol'}]):
+ sfv.delete_snapshot(testsnap)
def test_create_clone(self):
self.stubs.Set(solidfire.SolidFireDriver,
'volume_type_id': None,
'created_at': timeutils.utcnow()}
- sfv = solidfire.SolidFireDriver(configuration=self.configuration)
- sfv.create_cloned_volume(testvol_b, testvol)
+ with mock.patch.object(solidfire.SolidFireDriver,
+ '_get_sf_snapshots',
+ return_value=[]):
+ sfv = solidfire.SolidFireDriver(configuration=self.configuration)
+ sfv.create_cloned_volume(testvol_b, testvol)
def test_initialize_connector_with_blocksizes(self):
connector = {'initiator': 'iqn.2012-07.org.fake:01'}
return model_update
- def _do_clone_volume(self, src_uuid, src_project_id, v_ref):
+ def _do_clone_volume(self, src_uuid,
+ src_project_id,
+ v_ref):
"""Create a clone of an existing volume.
Currently snapshots are the same as clones on the SF cluster.
implemented in the pre-release version of the SolidFire Cluster.
"""
+
attributes = {}
qos = {}
sfaccount = self._get_sfaccount(src_project_id)
- 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)
-
if src_project_id != v_ref['project_id']:
sfaccount = self._create_sfaccount(v_ref['project_id'])
else:
new_size = v_ref['volume_size']
- params = {'volumeID': int(sf_vol['volumeID']),
- 'name': 'UUID-%s' % v_ref['id'],
+ params = {'name': 'UUID-%s' % v_ref['id'],
'newSize': int(new_size * units.Gi),
'newAccountID': sfaccount['accountID']}
- data = self._issue_api_request('CloneVolume', params)
+ # NOTE(jdg): First check the SF snapshots
+ # if we don't find a snap by the given name, just move on to check
+ # volumes. This may be a running system that was updated from
+ # before we did snapshots, so need to check both
+ snap_name = 'UUID-%s' % src_uuid
+ snaps = self._get_sf_snapshots()
+ snap = next((s for s in snaps if s["name"] == snap_name), None)
+ is_clone = False
+ if snap:
+ params['snapshotID'] = int(snap['snapshotID'])
+ params['volumeID'] = int(snap['volumeID'])
+ else:
+ sf_vol = self._get_sf_volume(
+ src_uuid, {'accountID': sfaccount['accountID']})
+ if sf_vol is None:
+ raise exception.VolumeNotFound(volume_id=src_uuid)
+ params['volumeID'] = int(sf_vol['volumeID'])
+ is_clone = True
+
+ data = self._issue_api_request('CloneVolume', params, version='6.0')
if (('result' not in data) or ('volumeID' not in data['result'])):
msg = _("API response: %s") % data
raise exception.SolidFireAPIException(msg)
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)
+ # We're only doing this for clones, not create_from snaps
+ if is_clone:
+ 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):
sfaccount = self._create_sfaccount(project_id)
-
params['accountID'] = sfaccount['accountID']
data = self._issue_api_request('CreateVolume', params)
sf_volume_id = data['result']['volumeID']
return self._get_model_info(sfaccount, sf_volume_id)
+ def _do_snapshot_create(self, params):
+ data = self._issue_api_request('CreateSnapshot', params, version='6.0')
+ if (('result' not in data) or ('snapshotID' not in data['result'])):
+ msg = _("Failed snapshot create: %s") % data
+ raise exception.SolidFireAPIException(msg)
+ return data['result']['snapshotID']
+
def _set_qos_presets(self, volume):
qos = {}
valid_presets = self.sf_qos_dict.keys()
return qos
def _get_sf_volume(self, uuid, params):
- # TODO(jdg): Going to fix this shortly to not iterate
- # but instead use the cinder UUID and our internal
- # mapping to get this more efficiently
data = self._issue_api_request('ListVolumesForAccount', params)
if 'result' not in data:
msg = _("Failed to get SolidFire Volume: %s") % data
return sf_volref
+ def _get_sf_snapshots(self, sf_volid=None):
+ params = {}
+ if sf_volid:
+ params = {'volumeID': sf_volid}
+ data = self._issue_api_request('ListSnapshots', params, version='6.0')
+ if 'result' not in data:
+ msg = _("Failed to get SolidFire Snapshot: %s") % data
+ raise exception.SolidFireAPIException(msg)
+ return data['result']['snapshots']
+
def _create_image_volume(self, context,
image_meta, image_service,
image_id):
volumeID is what's guaranteed unique.
"""
-
- LOG.debug("Enter SolidFire delete_volume...")
-
sfaccount = self._get_sfaccount(volume['project_id'])
if sfaccount is None:
LOG.error(_LE("Account for Volume ID %s was not found on "
return
params = {'accountID': sfaccount['accountID']}
-
sf_vol = self._get_sf_volume(volume['id'], params)
if sf_vol is not None:
"the SolidFire Cluster while attempting "
"delete_volume operation!"), volume['id'])
- LOG.debug("Leaving SolidFire delete_volume")
-
def ensure_export(self, context, volume):
"""Verify the iscsi export info."""
- LOG.debug("Executing SolidFire ensure_export...")
try:
return self._do_export(volume)
except exception.SolidFireAPIException:
def create_export(self, context, volume):
"""Setup the iscsi export info."""
- LOG.debug("Executing SolidFire create_export...")
return self._do_export(volume)
def delete_snapshot(self, snapshot):
"""Delete the specified snapshot from the SolidFire cluster."""
- self.delete_volume(snapshot)
+ sf_snap_name = 'UUID-%s' % snapshot['id']
+ sfaccount = self._get_sfaccount(snapshot['project_id'])
+ params = {'accountID': sfaccount['accountID'],
+ 'name': sf_snap_name}
+ params = {'accountID': sfaccount['accountID']}
+
+ # Get the parent volume of the snapshot
+ sf_vol = self._get_sf_volume(snapshot['volume_id'], params)
+ sf_snaps = self._get_sf_snapshots(sf_vol['volumeID'])
+ snap = next((s for s in sf_snaps if s["name"] == sf_snap_name), None)
+ if snap:
+ params = {'snapshotID': snap['snapshotID']}
+ data = self._issue_api_request('DeleteSnapshot',
+ params,
+ version='6.0')
+ if 'result' not in data:
+ msg = (_("Failed to delete SolidFire Snapshot: %s") %
+ data)
+ raise exception.SolidFireAPIException(msg)
+ else:
+ # Make sure it's not "old style" using clones as snaps
+ LOG.debug("Snapshot not found, checking old style clones.")
+ self.delete_volume(snapshot)
def create_snapshot(self, snapshot):
- """Create a snapshot of a volume on the SolidFire cluster.
+ sfaccount = self._get_sfaccount(snapshot['project_id'])
+ if sfaccount is None:
+ LOG.error(_LE("Account for Volume ID %s was not found on "
+ "the SolidFire Cluster while attempting "
+ "create_snapshot operation!"), snapshot['volume_id'])
- Note that for SolidFire Clusters currently there is no snapshot
- implementation. Due to the way SF does cloning there's no performance
- hit or extra space used. The only thing that's lacking from this is
- the ability to restore snaps.
+ params = {'accountID': sfaccount['accountID']}
+ sf_vol = self._get_sf_volume(snapshot['volume_id'], params)
- After GA a true snapshot implementation will be available with
- restore at which time we'll rework this appropriately.
+ if sf_vol is None:
+ raise exception.VolumeNotFound(volume_id=snapshot['volume_id'])
+ params = {'volumeID': sf_vol['volumeID'],
+ 'name': 'UUID-%s' % snapshot['id']}
- """
- (_data, _sfaccount, _model) = self._do_clone_volume(
- snapshot['volume_id'],
- snapshot['project_id'],
- snapshot)
+ self._do_snapshot_create(params)
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from the specified snapshot."""
def extend_volume(self, volume, new_size):
"""Extend an existing volume."""
- LOG.debug("Entering SolidFire extend_volume...")
-
sfaccount = self._get_sfaccount(volume['project_id'])
params = {'accountID': sfaccount['accountID']}
if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data)
- LOG.debug("Leaving SolidFire extend_volume")
-
def _update_cluster_status(self):
"""Retrieve status info for the Cluster."""
-
- LOG.debug("Updating cluster status info")
-
params = {}
# NOTE(jdg): The SF api provides an UNBELIEVABLE amount
instance_uuid, host_name,
mountpoint):
- LOG.debug("Entering SolidFire attach_volume...")
sfaccount = self._get_sfaccount(volume['project_id'])
params = {'accountID': sfaccount['accountID']}
raise exception.SolidFireAPIDataException(data=data)
def detach_volume(self, context, volume, attachment=None):
-
- LOG.debug("Entering SolidFire attach_volume...")
sfaccount = self._get_sfaccount(volume['project_id'])
params = {'accountID': sfaccount['accountID']}
volume['project_id'] = new_project
volume['user_id'] = new_user
model_update = self._do_export(volume)
- LOG.debug("Leaving SolidFire transfer volume")
return model_update
def retype(self, ctxt, volume, new_type, diff, host):
def unmanage(self, volume):
"""Mark SolidFire Volume as unmanaged (export from Cinder)."""
- LOG.debug("Enter SolidFire unmanage...")
sfaccount = self._get_sfaccount(volume['project_id'])
if sfaccount is None:
LOG.error(_LE("Account for Volume ID %s was not found on "