Use SolidFire snapshots for Cinder snapshots

This patch just replaces the use of clones on the SolidFire
backend for SolidFire Snapshot objects.  This isn't necessarily
an issue, but it is important necessary to do things like
consistency group snaps.

Note, that we have to keep compatability for things like
create_from_snapshot where the system may have clones that
were used for snapshots prior to updating the driver.

While we're here, we also removed the logging calls in the test
which shouldn't really be there, as well as the overly chatty
debug messages like "entering/leaving".  More cleanup work
can be done to logs but should be in their own patch.

Change-Id: Ia67b2b9e3c0bf3b0b1f991e958f8eac13830f9cc
This commit is contained in:
John Griffith 2015-05-29 23:41:25 +00:00
parent d4dc36d00a
commit 6864201def
2 changed files with 102 additions and 76 deletions

View File

@ -18,7 +18,6 @@ import datetime
import mock import mock
from mox3 import mox from mox3 import mox
from oslo_log import log as logging
from oslo_utils import timeutils from oslo_utils import timeutils
from oslo_utils import units from oslo_utils import units
@ -30,8 +29,6 @@ from cinder.volume.drivers import solidfire
from cinder.volume import qos_specs from cinder.volume import qos_specs
from cinder.volume import volume_types from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
def create_configuration(): def create_configuration():
configuration = mox.MockObject(conf.Configuration) configuration = mox.MockObject(conf.Configuration)
@ -101,7 +98,6 @@ class SolidFireVolumeTestCase(test.TestCase):
def fake_issue_api_request(obj, method, params, version='1.0'): def fake_issue_api_request(obj, method, params, version='1.0'):
if method is 'GetClusterCapacity' and version == '1.0': if method is 'GetClusterCapacity' and version == '1.0':
LOG.info('Called Fake GetClusterCapacity...')
data = {'result': data = {'result':
{'clusterCapacity': {'maxProvisionedSpace': 107374182400, {'clusterCapacity': {'maxProvisionedSpace': 107374182400,
'usedSpace': 1073741824, 'usedSpace': 1073741824,
@ -111,7 +107,6 @@ class SolidFireVolumeTestCase(test.TestCase):
return data return data
elif method is 'GetClusterInfo' and version == '1.0': elif method is 'GetClusterInfo' and version == '1.0':
LOG.info('Called Fake GetClusterInfo...')
results = {'result': {'clusterInfo': results = {'result': {'clusterInfo':
{'name': 'fake-cluster', {'name': 'fake-cluster',
'mvip': '1.1.1.1', 'mvip': '1.1.1.1',
@ -122,11 +117,9 @@ class SolidFireVolumeTestCase(test.TestCase):
return results return results
elif method is 'AddAccount' and version == '1.0': elif method is 'AddAccount' and version == '1.0':
LOG.info('Called Fake AddAccount...')
return {'result': {'accountID': 25}, 'id': 1} return {'result': {'accountID': 25}, 'id': 1}
elif method is 'GetAccountByName' and version == '1.0': elif method is 'GetAccountByName' and version == '1.0':
LOG.info('Called Fake GetAccountByName...')
results = {'result': {'account': results = {'result': {'account':
{'accountID': 25, {'accountID': 25,
'username': params['username'], 'username': params['username'],
@ -139,15 +132,15 @@ class SolidFireVolumeTestCase(test.TestCase):
return results return results
elif method is 'CreateVolume' and version == '1.0': elif method is 'CreateVolume' and version == '1.0':
LOG.info('Called Fake CreateVolume...')
return {'result': {'volumeID': 5}, 'id': 1} 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': elif method is 'DeleteVolume' and version == '1.0':
LOG.info('Called Fake DeleteVolume...')
return {'result': {}, 'id': 1} return {'result': {}, 'id': 1}
elif method is 'ModifyVolume' and version == '5.0': elif method is 'ModifyVolume' and version == '5.0':
LOG.info('Called Fake ModifyVolume...')
return {'result': {}, 'id': 1} return {'result': {}, 'id': 1}
elif method is 'CloneVolume': elif method is 'CloneVolume':
@ -158,7 +151,6 @@ class SolidFireVolumeTestCase(test.TestCase):
elif method is 'ListVolumesForAccount' and version == '1.0': elif method is 'ListVolumesForAccount' and version == '1.0':
test_name = 'OS-VOLID-a720b3c0-d1f0-11e1-9b23-0800200c9a66' test_name = 'OS-VOLID-a720b3c0-d1f0-11e1-9b23-0800200c9a66'
LOG.info('Called Fake ListVolumesForAccount...')
result = {'result': { result = {'result': {
'volumes': [{'volumeID': 5, 'volumes': [{'volumeID': 5,
'name': test_name, 'name': test_name,
@ -189,7 +181,8 @@ class SolidFireVolumeTestCase(test.TestCase):
'iqn': test_name}]}} 'iqn': test_name}]}}
return result return result
else: 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, def fake_issue_api_request_fails(obj, method,
params, version='1.0', params, version='1.0',
@ -265,13 +258,7 @@ class SolidFireVolumeTestCase(test.TestCase):
'4096 4096') '4096 4096')
self.configuration.sf_emulate_512 = True self.configuration.sf_emulate_512 = True
def test_create_snapshot(self): def test_create_delete_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)
testvol = {'project_id': 'testprjid', testvol = {'project_id': 'testprjid',
'name': 'testvol', 'name': 'testvol',
'size': 1, 'size': 1,
@ -290,6 +277,11 @@ class SolidFireVolumeTestCase(test.TestCase):
sfv = solidfire.SolidFireDriver(configuration=self.configuration) sfv = solidfire.SolidFireDriver(configuration=self.configuration)
sfv.create_volume(testvol) sfv.create_volume(testvol)
sfv.create_snapshot(testsnap) 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): def test_create_clone(self):
self.stubs.Set(solidfire.SolidFireDriver, self.stubs.Set(solidfire.SolidFireDriver,
@ -312,8 +304,11 @@ class SolidFireVolumeTestCase(test.TestCase):
'volume_type_id': None, 'volume_type_id': None,
'created_at': timeutils.utcnow()} 'created_at': timeutils.utcnow()}
sfv = solidfire.SolidFireDriver(configuration=self.configuration) with mock.patch.object(solidfire.SolidFireDriver,
sfv.create_cloned_volume(testvol_b, testvol) '_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): def test_initialize_connector_with_blocksizes(self):
connector = {'initiator': 'iqn.2012-07.org.fake:01'} connector = {'initiator': 'iqn.2012-07.org.fake:01'}

View File

@ -343,7 +343,9 @@ class SolidFireDriver(san.SanISCSIDriver):
return model_update 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. """Create a clone of an existing volume.
Currently snapshots are the same as clones on the SF cluster. Currently snapshots are the same as clones on the SF cluster.
@ -353,17 +355,11 @@ class SolidFireDriver(san.SanISCSIDriver):
implemented in the pre-release version of the SolidFire Cluster. implemented in the pre-release version of the SolidFire Cluster.
""" """
attributes = {} attributes = {}
qos = {} qos = {}
sfaccount = self._get_sfaccount(src_project_id) 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']: if src_project_id != v_ref['project_id']:
sfaccount = self._create_sfaccount(v_ref['project_id']) sfaccount = self._create_sfaccount(v_ref['project_id'])
@ -372,12 +368,30 @@ class SolidFireDriver(san.SanISCSIDriver):
else: else:
new_size = v_ref['volume_size'] new_size = v_ref['volume_size']
params = {'volumeID': int(sf_vol['volumeID']), params = {'name': 'UUID-%s' % v_ref['id'],
'name': 'UUID-%s' % v_ref['id'],
'newSize': int(new_size * units.Gi), 'newSize': int(new_size * units.Gi),
'newAccountID': sfaccount['accountID']} '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'])): if (('result' not in data) or ('volumeID' not in data['result'])):
msg = _("API response: %s") % data msg = _("API response: %s") % data
raise exception.SolidFireAPIException(msg) raise exception.SolidFireAPIException(msg)
@ -415,19 +429,20 @@ class SolidFireDriver(san.SanISCSIDriver):
raise exception.SolidFireAPIException(mesg) raise exception.SolidFireAPIException(mesg)
# Increment the usage count, just for data collection # Increment the usage count, just for data collection
cloned_count = sf_vol['attributes'].get('cloned_count', 0) # We're only doing this for clones, not create_from snaps
cloned_count += 1 if is_clone:
attributes = sf_vol['attributes'] cloned_count = sf_vol['attributes'].get('cloned_count', 0)
attributes['cloned_count'] = cloned_count cloned_count += 1
attributes = sf_vol['attributes']
attributes['cloned_count'] = cloned_count
params = {'volumeID': int(sf_vol['volumeID'])} params = {'volumeID': int(sf_vol['volumeID'])}
params['attributes'] = attributes params['attributes'] = attributes
data = self._issue_api_request('ModifyVolume', params) data = self._issue_api_request('ModifyVolume', params)
return (data, sfaccount, model_update) return (data, sfaccount, model_update)
def _do_volume_create(self, project_id, params): def _do_volume_create(self, project_id, params):
sfaccount = self._create_sfaccount(project_id) sfaccount = self._create_sfaccount(project_id)
params['accountID'] = sfaccount['accountID'] params['accountID'] = sfaccount['accountID']
data = self._issue_api_request('CreateVolume', params) data = self._issue_api_request('CreateVolume', params)
@ -438,6 +453,13 @@ class SolidFireDriver(san.SanISCSIDriver):
sf_volume_id = data['result']['volumeID'] sf_volume_id = data['result']['volumeID']
return self._get_model_info(sfaccount, sf_volume_id) 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): def _set_qos_presets(self, volume):
qos = {} qos = {}
valid_presets = self.sf_qos_dict.keys() valid_presets = self.sf_qos_dict.keys()
@ -480,9 +502,6 @@ class SolidFireDriver(san.SanISCSIDriver):
return qos return qos
def _get_sf_volume(self, uuid, params): 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) data = self._issue_api_request('ListVolumesForAccount', params)
if 'result' not in data: if 'result' not in data:
msg = _("Failed to get SolidFire Volume: %s") % data msg = _("Failed to get SolidFire Volume: %s") % data
@ -518,6 +537,16 @@ class SolidFireDriver(san.SanISCSIDriver):
return sf_volref 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, def _create_image_volume(self, context,
image_meta, image_service, image_meta, image_service,
image_id): image_id):
@ -743,9 +772,6 @@ class SolidFireDriver(san.SanISCSIDriver):
volumeID is what's guaranteed unique. volumeID is what's guaranteed unique.
""" """
LOG.debug("Enter SolidFire delete_volume...")
sfaccount = self._get_sfaccount(volume['project_id']) sfaccount = self._get_sfaccount(volume['project_id'])
if sfaccount is None: if sfaccount is None:
LOG.error(_LE("Account for Volume ID %s was not found on " LOG.error(_LE("Account for Volume ID %s was not found on "
@ -756,7 +782,6 @@ class SolidFireDriver(san.SanISCSIDriver):
return return
params = {'accountID': sfaccount['accountID']} params = {'accountID': sfaccount['accountID']}
sf_vol = self._get_sf_volume(volume['id'], params) sf_vol = self._get_sf_volume(volume['id'], params)
if sf_vol is not None: if sf_vol is not None:
@ -771,11 +796,8 @@ class SolidFireDriver(san.SanISCSIDriver):
"the SolidFire Cluster while attempting " "the SolidFire Cluster while attempting "
"delete_volume operation!"), volume['id']) "delete_volume operation!"), volume['id'])
LOG.debug("Leaving SolidFire delete_volume")
def ensure_export(self, context, volume): def ensure_export(self, context, volume):
"""Verify the iscsi export info.""" """Verify the iscsi export info."""
LOG.debug("Executing SolidFire ensure_export...")
try: try:
return self._do_export(volume) return self._do_export(volume)
except exception.SolidFireAPIException: except exception.SolidFireAPIException:
@ -783,29 +805,50 @@ class SolidFireDriver(san.SanISCSIDriver):
def create_export(self, context, volume): def create_export(self, context, volume):
"""Setup the iscsi export info.""" """Setup the iscsi export info."""
LOG.debug("Executing SolidFire create_export...")
return self._do_export(volume) return self._do_export(volume)
def delete_snapshot(self, snapshot): def delete_snapshot(self, snapshot):
"""Delete the specified snapshot from the SolidFire cluster.""" """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): 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 params = {'accountID': sfaccount['accountID']}
implementation. Due to the way SF does cloning there's no performance sf_vol = self._get_sf_volume(snapshot['volume_id'], params)
hit or extra space used. The only thing that's lacking from this is
the ability to restore snaps.
After GA a true snapshot implementation will be available with if sf_vol is None:
restore at which time we'll rework this appropriately. raise exception.VolumeNotFound(volume_id=snapshot['volume_id'])
params = {'volumeID': sf_vol['volumeID'],
'name': 'UUID-%s' % snapshot['id']}
""" self._do_snapshot_create(params)
(_data, _sfaccount, _model) = self._do_clone_volume(
snapshot['volume_id'],
snapshot['project_id'],
snapshot)
def create_volume_from_snapshot(self, volume, snapshot): def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from the specified snapshot.""" """Create a volume from the specified snapshot."""
@ -834,8 +877,6 @@ class SolidFireDriver(san.SanISCSIDriver):
def extend_volume(self, volume, new_size): def extend_volume(self, volume, new_size):
"""Extend an existing volume.""" """Extend an existing volume."""
LOG.debug("Entering SolidFire extend_volume...")
sfaccount = self._get_sfaccount(volume['project_id']) sfaccount = self._get_sfaccount(volume['project_id'])
params = {'accountID': sfaccount['accountID']} params = {'accountID': sfaccount['accountID']}
@ -857,13 +898,8 @@ class SolidFireDriver(san.SanISCSIDriver):
if 'result' not in data: if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data) raise exception.SolidFireAPIDataException(data=data)
LOG.debug("Leaving SolidFire extend_volume")
def _update_cluster_status(self): def _update_cluster_status(self):
"""Retrieve status info for the Cluster.""" """Retrieve status info for the Cluster."""
LOG.debug("Updating cluster status info")
params = {} params = {}
# NOTE(jdg): The SF api provides an UNBELIEVABLE amount # NOTE(jdg): The SF api provides an UNBELIEVABLE amount
@ -901,7 +937,6 @@ class SolidFireDriver(san.SanISCSIDriver):
instance_uuid, host_name, instance_uuid, host_name,
mountpoint): mountpoint):
LOG.debug("Entering SolidFire attach_volume...")
sfaccount = self._get_sfaccount(volume['project_id']) sfaccount = self._get_sfaccount(volume['project_id'])
params = {'accountID': sfaccount['accountID']} params = {'accountID': sfaccount['accountID']}
@ -926,8 +961,6 @@ class SolidFireDriver(san.SanISCSIDriver):
raise exception.SolidFireAPIDataException(data=data) raise exception.SolidFireAPIDataException(data=data)
def detach_volume(self, context, volume, attachment=None): def detach_volume(self, context, volume, attachment=None):
LOG.debug("Entering SolidFire attach_volume...")
sfaccount = self._get_sfaccount(volume['project_id']) sfaccount = self._get_sfaccount(volume['project_id'])
params = {'accountID': sfaccount['accountID']} params = {'accountID': sfaccount['accountID']}
@ -980,7 +1013,6 @@ class SolidFireDriver(san.SanISCSIDriver):
volume['project_id'] = new_project volume['project_id'] = new_project
volume['user_id'] = new_user volume['user_id'] = new_user
model_update = self._do_export(volume) model_update = self._do_export(volume)
LOG.debug("Leaving SolidFire transfer volume")
return model_update return model_update
def retype(self, ctxt, volume, new_type, diff, host): def retype(self, ctxt, volume, new_type, diff, host):
@ -1101,7 +1133,6 @@ class SolidFireDriver(san.SanISCSIDriver):
def unmanage(self, volume): def unmanage(self, volume):
"""Mark SolidFire Volume as unmanaged (export from Cinder).""" """Mark SolidFire Volume as unmanaged (export from Cinder)."""
LOG.debug("Enter SolidFire unmanage...")
sfaccount = self._get_sfaccount(volume['project_id']) sfaccount = self._get_sfaccount(volume['project_id'])
if sfaccount is None: if sfaccount is None:
LOG.error(_LE("Account for Volume ID %s was not found on " LOG.error(_LE("Account for Volume ID %s was not found on "