diff --git a/cinder/tests/test_solidfire.py b/cinder/tests/test_solidfire.py index ec5126310ea..09f0dca4e5a 100644 --- a/cinder/tests/test_solidfire.py +++ b/cinder/tests/test_solidfire.py @@ -130,6 +130,21 @@ class SolidFireVolumeTestCase(test.TestCase): 'iqn': test_name}]}} return result + elif method is 'ListActiveVolumes': + test_name = "existing_volume" + result = {'result': { + 'volumes': [{'volumeID': 5, + 'name': test_name, + 'accountID': 8, + 'sliceCount': 1, + 'totalSize': 1 * units.Gi, + 'enable512e': True, + 'access': "readWrite", + 'status': "active", + 'attributes': {}, + 'qos': None, + 'iqn': test_name}]}} + return result else: LOG.error('Crap, unimplemented API call in Fake:%s' % method) @@ -572,3 +587,17 @@ class SolidFireVolumeTestCase(test.TestCase): sfv._update_cluster_status() self.assertEqual(sfv.cluster_stats['free_capacity_gb'], 99.0) self.assertEqual(sfv.cluster_stats['total_capacity_gb'], 100.0) + + def test_manage_existing_volume(self): + external_ref = {'name': 'existing volume', 'source-id': 5} + testvol = {'project_id': 'testprjid', + 'name': 'testvol', + 'size': 1, + 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66', + 'created_at': timeutils.utcnow()} + self.stubs.Set(SolidFireDriver, '_issue_api_request', + self.fake_issue_api_request) + sfv = SolidFireDriver(configuration=self.configuration) + model_update = sfv.manage_existing(testvol, external_ref) + self.assertIsNotNone(model_update) + self.assertIsNone(model_update.get('provider_geometry', None)) diff --git a/cinder/volume/drivers/solidfire.py b/cinder/volume/drivers/solidfire.py index 5e1248a8504..279eceb6cf8 100644 --- a/cinder/volume/drivers/solidfire.py +++ b/cinder/volume/drivers/solidfire.py @@ -68,10 +68,12 @@ class SolidFireDriver(SanISCSIDriver): Version history: 1.0 - Initial driver 1.1 - Refactor, clone support, qos by type and minor bug fixes + 1.2 - Add xfr and retype support + 1.2.1 - Add export/import support """ - VERSION = '1.2.0' + VERSION = '1.2.1' sf_qos_dict = {'slow': {'minIOPS': 100, 'maxIOPS': 200, @@ -462,7 +464,13 @@ class SolidFireDriver(SanISCSIDriver): found_count = 0 sf_volref = None for v in data['result']['volumes']: - if uuid in v['name']: + # NOTE(jdg): In the case of "name" we can't + # update that on manage/import, so we use + # the uuid attribute + meta = v.get('attributes') + alt_id = meta.get('uuid', 'empty') + + if uuid in v['name'] or uuid in alt_id: found_count += 1 sf_volref = v LOG.debug("Mapped SolidFire volumeID %(sfid)s " @@ -813,3 +821,107 @@ class SolidFireDriver(SanISCSIDriver): self._issue_api_request('ModifyVolume', params) return True + + def manage_existing(self, volume, external_ref): + """Manages an existing SolidFire Volume (import to Cinder). + + Renames the Volume to match the expected name for the volume. + Also need to consider things like QoS, Emulation, account/tenant. + """ + + sfid = external_ref.get('source-id', None) + sfname = external_ref.get('name', None) + if sfid is None: + raise exception.SolidFireAPIException("Manage existing volume " + "requires 'source-id'.") + + # First get the volume on the SF cluster (MUST be active) + params = {'startVolumeID': sfid, + 'limit': 1} + data = self._issue_api_request('ListActiveVolumes', params) + if 'result' not in data: + raise exception.SolidFireAPIDataException(data=data) + sf_ref = data['result']['volumes'][0] + + sfaccount = self._create_sfaccount(volume['project_id']) + + attributes = {} + qos = {} + if (self.configuration.sf_allow_tenant_qos and + volume.get('volume_metadata')is not None): + qos = self._set_qos_presets(volume) + + ctxt = context.get_admin_context() + type_id = volume.get('volume_type_id', None) + if type_id is not None: + qos = self._set_qos_by_volume_type(ctxt, type_id) + + import_time = timeutils.strtime(volume['created_at']) + attributes = {'uuid': volume['id'], + 'is_clone': 'False', + 'os_imported_at': import_time, + 'old_name': sfname} + if qos: + for k, v in qos.items(): + attributes[k] = str(v) + + params = {'name': volume['name'], + 'volumeID': sf_ref['volumeID'], + 'accountID': sfaccount['accountID'], + 'enable512e': self.configuration.sf_emulate_512, + 'attributes': attributes, + 'qos': qos} + + data = self._issue_api_request('ModifyVolume', + params, version='5.0') + if 'result' not in data: + raise exception.SolidFireAPIDataException(data=data) + + return self._get_model_info(sfaccount, sf_ref['volumeID']) + + def manage_existing_get_size(self, volume, external_ref): + """Return size of an existing LV for manage_existing. + + existing_ref is a dictionary of the form: + {'name': } + """ + + sfid = external_ref.get('source-id', None) + if sfid is None: + raise exception.SolidFireAPIException("Manage existing get size " + "requires 'id'.") + + params = {'startVolumeID': int(sfid), + 'limit': 1} + data = self._issue_api_request('ListActiveVolumes', params) + if 'result' not in data: + raise exception.SolidFireAPIDataException(data=data) + sf_ref = data['result']['volumes'][0] + return int(sf_ref['totalSize']) / int(units.Gi) + + 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(_("Account for Volume ID %s was not found on " + "the SolidFire Cluster!") % volume['id']) + raise exception.SolidFireAPIException("Failed to find account " + "for volume.") + + params = {'accountID': sfaccount['accountID']} + sf_vol = self._get_sf_volume(volume['id'], params) + if sf_vol is None: + raise exception.VolumeNotFound(volume_id=volume['id']) + + export_time = timeutils.strtime() + attributes = sf_vol['attributes'] + attributes['os_exported_at'] = export_time + params = {'volumeID': int(sf_vol['volumeID']), + 'attributes': attributes} + + data = self._issue_api_request('ModifyVolume', + params, version='5.0') + if 'result' not in data: + raise exception.SolidFireAPIDataException(data=data)